Re: [thredds] [Ncwms-users] Integrating ncWMS trunk into THREDDS

Hi Matthias (and all),
I've followed your work integrating ncWMS trunk into THREDDS and after adding a
few more changes to both ncWMS trunk and TDS code I got it working.

I've worked on 919 code revision of ncWMS trunk and on 13794 revision of TDS.
The features added to TDS are the Kyle Wilcox's styles for vector layers (the
styles weren't added to the capabilities_xml.jsp
andcapabilities_xml_1_1_1.jspfiles yet so they aren't announced in the
capabilities documents) and theGetVerticalProfile and GetVerticalSection
requests.
Besides the code changes, I had to update the geotk-bundle-referencing.jar from
version 3.04 to version 3.17 and add the prtree.jaron TDS third party libraries
needed for runtime.
I understand these changes aren't the best way to do the integration but at
least are a workaroundwhile there isn't a new TDS version with an updated ncWMS
andin case someone else is interested.Â



Â
Cheers!
Â
Â

On May 9, 2011 at 8:50 AM "Matthias MÃller" <Matthias_Mueller@xxxxxxxxxxxxx>
wrote:

> Hi Jon and Kyle,
>
> here are the results of my integration efforts with thredds-trunk and
> ncwms-trunk. There's one missing method (from ncwms ScalarLayer) in
> ThreddsScalarLayer and ThreddsVectorLayer:
>
> @Override
> public List<List<Float>> readVerticalSection(DateTime arg0,    Â
> List<Double> arg1, Domain<HorizontalPosition> arg2) throws
> InvalidDimensionValueException, IOException {}
>
> I don't have a good idea how to implement the missing logic. Maybe you
> can use the attached diff to prepare the next release or a preliminary
> patch. This is what I've done so far:
>
> - set up a combined project from the thredds and ncwms svn
> - copied Kyles new ncwms modifications from the git repository
> (https://github.com/asascience/THREDDS/commit/53987775e1bdd31b533f516383226ac3b77e5b58)
> - removed everything that had to do with "nearest time" (in favour of a
> first "clean" integration)
> - updated some method names in the thredds classes that had changed in
> the current ncwms
>
> Cheers,
> Matthias
>
> Am 04.05.2011 20:24, schrieb Jon Blower:
> > Matthias,
> >
> > Yes, the TDS trunk is a bit out of sync with the ncWMS trunk so you can't
> > easily port a custom ncWMS into TDS. (In theory, the "jar-for-THREDDS" ant
> > target in ncWMS will generate the right JAR file, but as it stands it won't
> > drop into THREDDS.)
> >
> > Kyle's work below might fix the issue, but Ethan and I need to work this up
> > properly for a near-future THREDDS release.
> >
> > Cheers, Jon
> >
> > -----Original Message-----
> > From: Kyle Wilcox [mailto:KWilcox@xxxxxxxxxxxxxx]
> > Sent: 04 May 2011 14:30
> > To: 'Matthias MÃller'
> > Cc: 'ncwms-users@xxxxxxxxxxxxxxxxxxxxx'
> > Subject: Re: [Ncwms-users] [thredds] Simple fix =>Â much smaller TDS WMS
> > GetCapabilities size (for model output)
> >
> > I don't know if anyone has tested this, but try this:
> >
> > https://github.com/asascience/ToolsUI
> >
> > These are NetBeans projects for TDS and ToolsUI. It includes the TRUNK
> > NetCDF-Java codebase as a git submodule (see README). The NetCDF-Java
> > codebase is here: https://github.com/asascience/THREDDS.Â; I update the
> > THREDDS codebase weekly to the trunk.
> >
> > There are issues with the maven and ant files in NetCDF-Java... some don't
> > generate the JARs in the correct places or with the correct version
> > numbers. You may have to play with this a bit.
> >
> > I also gave a small shot at getting NcWMS trunk into TDS trunk last
> > December:
> >
> > https://github.com/asascience/THREDDS/commit/53987775e1bdd31b533f516383226ac3b77e5b58
> >
> > If you get it working, please send me the patch or a pull request!
> >
> > ---------
> > Kyle Wilcox, Engineer
> > Applied Science Associates
> > 55 Village Square Drive
> > South Kingstown, RI 02879
> > p: (401) 789-6224
> > e: kwilcox@xxxxxxxxxxxxxx
> >
> >
> >> -----Original Message-----
> >> From: Matthias MÃller [mailto:Matthias_Mueller@xxxxxxxxxxxxx]
> >> Sent: Wednesday, May 04, 2011 8:30 AM
> >> To: ncwms-users@xxxxxxxxxxxxxxxxxxxxx
> >> Subject: Re: [Ncwms-users] [thredds] Simple fix =>Â much smaller TDS
> >> WMS GetCapabilities size (for model output)
> >>
> >> Hello Jon, all,
> >>
> >> this issue (see below) was discussed earlier his year on the thredds
> >> mailing list. Is there any documentation on how to set up a combined
> >> ncwms/thredds project (e.g. in eclipse or netbeans?) to incoporate the
> >> changes into a current thredds distribution? I've modified the old
> >> ncwms classes that were used for THREDDS 4.2 and re-integrated them,
> >> but some cross-dependencies keep raising errors at runtime.
> >>
> >> Thanks,
> >> Matthias
> >>
> >>
> >> Am 04.01.2011 16:28, schrieb Jon Blower:
> >>> Hi Kyle,
> >>>
> >>> Great that you've had a go at #1. I'll chew it over and merge it
> >> when I get a chance.
> >>>
> >>> Regarding the custom URL parameter - generally I agree that non-
> >> standard params are to be avoided if possible, but actually in the
> >> case of a Capabilities doc I don't think it's so bad. A GIS will
> >> often ask for the full Capabilities URL, in which case you can simply
> >> add the custom parameter to the URL that's given. If the GIS asks for
> >> the "base" URL, it can still work, i.e. you pass it:
> >>>
> >>> http://myserver.com/wms?CUSTOM_PARAM=foo
> >>>
> >>> instead of
> >>>
> >>> http://myserver.com/wms.
> >>>
> >>> ncWMS does this already, in fact, with the DATASET parameter (which
> >> isn't used in TDS-WMS).
> >>>
> >>> Cheers, Jon
> >>>
> >>> -----Original Message-----
> >>> From: Kyle Wilcox [mailto:KWilcox@xxxxxxxxxxxxxx]
> >>> Sent: 04 January 2011 15:10
> >>> To: 'Jon Blower'; thredds@xxxxxxxxxxxxxxxx
> >>> Subject: RE: [thredds] Simple fix =>Â Âmuch smaller TDS WMS
> >> GetCapabilities size (for model output)
> >>>
> >>> I'd prefer a configurable setting rather than a custom URL parameter.
> >> I try to avoid extending specifications if at all possible. No
> >> existing clients will know about the additional parameter, and some
> >> datasets won't benefit from start/stop/step. On the downside, the
> >> GetCap could list all of the times for some datasets, and use
> >> start/stop/step for some others. At least with a URL parameter, it
> >> would be consistent.
> >>>
> >>>
> >>>
> >>> I took a simple stab at #1 a few weeks ago. The rounding isn't
> >> triggered by a flag in the GetCapabilities request, it is instead
> >> enabled by using a checkbox in the admin panel on each dataset
> >>>
> >>> It finds the smallest interval between all timesteps in the dataset
> >> and then assumes that this interval is consistent throughout the
> >> dataset. It steps through all of the timesteps and if the interval
> >> between two adjacent timesteps is greater than the smallest found
> >> interval, it ends the current "start/stop/step" and starts another.
> >>>
> >>>>Â From what I've seen, it works, but I haven't tested it at all.
> >>>
> >>>
> >> https://github.com/asascience/ncWMS/commit/9e2925fc607a05d6484299e017d
> >> b
> >> 0180a2200fa4
> >>>
> >>>
> >>> ---------
> >>> Kyle Wilcox, Engineer
> >>> Applied Science Associates
> >>> 55 Village Square Drive
> >>> South Kingstown, RI 02879
> >>> p: (401) 789-6224
> >>> e: kwilcox@xxxxxxxxxxxxxx
> >>>
> >>> -----Original Message-----
> >>> From: thredds-bounces@xxxxxxxxxxxxxxxx [mailto:thredds-
> >> bounces@xxxxxxxxxxxxxxxx] On Behalf Of Jon Blower
> >>> Sent: Tuesday, January 04, 2011 5:54 AM
> >>> To: thredds@xxxxxxxxxxxxxxxx
> >>> Subject: Re: [thredds] Simple fix =>Â Âmuch smaller TDS WMS
> >> GetCapabilities size (for model output)
> >>>
> >>> Hi all,
> >>>
> >>> I can certainly see that there is a problem that needs to be
> >> addressed here (explicit lists of all individual TIME values cause the
> >> Capabilities document to blow up). There are actually two approaches
> >> to this, which could be used individually or in combination:
> >>>
> >>> 1) Use the syntax start/stop/period, potentially multiple times, to
> >> define the TIME values instead of listing them explicitly.
> >>>
> >>> 2) Use Layer inheritance properties to define the time dimension
> >>> once
> >> only, if the same time axis is shared by all the children of a parent
> >> layer:
> >>>
> >>> <Layer>
> >>>Â Â Â<Title>My Model Output</Title>
> >>>Â Â Â<!-- Non-displayable parent Layer -->
> >>>Â Â Â<Dimension name="time">Â Â... values ...</Dimension>
> >>>Â Â Â<Layer>
> >>>Â Â Â Â<Title>sea_water_temperature</Title>
> >>>Â Â Â Â<Name>TMP</Name>
> >>>Â Â Â Â<!-- Inherits time axis from parent -->
> >>>Â Â Â</Layer>
> >>>Â Â Â<!-- More child layers -->
> >>>Â Â Â<!-- Children can override the time axis if theirs is different
> >> for some reason -->Â Â</Layer>
> >>>
> >>> The most concise possible Capabilities doc would be achieved by
> >> combining both approaches.
> >>>
> >>> I feel that we should ensure that only those time values that are
> >> actually present should appear in the Capabilities doc - I think
> >> things get a bit confusing if the Capabilities doc advertises
> >> "missing" times (what would the returned image from a missing time
> >> look like?). I also agree with Bob Simons that the use of "nearest"
> >> values is dangerous, even though it's in the spec (sorry Kyle) - the
> >> client can always perform the nearest-neighbour calculation if this is
> >> required, given the server's advertised capabilities. (Feel free to
> >> disagree with me of course!)
> >>>
> >>> I can see two potential problems:
> >>>
> >>> 1. Solution 1 above is a bit tricky to implement in the general
> >>> case,
> >> avoiding the corner cases. (Solution 2 would actually be pretty easy
> >> to implement.)
> >>>
> >>> 2. As Ethan and Roy have pointed out, third-party client support for
> >> multidimensional WMS is, er, generally not great. It's hard enough to
> >> find a client that supports TIME at all, never mind all the possible
> >> syntaxes. I'm torn on this - in one respect it's not our problem, but
> >> we don't want to cut out portions of the user base.
> >>>
> >>> So, after all this, I propose a solution:
> >>>
> >>> 1. Implement one or both measures above, ensuring that the
> >> Capabilities document is accurate. This may involve being
> >> conservative. The default Capabilities doc would be much smaller .
> >>>
> >>> 2. Allow clients to specify a URL parameter to GetCapabilities that
> >> triggers the generation of a Capabilities document that *does* list
> >> all the time values explicitly, allowing compatibility with some GIS
> >> clients. (Clients usually require a URL to the Cap doc, which could
> >> include this non-standard URL parameter. Or the parameter could be
> >> considered part of the "base URL").
> >>>
> >>> Does anyone have any thoughts on this before I start an
> >> implementation? It's tempting to implement the "layer inheritance"
> >> solution first since it's easiest; I think this would be effective in
> >> TDS, where each Cap doc usually represents a single model run, which
> >> will usually have a single time axis, shared among all variables.
> >>>
> >>> Happy New Year!
> >>> Jon
> >>>
> >>> --
> >>> Dr Jon Blower
> >>> Technical Director, Reading e-Science Centre Environmental Systems
> >> Science Centre University of Reading, UK
> >>> Tel: +44 (0)118 378 5213
> >>> http://www.resc.reading.ac.uk
> >>>
> >>>
> >>> _______________________________________________
> >>> thredds mailing list
> >>> thredds@xxxxxxxxxxxxxxxx
> >>> For list information or to unsubscribe, visit:
> >> http://www.unidata.ucar.edu/mailing_lists/
> >>>
> >>> _______________________________________________
> >>> thredds mailing list
> >>> thredds@xxxxxxxxxxxxxxxx
> >>> For list information or to unsubscribe, visit:
> >> http://www.unidata.ucar.edu/mailing_lists/
> >>>
> >>
> >>
> >> --
> >> Matthias MÃller
> >> Dipl.-Geogr. | Research Associate
> >>
> >> Technische UniversitÃt Dresden
> >> Geoinformation Systems
> >> 01062 Dresden
> >>
> >> Phone: +49 351 463-31953
> >> Fax: +49 351 463-35879
> >> Mail: Matthias_Mueller@xxxxxxxxxxxxx
> >>
> >> www: http://tu-dresden.de/fgh/geo/gis
> >>
> >> ----------------------------------------------------------------------
> >> -
> >> -------
> >> WhatsUp Gold - Download Free Network Management Software The most
> >> intuitive, comprehensive, and cost-effective network management
> >> toolset available today. Delivers lowest initial acquisition cost and
> >> overall TCO of any competing solution.
> >> http://p.sf.net/sfu/whatsupgold-sd
> >> _______________________________________________
> >> Ncwms-users mailing list
> >> Ncwms-users@xxxxxxxxxxxxxxxxxxxxx
> >> https://lists.sourceforge.net/lists/listinfo/ncwms-users
> >
> > ------------------------------------------------------------------------------
> > WhatsUp Gold - Download Free Network Management Software The most intuitive,
> > comprehensive, and cost-effective network management toolset available
> > today. Delivers lowest initial acquisition cost and overall TCO of any
> > competing solution.
> > http://p.sf.net/sfu/whatsupgold-sd
> > _______________________________________________
> > Ncwms-users mailing list
> > Ncwms-users@xxxxxxxxxxxxxxxxxxxxx
> > https://lists.sourceforge.net/lists/listinfo/ncwms-users
> >
>
>
> --
> Matthias MÃller
> Dipl.-Geogr. | Research Associate
>
> Technische UniversitÃt Dresden
> Geoinformation Systems
> 01062 Dresden
>
> Phone: +49 351 463-31953
> Fax: +49 351 463-35879
> Mail: Matthias_Mueller@xxxxxxxxxxxxx
>
> www: http://tu-dresden.de/fgh/geo/gis
>
Index: java/uk/ac/rdg/resc/edal/cdm/CdmUtils.java
===================================================================
--- java/uk/ac/rdg/resc/edal/cdm/CdmUtils.java  (revisión: 919)
+++ java/uk/ac/rdg/resc/edal/cdm/CdmUtils.java  (copia de trabajo)
@@ -68,8 +68,10 @@
 import uk.ac.rdg.resc.edal.coverage.CoverageMetadata;
 import uk.ac.rdg.resc.edal.coverage.domain.Domain;
 import uk.ac.rdg.resc.edal.coverage.grid.HorizontalGrid;
+import uk.ac.rdg.resc.edal.coverage.grid.RectilinearGrid;
 import uk.ac.rdg.resc.edal.coverage.grid.ReferenceableAxis;
 import uk.ac.rdg.resc.edal.coverage.grid.RegularAxis;
+import uk.ac.rdg.resc.edal.coverage.grid.RegularGrid;
 import uk.ac.rdg.resc.edal.coverage.grid.impl.RectilinearGridImpl;
 import uk.ac.rdg.resc.edal.coverage.grid.impl.ReferenceableAxisImpl;
 import uk.ac.rdg.resc.edal.coverage.grid.impl.RegularAxisImpl;
@@ -444,6 +446,25 @@
         return data;
     }
     
+        
+    public static List<List<Float>> readVerticalSection(DataReadingStrategy 
drStrategy, GridDatatype grid, int tIndex, List<Integer> zIndices,
+            Domain<HorizontalPosition> targetDomain)
+            throws IOException
+    {
+        
+        //Create the source grid
+        HorizontalGrid sourceGrid = 
CdmUtils.createHorizontalGrid(grid.getCoordinateSystem());        
+        PixelMap pixelMap = new PixelMap(sourceGrid, targetDomain);
+
+        // Defend against null values
+        if (zIndices == null) zIndices = Arrays.asList(-1);
+        List<List<Float>> data = new ArrayList<List<Float>>(zIndices.size());
+        for (int zIndex : zIndices) {
+            data.add(readHorizontalPoints(drStrategy, grid, tIndex, zIndex, 
pixelMap, targetDomain.size()));
+        }
+        return data;
+    }    
+    
     /**
      * Reads a set of points at a given time and elevation from the given
      * GridDatatype.  Use this method if you already have a {@link 
HorizontalGrid}
@@ -470,7 +491,8 @@
             throws IOException
     {
         GridDatatype grid = getGridDatatype(nc, varId);
-        return readHorizontalPoints(nc, grid, sourceGrid, tIndex, zIndex, 
targetDomain);
+        DataReadingStrategy drStrategy =  getOptimumDataReadingStrategy(nc);
+        return readHorizontalPoints(drStrategy, grid, tIndex, zIndex, 
targetDomain);
     }
 
     /**
@@ -500,7 +522,7 @@
     {
         // Create the mapping between the requested points in the target domain
         // and the nearest cells in the source grid
-        long start = System.nanoTime();
+        /*long start = System.nanoTime();
         PixelMap pixelMap = new PixelMap(sourceGrid, targetDomain);
         long finish = System.nanoTime();
         logger.debug("Pixel map created in {} ms", (finish - start) / 1.e6);
@@ -512,27 +534,59 @@
             return nullList(targetDomain.size());
         }
 
-        return readHorizontalPoints(nc, grid, tIndex, zIndex, pixelMap, 
targetDomain.size());
+        return readHorizontalPoints(nc, grid, tIndex, zIndex, pixelMap, 
targetDomain.size());*/
+        DataReadingStrategy strategy = getOptimumDataReadingStrategy(nc);
+        return readHorizontalPoints(strategy, grid, tIndex, zIndex, 
targetDomain );
     }
+    
+    public static List<Float> readHorizontalPoints(DataReadingStrategy 
strategy, GridDatatype grid, int tIndex, int zIndex,
+            Domain<HorizontalPosition> targetDomain)
+            throws IOException
+    {
+        //Create the source grid
+        HorizontalGrid sourceGrid = 
CdmUtils.createHorizontalGrid(grid.getCoordinateSystem());
+        // Create the mapping between the requested points in the target domain
+        // and the nearest cells in the source grid
+        long start = System.nanoTime();
+        PixelMap pixelMap = new PixelMap(sourceGrid, targetDomain);
+        long finish = System.nanoTime();
+        logger.debug("Pixel map created in {} ms", (finish - start) / 1.e6);
+        
+        if (pixelMap.isEmpty())
+        {
+            // There is no overlap between the source data grid and the target
+            // domain.  Return a list of null values.
+            return nullList(targetDomain.size());
+        }
 
+        return readHorizontalPoints(strategy, grid, tIndex, zIndex, pixelMap, 
targetDomain.size());
+    }    
+
     static List<Float> readHorizontalPoints(NetcdfDataset nc, GridDatatype 
grid,
             int tIndex, int zIndex, PixelMap pixelMap, int targetDomainSize)
             throws IOException
+    {        
+        DataReadingStrategy strategy = getOptimumDataReadingStrategy(nc);
+        return readHorizontalPoints(strategy, grid, tIndex, zIndex, pixelMap, 
targetDomainSize);
+    }
+
+    static List<Float> readHorizontalPoints(DataReadingStrategy strategy, 
GridDatatype grid,
+            int tIndex, int zIndex, PixelMap pixelMap, int targetDomainSize)
+            throws IOException
     {
         // Create an array of the right size to hold the data
         float[] data = new float[targetDomainSize];
         Arrays.fill(data, Float.NaN); // Will be represented as nulls in the 
returned List
-
         // Now read the actual data
-        DataReadingStrategy strategy = getOptimumDataReadingStrategy(nc);
         long start = System.nanoTime();
         int bytesRead = strategy.readData(tIndex, zIndex, grid, pixelMap, 
data);
         long finish = System.nanoTime();
         logger.debug("{} bytes read in {} ms", bytesRead, (finish - start) / 
1.e6);
 
         // Wrap the data array as an immutable list and return
-        return wrap(data);
+        return wrap(data);        
     }
+    
 
     /**
      * Returns an immutable List of the given size in which all values are 
null.
Index: java/uk/ac/rdg/resc/ncwms/graphics/ImageProducer.java
===================================================================
--- java/uk/ac/rdg/resc/ncwms/graphics/ImageProducer.java       (revisión: 919)
+++ java/uk/ac/rdg/resc/ncwms/graphics/ImageProducer.java       (copia de 
trabajo)
@@ -31,7 +31,9 @@
 import java.awt.BasicStroke;
 import java.awt.Color;
 import java.awt.Graphics2D;
+import java.awt.geom.Path2D;
 import java.awt.image.BufferedImage;
+import java.awt.RenderingHints;
 import java.awt.image.ColorModel;
 import java.awt.image.DataBuffer;
 import java.awt.image.DataBufferByte;
@@ -39,6 +41,7 @@
 import java.awt.image.Raster;
 import java.awt.image.SampleModel;
 import java.awt.image.WritableRaster;
+import java.util.Arrays;
 import java.util.ArrayList;
 import java.util.List;
 import org.slf4j.Logger;
@@ -58,7 +61,7 @@
 {
     private static final Logger logger = 
LoggerFactory.getLogger(ImageProducer.class);
 
-    public static enum Style {BOXFILL, VECTOR};
+    public static enum Style {BOXFILL, VECTOR, BARB, STUMPVEC, TRIVEC, 
LINEVEC, FANCYVEC};
     
     private Style style;
     // Width and height of the resulting picture
@@ -77,11 +80,13 @@
      * means that the picture will be auto-scaled.
      */
     private Range<Float> scaleRange;
-    
+
     /**
-     * The length of arrows in pixels, only used for vector plots
+     * The scale factor between vectors
      */
-    private float arrowLength = 10.0f;
+    private float vectorScale;
+    private float arrowLength = 14.0f;
+    private float barbLength = 28.0f;
     
     // set of rendered images, ready to be turned into a picture
     private List<BufferedImage> renderedFrames = new 
ArrayList<BufferedImage>();
@@ -173,28 +178,12 @@
         return this.colorPalette.getColorModel(this.numColourBands,
             this.opacity, this.bgColor, this.transparent);
     }
-    
-    /**
-     * Creates and returns a single frame as an Image, based on the given data.
-     * Adds the label if one has been set.  The scale must be set before
-     * calling this method.
-     */
-    private BufferedImage createImage(Components comps, String label)
-    {
-        // Create the pixel array for the frame
+
+    // Create the pixel array for the frame
+    private BufferedImage createVector(Components comps, String label) {
+
         byte[] pixels = new byte[this.picWidth * this.picHeight];
-        // We get the magnitude of the input data (takes care of the case
-        // in which the data are two components of a vector)
-        List<Float> magnitudes = comps.getMagnitudes();
-        for (int i = 0; i < pixels.length; i++)
-        {
-            // The image coordinate system has the vertical axis increasing
-            // downward, but the data's coordinate system has the vertical axis
-            // increasing upwards.  The method below flips the axis
-            int dataIndex = this.getDataIndex(i);
-            pixels[i] = (byte)this.getColourIndex(magnitudes.get(dataIndex));
-        }
-        
+        Arrays.fill(pixels, (byte)this.numColourBands);
         // Create a ColorModel for the image
         ColorModel colorModel = this.getColorModel();
         
@@ -203,7 +192,7 @@
         SampleModel sampleModel = 
colorModel.createCompatibleSampleModel(this.picWidth, this.picHeight);
         WritableRaster raster = Raster.createWritableRaster(sampleModel, buf, 
null);
         BufferedImage image = new BufferedImage(colorModel, raster, false, 
null);
-        
+
         // Add the label to the image
         // TODO: colour needs to change with different palettes!
         if (label != null && !label.equals(""))
@@ -214,49 +203,72 @@
             gfx.setPaint(new Color(255, 151, 0));
             gfx.drawString(label, 10, image.getHeight() - 5);
         }
-        
-        if (this.style == Style.VECTOR)
+
+        Graphics2D g = image.createGraphics();
+        
g.setRenderingHint(RenderingHints.KEY_ANTIALIASING,RenderingHints.VALUE_ANTIALIAS_OFF);
+        g.setColor(new Color(0,0,0,100));
+
+        //logger.debug("Drawing vectors, length = {} pixels", 
this.arrowLength);
+        //List<Float> east = data.get(0);
+        //List<Float> north = data.get(1);
+
+        float stepScale = 1.1f;
+        float imageLength = this.arrowLength;
+
+        if (this.style == Style.BARB) {
+            imageLength = this.barbLength * this.vectorScale;
+            stepScale = 1.2f * this.vectorScale;
+         } else {
+            imageLength = this.arrowLength * this.vectorScale;
+            stepScale = 1.1f * this.vectorScale;
+         }
+
+        int index;
+        int dataIndex;
+        double angle;
+        double radangle;
+        Double mag;
+        Float eastVal;
+        Float northVal;
+        Path2D drawing;
+
+        for (int i = 0; i < this.picWidth; i += Math.ceil(imageLength + 
stepScale))
         {
-            // We superimpose direction arrows on top of the background
-            // TODO: only do this for lat-lon projections!
-            Graphics2D g = image.createGraphics();
-            // TODO: control the colour of the arrows with an attribute
-            // Must be part of the colour palette (here we use the colour
-            // for out-of-range values)
-            g.setColor(Color.BLACK);
-
-            logger.debug("Drawing vectors, length = {} pixels", 
this.arrowLength);
-            for (int i = 0; i < this.picWidth; i += Math.ceil(this.arrowLength 
* 1.2))
+            for (int j = 0; j < this.picHeight; j += Math.ceil(imageLength + 
stepScale))
             {
-                for (int j = 0; j < this.picHeight; j += 
Math.ceil(this.arrowLength * 1.2))
+                dataIndex = this.getDataIndex(i, j);
+                eastVal = comps.x.get(dataIndex);
+                northVal = comps.y.get(dataIndex);
+                if (eastVal != null && northVal != null)
                 {
-                    int dataIndex = this.getDataIndex(i, j);
-                    Float eastVal = comps.x.get(dataIndex);
-                    Float northVal = comps.y.get(dataIndex);
-                    if (eastVal != null && northVal != null)
-                    {
-                        double angle = Math.atan2(northVal.doubleValue(), 
eastVal.doubleValue());
-                        // Calculate the end point of the arrow
-                        double iEnd = i + this.arrowLength * Math.cos(angle);
-                        // Screen coordinates go down, but north is up, hence 
the minus sign
-                        double jEnd = j - this.arrowLength * Math.sin(angle);
-                        //logger.debug("i={}, j={}, dataIndex={}, east={}, 
north={}",
-                        //    new Object[]{i, j, dataIndex, 
data[0][dataIndex], data[1][dataIndex]});
-                        // Draw a dot representing the data location
-                        g.fillOval(i - 2, j - 2, 4, 4);
-                        // Draw a line representing the vector direction and 
magnitude
-                        g.setStroke(new BasicStroke(1));
-                        g.drawLine(i, j, (int)Math.round(iEnd), 
(int)Math.round(jEnd));
-                        // Draw the arrow on the canvas
-                        //drawArrow(g, i, j, (int)Math.round(iEnd), 
(int)Math.round(jEnd), 2);
+                    angle = Math.toDegrees(Math.atan2(eastVal.doubleValue(), 
northVal.doubleValue()));
+                    angle = (eastVal.doubleValue() < 0) ? angle + 360 : angle;
+                    radangle = Math.toRadians(angle);
+                    mag = Math.sqrt(Math.pow(northVal.doubleValue(), 2) + 
Math.pow(eastVal.doubleValue() , 2));
+
+                    // Color arrow
+                    index = this.getColourIndex(mag.floatValue());
+                    g.setColor(new Color(colorModel.getRGB(index)));
+                    if (this.style == Style.BARB) {
+                      // I used to reference this.layer.getUnits(), but 
this.layer is
+                      // no longer available.  How to get units here?
+                       g.setStroke(new BasicStroke(1));
+                      //drawing = BarbFactory.getWindBarbForSpeed(mag, 
radangle, i, j, "m/s", this.vectorScale);                        
+                      BarbFactory.drawWindBarbForSpeed(mag, radangle, i, j, 
"m/s", this.vectorScale, g);                                              
+                      //g.draw(drawing);
+                    } else {
+                      // Arrows.  We need to pick the style arrow now
+                      drawing = VectorFactory.getVector(this.style.name(), 
mag, radangle, i, j, this.vectorScale);
+                      if (this.style != Style.LINEVEC) {
+                        g.fill(drawing);
+                      }
+                      g.draw(drawing);
                     }
                 }
             }
         }
-        
         return image;
     }
-
     /**
      * Calculates the index of the data point in a data array that corresponds
      * with the given index in the image array, taking into account that the
@@ -280,6 +292,94 @@
         int dataIndex = dataJ * this.picWidth + imageI;
         return dataIndex;
     }
+
+    /**
+    * Creates and returns a single frame as an Image, based on the given data.
+    * Adds the label if one has been set. The scale must be set before
+    * calling this method.
+    */
+    private BufferedImage createImage(Components comps, String label)
+    {
+        if (this.style == Style.FANCYVEC || this.style == Style.TRIVEC || 
this.style == Style.BARB || this.style == Style.STUMPVEC || this.style == 
Style.LINEVEC) {
+            return this.createVector(comps, label);
+        } else {
+            // Create the pixel array for the frame
+            byte[] pixels = new byte[this.picWidth * this.picHeight];
+            // We get the magnitude of the input data (takes care of the case
+            // in which the data are two components of a vector)
+            List<Float> magnitudes = comps.getMagnitudes();
+            for (int i = 0; i < pixels.length; i++)
+            {
+                // The image coordinate system has the vertical axis increasing
+                // downward, but the data's coordinate system has the vertical 
axis
+                // increasing upwards. The method below flips the axis
+                int dataIndex = this.getDataIndex(i);
+                pixels[i] = 
(byte)this.getColourIndex(magnitudes.get(dataIndex));
+            }
+
+            // Create a ColorModel for the image
+            ColorModel colorModel = this.getColorModel();
+
+
+            // Create the Image
+            DataBuffer buf = new DataBufferByte(pixels, pixels.length);
+            SampleModel sampleModel = 
colorModel.createCompatibleSampleModel(this.picWidth, this.picHeight);
+            WritableRaster raster = Raster.createWritableRaster(sampleModel, 
buf, null);
+            BufferedImage image = new BufferedImage(colorModel, raster, false, 
null);
+
+            // Add the label to the image
+            // TODO: colour needs to change with different palettes!
+            if (label != null && !label.equals(""))
+            {
+                Graphics2D gfx = (Graphics2D)image.getGraphics();
+                gfx.setPaint(new Color(0, 0, 143));
+                gfx.fillRect(1, image.getHeight() - 19, image.getWidth() - 1, 
18);
+                gfx.setPaint(new Color(255, 151, 0));
+                gfx.drawString(label, 10, image.getHeight() - 5);
+            }
+
+            if (this.style == Style.VECTOR)
+            {
+                // We superimpose direction arrows on top of the background
+                // TODO: only do this for lat-lon projections!
+                Graphics2D g = image.createGraphics();
+                // TODO: control the colour of the arrows with an attribute
+                // Must be part of the colour palette (here we use the colour
+                // for out-of-range values)
+                g.setColor(Color.BLACK);
+
+                logger.debug("Drawing vectors, length = {} pixels", 
this.arrowLength);
+                for (int i = 0; i < this.picWidth; i += 
Math.ceil(this.arrowLength * 1.2))
+                {
+                    for (int j = 0; j < this.picHeight; j += 
Math.ceil(this.arrowLength * 1.2))
+                    {
+                        int dataIndex = this.getDataIndex(i, j);
+                        Float eastVal = comps.x.get(dataIndex);
+                        Float northVal = comps.y.get(dataIndex);
+                        if (eastVal != null && northVal != null)
+                        {
+                            double angle = Math.atan2(northVal.doubleValue(), 
eastVal.doubleValue());
+                            // Calculate the end point of the arrow
+                            double iEnd = i + this.arrowLength * 
Math.cos(angle);
+                            // Screen coordinates go down, but north is up, 
hence the minus sign
+                            double jEnd = j - this.arrowLength * 
Math.sin(angle);
+                            //logger.debug("i={}, j={}, dataIndex={}, east={}, 
north={}",
+                            // new Object[]{i, j, dataIndex, 
data[0][dataIndex], data[1][dataIndex]});
+                            // Draw a dot representing the data location
+                            g.fillOval(i - 2, j - 2, 4, 4);
+                            // Draw a line representing the vector direction 
and magnitude
+                            g.setStroke(new BasicStroke(1));
+                            g.drawLine(i, j, (int)Math.round(iEnd), 
(int)Math.round(jEnd));
+                            // Draw the arrow on the canvas
+                            //drawArrow(g, i, j, (int)Math.round(iEnd), 
(int)Math.round(jEnd), 2);
+                        }
+                    }
+                }
+            }
+
+            return image;
+        }
+    }
     
     /**
      * @return the colour index that corresponds to the given value
@@ -387,6 +487,7 @@
         private int picHeight = -1;
         private boolean transparent = false;
         private int opacity = 100;
+        private float vectorScale = 1;
         private int numColourBands = ColorPalette.MAX_NUM_COLOURS;
         private Boolean logarithmic = null;
         private Color bgColor = Color.WHITE;
@@ -479,6 +580,13 @@
             return this;
         }
 
+        /** Sets the width of the picture (must be set: there is no default) */
+        public Builder vectorScale(float scale) {
+            if (scale <= 0) throw new IllegalArgumentException();
+            this.vectorScale = scale;
+            return this;
+        }
+
         /**
          * Checks the fields for internal consistency, then creates and returns
          * a new ImageProducer object.
@@ -512,7 +620,7 @@
             ip.scaleRange = this.scaleRange == null
                 ? emptyRange
                 : this.scaleRange;
-
+            ip.vectorScale = this.vectorScale;
             return ip;
         }
     }
Index: java/uk/ac/rdg/resc/ncwms/graphics/VectorFactory.java
===================================================================
--- java/uk/ac/rdg/resc/ncwms/graphics/VectorFactory.java       (revisión: 0)
+++ java/uk/ac/rdg/resc/ncwms/graphics/VectorFactory.java       (revisión: 0)
@@ -0,0 +1,112 @@
+/*
+ * Applied Science Associates, Inc.
+ * Copyright 2009. All Rights Reserved.
+ *
+ * VectorFactory.java
+ *
+ * Created on Apr 6, 2010 @ 11:12:12 AM
+ */
+
+package uk.ac.rdg.resc.ncwms.graphics;
+
+import java.util.List;
+import java.util.ArrayList;
+import java.awt.geom.Path2D;
+import java.awt.geom.AffineTransform;
+
+/**
+ *
+ * @author CBM <cmueller@xxxxxxxxxxxxxx>
+ * @adapted Kyle Wilcox <kwilcox@xxxxxxxxxxxxxx>
+ */
+public class VectorFactory {
+    private static List<Path2D> vectors;
+
+    static {
+        vectors = new ArrayList<Path2D>();
+        vectors.add(stumpyVector());
+        vectors.add(triangleVector());
+        vectors.add(lineVector());
+        vectors.add(fancyVector());
+    }
+
+    public VectorFactory() {
+
+    }
+
+    public static Path2D getVector(String style, double speed, double angle, 
int i, int j, float scale) {
+
+        int type = 0;
+        if (style.equalsIgnoreCase("STUMPVEC")) {
+            type = 0;
+        } else if (style.equalsIgnoreCase("TRIVEC")) {
+            type = 1;
+        } else if (style.equalsIgnoreCase("LINEVEC")) {
+            type = 2;
+        } else if (style.equalsIgnoreCase("FANCYVEC")) {
+            type = 3;
+        }
+
+        Path2D ret = (Path2D)vectors.get(type).clone();
+        /* Rotate and set position */
+        ret.transform(AffineTransform.getRotateInstance(angle));
+        ret.transform(AffineTransform.getScaleInstance(scale, scale));
+        ret.transform(AffineTransform.getTranslateInstance(i, j));
+        return ret;
+    }
+
+    private static Path2D stumpyVector() {
+        Path2D path = new Path2D.Double();
+        path.moveTo(0,1);
+        path.lineTo(0,-1);
+        path.lineTo(4,-1);
+        path.lineTo(4,-4);
+        path.lineTo(10,0);
+        path.lineTo(4,4);
+        path.lineTo(4,1);
+        path.closePath();
+        //path.transform(AffineTransform.getRotateInstance(-90));
+        path.transform(AffineTransform.getRotateInstance(-Math.PI / 2 ));
+        return path;
+    }
+
+    private static Path2D triangleVector() {
+        Path2D path = new Path2D.Double();
+        path.moveTo(0,4);
+        path.lineTo(0,-4);
+        path.lineTo(10,0);
+        path.closePath();
+        //path.transform(AffineTransform.getRotateInstance(-90));
+        path.transform(AffineTransform.getRotateInstance(-Math.PI / 2 ));
+        return path;
+    }
+
+    private static Path2D lineVector() {
+        Path2D path = new Path2D.Double();
+        path.moveTo(0,0);
+        path.lineTo(10,0);
+        path.moveTo(10,0);
+        path.lineTo(6,3);
+        path.moveTo(10,0);
+        path.lineTo(6,-3);
+        path.closePath();
+        //path.transform(AffineTransform.getRotateInstance(-90));
+        path.transform(AffineTransform.getRotateInstance(-Math.PI / 2 ));
+        return path;
+    }
+
+    private static Path2D fancyVector() {
+        Path2D path = new Path2D.Double();
+        path.moveTo(0,0);
+        path.lineTo(0,-3);
+        path.lineTo(5,-2);
+        path.lineTo(3,-5);
+        path.lineTo(11,-1.5);
+        path.lineTo(3,2);
+        path.lineTo(5,-1);
+        path.closePath();
+        //path.transform(AffineTransform.getRotateInstance(-90));
+        path.transform(AffineTransform.getRotateInstance(-Math.PI / 2 ));
+        return path;
+    }
+}
Index: java/uk/ac/rdg/resc/ncwms/graphics/BarbFactory.java
===================================================================
--- java/uk/ac/rdg/resc/ncwms/graphics/BarbFactory.java (revisión: 0)
+++ java/uk/ac/rdg/resc/ncwms/graphics/BarbFactory.java (revisión: 0)
@@ -0,0 +1,534 @@
+/*
+ * Applied Science Associates, Inc.
+ * Copyright 2009. All Rights Reserved.
+ *
+ * BarbFactory.java
+ *
+ * Created on Apr 6, 2010 @ 11:12:12 AM
+ */
+
+package uk.ac.rdg.resc.ncwms.graphics;
+
+import java.awt.Graphics2D;
+import java.util.List;
+import java.util.ArrayList;
+import java.awt.geom.Path2D;
+import java.awt.geom.AffineTransform;
+import java.awt.geom.PathIterator;
+import java.util.Arrays;
+
+/**
+ *
+ * @author CBM <cmueller@xxxxxxxxxxxxxx>
+ * @adapted Kyle Wilcox <kwilcox@xxxxxxxxxxxxxx>
+ */
+public class BarbFactory {
+    private static List<Path2D> windBarbs;
+
+    static {
+        windBarbs = new ArrayList<Path2D>();
+        windBarbs.add(barb_0_4());
+        windBarbs.add(barb_5_9());
+        windBarbs.add(barb_10_14());
+        windBarbs.add(barb_15_19());
+        windBarbs.add(barb_20_24());
+        windBarbs.add(barb_25_29());
+        windBarbs.add(barb_30_34());
+        windBarbs.add(barb_35_39());
+        windBarbs.add(barb_40_44());
+        windBarbs.add(barb_45_49());
+        windBarbs.add(barb_50_54());
+        windBarbs.add(barb_55_59());
+        windBarbs.add(barb_60_64());
+        windBarbs.add(barb_65_69());
+        windBarbs.add(barb_70_74());
+        windBarbs.add(barb_75_79());
+        windBarbs.add(barb_80_84());
+        windBarbs.add(barb_85_89());
+        windBarbs.add(barb_90_94());
+        windBarbs.add(barb_95_99());
+        windBarbs.add(barb_100());
+       }
+
+    public BarbFactory() {
+
+    }
+    
+    public static void drawWindBarbForSpeed(double speed, double angle, int i, 
int j, String units, float scale, Graphics2D g){
+       
+        /* Convert to knots */
+        if (units.equalsIgnoreCase("m/s")) {
+            speed = speed * 1.94384449;
+        } else if (units.equalsIgnoreCase("cm/s")) {
+           speed = speed * 0.0194384449;        
+        }
+        int knots = (Double.valueOf(speed)).intValue();
+        int pennants = knots / 50;
+        int fullLines = (knots - ( pennants* 50 )) / 10; 
+        int halfLines = (knots - ( pennants* 50 ) - (fullLines *10) ) / 5; 
+        
+        int barbLength = 18;
+        int fullLineLength = 10;
+        int halfLineLength = fullLineLength / 2;
+        int basePennantLength = 4;
+        int distanceBetweenItems = 3;
+        int currentPosOnBaseBarb = 0;
+        int hasPennants =0;
+        //Base barb 
+        //Graphics2D g2 = (Graphics2D) g;
+        Path2D path = new Path2D.Double();
+        path.moveTo(0,0);
+        //path.quadTo(-2,2,-4,0);
+        //path.quadTo(-2,-2,0,0);                
+        path.lineTo(barbLength,0);
+        //Drawing pennants...        
+        if(pennants > 0){
+            int contPennants =0;
+            hasPennants =1;
+            while(contPennants < pennants ){
+                currentPosOnBaseBarb = barbLength -( contPennants* 
basePennantLength );
+                path.moveTo( currentPosOnBaseBarb, 0);
+                path.lineTo(currentPosOnBaseBarb - (basePennantLength/2) , 
fullLineLength );
+                path.lineTo(currentPosOnBaseBarb - basePennantLength, 0);
+                path.closePath();            
+                currentPosOnBaseBarb = 0;
+                contPennants++;
+            }
+        }
+        //Drawing full lines...
+        if( fullLines > 0 ){
+            int contFullLines =0;
+            currentPosOnBaseBarb = barbLength - ( pennants * basePennantLength 
)- distanceBetweenItems*hasPennants;
+            while(contFullLines < fullLines){
+                path.moveTo( currentPosOnBaseBarb, 0);
+                path.lineTo(currentPosOnBaseBarb + basePennantLength/2, 
fullLineLength );
+                contFullLines++;
+                currentPosOnBaseBarb-=distanceBetweenItems;
+            }
+        }
+        
+        //half line...
+        if( halfLines > 0 ){
+             if(pennants == 0 && fullLines == 0){
+                currentPosOnBaseBarb = barbLength - 5;
+                path.moveTo( currentPosOnBaseBarb, 0);
+                path.lineTo(currentPosOnBaseBarb + basePennantLength/4, 
halfLineLength );                 
+             }else{
+                currentPosOnBaseBarb = barbLength - ( pennants * 
basePennantLength )- distanceBetweenItems*(fullLines+hasPennants); 
+                path.moveTo( currentPosOnBaseBarb, 0);
+                path.lineTo(currentPosOnBaseBarb + basePennantLength/4, 
halfLineLength );                                 
+             }
+        }
+        
+        path.transform(AffineTransform.getRotateInstance( -Math.PI / 2  ));
+        //path.transform(AffineTransform.getRotateInstance( -90 ));
+        path.transform(AffineTransform.getRotateInstance( angle  ));
+        path.transform(AffineTransform.getScaleInstance(scale, scale));
+        path.transform(AffineTransform.getTranslateInstance(i, j));          
+        g.draw(path);
+         
+
+        //Iterating the transformed path to get the right coordinates for the 
polygons
+        PathIterator pi = path.getPathIterator(null);
+        double[] coordinates = new double[6];         
+        //double arrays for polygon points coordinates (PathIterator works 
with double)
+        double[] xcoords = new double[4];
+        double[] ycoords = new double[4];
+        //int arrays for polygon points coordinates -> fill method works with 
int
+        int [] iXcoords = new int[4]; 
+        int [] iYcoords = new int[4];
+        int prevType = pi.currentSegment(coordinates);
+         g.fillOval( Double.valueOf(coordinates[0] ).intValue()-2 , 
Double.valueOf(coordinates[1] ).intValue()-2, 4, 4 );
+        //Filling the pennants
+        if(hasPennants == 1 ){        
+            
+            double[] prevCoordinates = Arrays.copyOf(coordinates, 
coordinates.length);
+            int type =prevType;
+            int cont =0;
+            boolean isPolygon = false;
+            while (pi.isDone() == false) {
+                
+                if(isPolygon){
+                    xcoords[cont] = coordinates[0];
+                    ycoords[cont] = coordinates[1];
+                    cont++;
+                }
+                prevType = type;
+                prevCoordinates = Arrays.copyOf(coordinates, 
coordinates.length );
+                pi.next();
+                type = pi.currentSegment(coordinates);            
+                if(prevType == PathIterator.SEG_MOVETO &&  type == 
PathIterator.SEG_LINETO ){
+                    isPolygon = true;
+                    cont = 0;
+                    xcoords[cont] = prevCoordinates[0];
+                    ycoords[cont] = prevCoordinates[1];
+                    cont++;               
+                }
+                if(type == PathIterator.SEG_CLOSE){ 
+                    isPolygon = false;
+                    cont = 0;          
+                    //copy doubles into ints
+                    for(int k =0; k < 3; k++ ){
+                        iXcoords[k] = Double.valueOf( xcoords[k]).intValue();
+                        iYcoords[k] = Double.valueOf( ycoords[k]).intValue();
+                    }         
+                    iXcoords[ iXcoords.length - 1] = iXcoords[ 0 ];
+                    iYcoords[ iYcoords.length - 1 ] = iYcoords[ 0 ];         
+                    g.fillPolygon( iXcoords, iYcoords, 4);
+                }
+            }    
+          }
+        }    
+
+    public static Path2D getWindBarbForSpeed(double speed, double angle, int 
i, int j, String units, float scale) {
+        /* Convert to knots */
+        if (units.equalsIgnoreCase("m/s")) {
+            speed = speed * 1.94384449;
+        } else if (units.equalsIgnoreCase("cm/s")) {
+           speed = speed * 0.0194384449;
+        }
+
+        /* Get index into windBarbs array */
+        int rank = (int)(speed / 5) + 1;
+        if(rank < 0) {
+            rank = 0;
+        } else if (rank >= windBarbs.size()) {
+            rank = windBarbs.size() - 1;
+        }
+
+        Path2D ret = (Path2D)windBarbs.get(rank).clone();
+        /* Rotate and set position */
+        ret.transform(AffineTransform.getRotateInstance(angle));
+        ret.transform(AffineTransform.getScaleInstance(scale, scale));
+        ret.transform(AffineTransform.getTranslateInstance(i, j));
+        return ret;
+    }
+
+    private static Path2D barb_0_4() {
+        Path2D path = new Path2D.Double();
+        path.moveTo(0,0);
+        path.quadTo(-2,2,-4,0);
+        path.quadTo(-2,-2,0,0);
+        path.lineTo(18,0);
+        //path.transform(AffineTransform.getRotateInstance(-90));
+        path.transform(AffineTransform.getRotateInstance( -Math.PI / 2  ));
+        return path;
+    }
+    private static Path2D barb_5_9() {
+        Path2D path = new Path2D.Double();
+        path.moveTo(0,0);
+        path.quadTo(-2,2,-4,0);
+        path.quadTo(-2,-2,0,0);
+        path.lineTo(18,0);
+        path.moveTo(15,0);
+        path.lineTo(17,-8);
+        //path.transform(AffineTransform.getRotateInstance(-90));
+        path.transform(AffineTransform.getRotateInstance( -Math.PI / 2  ));
+        return path;
+    }
+    private static Path2D barb_10_14() {
+        Path2D path = new Path2D.Double();
+        path.moveTo(0,0);
+        path.quadTo(-2,2,-4,0);
+        path.quadTo(-2,-2,0,0);
+        path.lineTo(18,0);
+        path.lineTo(22,-16);
+        //path.transform(AffineTransform.getRotateInstance(-90));
+        path.transform(AffineTransform.getRotateInstance( -Math.PI / 2  ));
+        return path;
+    }
+    private static Path2D barb_15_19() {
+        Path2D path = new Path2D.Double();
+        path.moveTo(0,0);
+        path.quadTo(-2,2,-4,0);
+        path.quadTo(-2,-2,0,0);
+        path.lineTo(18,0);
+        path.lineTo(22,-16);
+        path.moveTo(15,0);
+        path.lineTo(17,-8);
+        //path.transform(AffineTransform.getRotateInstance(-90));
+        path.transform(AffineTransform.getRotateInstance( -Math.PI / 2  ));
+        return path;
+    }
+    private static Path2D barb_20_24() {
+        Path2D path = new Path2D.Double();
+        path.moveTo(0,0);
+        path.quadTo(-2,2,-4,0);
+        path.quadTo(-2,-2,0,0);
+        path.lineTo(18,0);
+        path.lineTo(22,-16);
+        path.moveTo(15,0);
+        path.lineTo(19,-16);
+        //path.transform(AffineTransform.getRotateInstance(-90));
+        path.transform(AffineTransform.getRotateInstance( -Math.PI / 2  ));
+        return path;
+    }
+
+    /* CONTINUE */
+    private static Path2D barb_25_29() {
+        Path2D path = new Path2D.Double();
+        path.moveTo(0,0);
+        path.quadTo(-2,2,-4,0);
+        path.quadTo(-2,-2,0,0);
+        path.lineTo(18,0);
+        path.lineTo(22,-16);
+        path.moveTo(15,0);
+        path.lineTo(19,-16);
+        path.moveTo(12,0);
+        path.lineTo(14,-8);
+        //path.transform(AffineTransform.getRotateInstance(-90));
+        path.transform(AffineTransform.getRotateInstance( -Math.PI / 2  ));
+        return path;
+    }
+    private static Path2D barb_30_34() {
+        Path2D path = new Path2D.Double();
+        path.moveTo(0,0);
+        path.quadTo(-2,2,-4,0);
+        path.quadTo(-2,-2,0,0);
+        path.lineTo(18,0);
+        path.lineTo(22,-16);
+        path.moveTo(15,0);
+        path.lineTo(19,-16);
+        path.moveTo(12,0);
+        path.lineTo(16,-16);
+        //path.transform(AffineTransform.getRotatenstance(-90));
+        path.transform(AffineTransform.getRotateInstance( -Math.PI / 2  ));
+        return path;
+    }
+    private static Path2D barb_35_39() {
+        Path2D path = new Path2D.Double();
+        path.moveTo(0,0);
+        path.quadTo(-2,2,-4,0);
+        path.quadTo(-2,-2,0,0);
+        path.lineTo(18,0);
+        path.lineTo(22,-16);
+        path.moveTo(15,0);
+        path.lineTo(19,-16);
+        path.moveTo(12,0);
+        path.lineTo(16,-16);
+        path.moveTo(9,0);
+        path.lineTo(11,-8);
+        //path.transform(AffineTransform.getRotateInstance(-90));
+        path.transform(AffineTransform.getRotateInstance( -Math.PI / 2  ));
+        return path;
+    }
+    private static Path2D barb_40_44() {
+        Path2D path = new Path2D.Double();
+        path.moveTo(0,0);
+        path.quadTo(-2,2,-4,0);
+        path.quadTo(-2,-2,0,0);
+        path.lineTo(18,0);
+        path.lineTo(22,-16);
+        path.moveTo(15,0);
+        path.lineTo(19,-16);
+        path.moveTo(12,0);
+        path.lineTo(16,-16);
+        path.moveTo(9,0);
+        path.lineTo(13,-16);
+        //path.transform(AffineTransform.getRotateInstance(-90));
+        path.transform(AffineTransform.getRotateInstance( -Math.PI / 2  ));
+        return path;
+    }
+    private static Path2D barb_45_49() {
+        Path2D path = new Path2D.Double();
+        path.moveTo(0,0);
+        path.quadTo(-2,2,-4,0);
+        path.quadTo(-2,-2,0,0);
+        path.lineTo(18,0);
+        path.lineTo(22,-16);
+        path.moveTo(15,0);
+        path.lineTo(19,-16);
+        path.moveTo(12,0);
+        path.lineTo(16,-16);
+        path.moveTo(9,0);
+        path.lineTo(13,-16);
+        path.moveTo(6,0);
+        path.lineTo(8,-8);
+        //path.transform(AffineTransform.getRotateInstance(-90));
+        path.transform(AffineTransform.getRotateInstance( -Math.PI / 2  ));
+        return path;
+    }
+    private static Path2D barb_50_54() {
+        Path2D path = new Path2D.Double();
+        path.moveTo(0,0);
+        path.quadTo(-2,2,-4,0);
+        path.quadTo(-2,-2,0,0);
+        path.lineTo(22,0);
+        path.lineTo(22,-16);
+        path.lineTo(18,0);
+        //path.transform(AffineTransform.getRotateInstance(-90));
+        path.transform(AffineTransform.getRotateInstance( -Math.PI / 2  ));
+        return path;
+    }
+    private static Path2D barb_55_59() {
+        Path2D path = new Path2D.Double();
+        path.moveTo(0,0);
+        path.quadTo(-2,2,-4,0);
+        path.quadTo(-2,-2,0,0);
+        path.lineTo(22,0);
+        path.lineTo(22,-16);
+        path.lineTo(18,0);
+        path.moveTo(15,0);
+        path.lineTo(17,-8);
+        //path.transform(AffineTransform.getRotateInstance(-90));
+        path.transform(AffineTransform.getRotateInstance( -Math.PI / 2  ));
+        return path;
+    }
+    private static Path2D barb_60_64() {
+        Path2D path = new Path2D.Double();
+        path.moveTo(0,0);
+        path.quadTo(-2,2,-4,0);
+        path.quadTo(-2,-2,0,0);
+        path.lineTo(22,0);
+        path.lineTo(22,-16);
+        path.lineTo(18,0);
+        path.moveTo(15,0);
+        path.lineTo(19,-16);
+        //path.transform(AffineTransform.getRotateInstance(-90));
+        path.transform(AffineTransform.getRotateInstance( -Math.PI / 2  ));
+        return path;
+    }
+    private static Path2D barb_65_69() {
+        Path2D path = new Path2D.Double();
+        path.moveTo(0,0);
+        path.quadTo(-2,2,-4,0);
+        path.quadTo(-2,-2,0,0);
+        path.lineTo(22,0);
+        path.lineTo(22,-16);
+        path.lineTo(18,0);
+        path.moveTo(15,0);
+        path.lineTo(19,-16);
+        path.moveTo(12,0);
+        path.lineTo(14,-8);
+        //path.transform(AffineTransform.getRotateInstance(-90));
+        path.transform(AffineTransform.getRotateInstance( -Math.PI / 2  ));
+        return path;
+    }
+    private static Path2D barb_70_74() {
+        Path2D path = new Path2D.Double();
+        path.moveTo(0,0);
+        path.quadTo(-2,2,-4,0);
+        path.quadTo(-2,-2,0,0);
+        path.lineTo(22,0);
+        path.lineTo(22,-16);
+        path.lineTo(18,0);
+        path.moveTo(15,0);
+        path.lineTo(19,-16);
+        path.moveTo(12,0);
+        path.lineTo(16,-16);
+        //path.transform(AffineTransform.getRotateInstance(-90));
+        path.transform(AffineTransform.getRotateInstance( -Math.PI / 2  ));
+        return path;
+    }
+    private static Path2D barb_75_79() {
+        Path2D path = new Path2D.Double();
+        path.moveTo(0,0);
+        path.quadTo(-2,2,-4,0);
+        path.quadTo(-2,-2,0,0);
+        path.lineTo(22,0);
+        path.lineTo(22,-16);
+        path.lineTo(18,0);
+        path.moveTo(15,0);
+        path.lineTo(19,-16);
+        path.moveTo(12,0);
+        path.lineTo(16,-16);
+        path.moveTo(9,0);
+        path.lineTo(11,-8);
+        //path.transform(AffineTransform.getRotateInstance(-90));
+        path.transform(AffineTransform.getRotateInstance( -Math.PI / 2  ));
+        return path;
+    }
+    private static Path2D barb_80_84() {
+        Path2D path = new Path2D.Double();
+        path.moveTo(0,0);
+        path.quadTo(-2,2,-4,0);
+        path.quadTo(-2,-2,0,0);
+        path.lineTo(22,0);
+        path.lineTo(22,-16);
+        path.lineTo(18,0);
+        path.moveTo(15,0);
+        path.lineTo(19,-16);
+        path.moveTo(12,0);
+        path.lineTo(16,-16);
+        path.moveTo(9,0);
+        path.lineTo(13,-16);
+        //path.transform(AffineTransform.getRotateInstance(-90));
+        path.transform(AffineTransform.getRotateInstance( -Math.PI / 2  ));
+        return path;
+    }
+    private static Path2D barb_85_89() {
+        Path2D path = new Path2D.Double();
+        path.moveTo(0,0);
+        path.quadTo(-2,2,-4,0);
+        path.quadTo(-2,-2,0,0);
+        path.lineTo(22,0);
+        path.lineTo(22,-16);
+        path.lineTo(18,0);
+        path.moveTo(15,0);
+        path.lineTo(19,-16);
+        path.moveTo(12,0);
+        path.lineTo(16,-16);
+        path.moveTo(9,0);
+        path.lineTo(13,-16);
+        path.moveTo(6,0);
+        path.lineTo(8,-8);
+        //path.transform(AffineTransform.getRotateInstance(-90));
+        path.transform(AffineTransform.getRotateInstance( -Math.PI / 2  ));
+        return path;
+    }
+    private static Path2D barb_90_94() {
+        Path2D path = new Path2D.Double();
+        path.moveTo(0,0);
+        path.quadTo(-2,2,-4,0);
+        path.quadTo(-2,-2,0,0);
+        path.lineTo(22,0);
+        path.lineTo(22,-16);
+        path.lineTo(18,0);
+        path.moveTo(15,0);
+        path.lineTo(19,-16);
+        path.moveTo(12,0);
+        path.lineTo(16,-16);
+        path.moveTo(9,0);
+        path.lineTo(13,-16);
+        path.moveTo(6,0);
+        path.lineTo(10,-16);
+        //path.transform(AffineTransform.getRotateInstance(-90));
+        path.transform(AffineTransform.getRotateInstance( -Math.PI / 2  ));
+        return path;
+    }
+    private static Path2D barb_95_99() {
+        Path2D path = new Path2D.Double();
+        path.moveTo(0,0);
+        path.quadTo(-2,2,-4,0);
+        path.quadTo(-2,-2,0,0);
+        path.lineTo(22,0);
+        path.lineTo(22,-16);
+        path.lineTo(18,0);
+        path.moveTo(15,0);
+        path.lineTo(19,-16);
+        path.moveTo(12,0);
+        path.lineTo(16,-16);
+        path.moveTo(9,0);
+        path.lineTo(13,-16);
+        path.moveTo(6,0);
+        path.lineTo(10,-16);
+        path.moveTo(3,0);
+        path.lineTo(5,-8);
+        //path.transform(AffineTransform.getRotateInstance(-90));
+        path.transform(AffineTransform.getRotateInstance( -Math.PI / 2  ));
+        return path;
+    }
+    private static Path2D barb_100() {
+        Path2D path = new Path2D.Double();
+        path.moveTo(0,0);
+        path.quadTo(-2,2,-4,0);
+        path.quadTo(-2,-2,0,0);
+        path.lineTo(22,0);
+        path.lineTo(22,-16);
+        path.lineTo(14,-16);
+        path.lineTo(14,0);
+        //path.transform(AffineTransform.getRotateInstance(-90));
+        path.transform(AffineTransform.getRotateInstance( -Math.PI / 2  ));
+        return path;
+    }
+}
Index: java/uk/ac/rdg/resc/ncwms/controller/GetMapStyleRequest.java
===================================================================
--- java/uk/ac/rdg/resc/ncwms/controller/GetMapStyleRequest.java        
(revisión: 919)
+++ java/uk/ac/rdg/resc/ncwms/controller/GetMapStyleRequest.java        (copia 
de trabajo)
@@ -55,6 +55,7 @@
     // These are the data values that correspond with the extremes of the
     // colour scale
     private Range<Float> colorScaleRange;
+    private float vectorScale;
     
     /**
      * Creates a new instance of GetMapStyleRequest from the given parameters
@@ -90,6 +91,7 @@
         this.colorScaleRange = getColorScaleRange(params);
         this.numColourBands = getNumColourBands(params);
         this.logarithmic = isLogScale(params);
+        this.vectorScale = Float.parseFloat(params.getString("vectorScale", 
"1").toLowerCase());
     }
     
     /**
@@ -218,4 +220,9 @@
         return numColourBands;
     }
     
+    public float getVectorScale()
+    {
+        return vectorScale;
+    }    
+    
 }
Index: java/uk/ac/rdg/resc/ncwms/controller/AbstractWmsController.java
===================================================================
--- java/uk/ac/rdg/resc/ncwms/controller/AbstractWmsController.java     
(revisión: 919)
+++ java/uk/ac/rdg/resc/ncwms/controller/AbstractWmsController.java     (copia 
de trabajo)
@@ -84,10 +84,10 @@
 import uk.ac.rdg.resc.edal.util.Range;
 import uk.ac.rdg.resc.edal.util.Ranges;
 import uk.ac.rdg.resc.ncwms.exceptions.StyleNotDefinedException;
-import uk.ac.rdg.resc.ncwms.util.WmsUtils;
 import uk.ac.rdg.resc.ncwms.wms.Dataset;
 import uk.ac.rdg.resc.ncwms.wms.Layer;
 import uk.ac.rdg.resc.ncwms.wms.ScalarLayer;
+import uk.ac.rdg.resc.ncwms.util.WmsUtils;
 
 /**
  * <p>This Controller is the entry point for all standard WMS operations
@@ -307,10 +307,6 @@
             }
         }
 
-        // Find out whether we are going to represent times in a verbose or
-        // concise way in the Capabilities document.
-        boolean verboseTimes = params.getBoolean("verbose", false);
-
         Map<String, Object> models = new HashMap<String, Object>();
         models.put("config", this.serverConfig);
         models.put("datasets", datasets);
@@ -323,12 +319,13 @@
         String[] supportedCrsCodes = new String[]{
             "EPSG:4326", "CRS:84", // Plate Carree
             "EPSG:41001", // Mercator
+            "EPSG:3857", // The official "Web" project (Bing, Google, Yahoo)
+            "EPSG:900913", // Depreceated Google projection code
             "EPSG:27700", // British National Grid
             // See http://nsidc.org/data/atlas/ogc_services.html for useful
             // stuff about polar stereographic projections
             "EPSG:3408", // NSIDC EASE-Grid North
             "EPSG:3409", // NSIDC EASE-Grid South
-            "EPSG:3857", // Google Maps
             "EPSG:32661", // North Polar stereographic
             "EPSG:32761" // South Polar stereographic
         };
@@ -340,7 +337,6 @@
         models.put("legendWidth", ColorPalette.LEGEND_WIDTH);
         models.put("legendHeight", ColorPalette.LEGEND_HEIGHT);
         models.put("paletteNames", ColorPalette.getAvailablePaletteNames());
-        models.put("verboseTimes", verboseTimes);
 
         // Do WMS version negotiation.  From the WMS 1.3.0 spec:
         // * If a version unknown to the server and higher than the lowest
@@ -400,6 +396,7 @@
         String mimeType = styleRequest.getImageFormat();
         // This throws an InvalidFormatException if the MIME type is not 
supported
         ImageFormat imageFormat = ImageFormat.get(mimeType);
+        float vectorScale = styleRequest.getVectorScale();
 
         GetMapDataRequest dr = getMapRequest.getDataRequest();
 
@@ -435,6 +432,11 @@
             String styleType = styleStrEls[0];
             if (styleType.equalsIgnoreCase("boxfill")) style = 
ImageProducer.Style.BOXFILL;
             else if (styleType.equalsIgnoreCase("vector")) style = 
ImageProducer.Style.VECTOR;
+            else if (styleType.equalsIgnoreCase("fancyvec")) style = 
ImageProducer.Style.FANCYVEC;
+            else if (styleType.equalsIgnoreCase("linevec")) style = 
ImageProducer.Style.LINEVEC;
+            else if (styleType.equalsIgnoreCase("stumpvec")) style = 
ImageProducer.Style.STUMPVEC;
+            else if (styleType.equalsIgnoreCase("trivec")) style = 
ImageProducer.Style.TRIVEC;
+            else if (styleType.equalsIgnoreCase("barb")) style = 
ImageProducer.Style.BARB;
             else throw new StyleNotDefinedException("The style " + styles[0] +
                 " is not supported by this server");
 
@@ -459,6 +461,7 @@
             .logarithmic(logScale)
             .opacity(styleRequest.getOpacity())
             .numColourBands(styleRequest.getNumColourBands())
+            .vectorScale(vectorScale)
             .build();
         // Need to make sure that the images will be compatible with the
         // requested image format
@@ -474,6 +477,7 @@
         double zValue = getElevationValue(dr.getElevationString(), layer);
 
         // Cycle through all the provided timesteps, extracting data for each 
step
+        // Cycle through all the provided timesteps, extracting data for each 
step
         List<String> tValueStrings = new ArrayList<String>();
         List<DateTime> timeValues = getTimeValues(dr.getTimeString(), layer);
         if (timeValues.size() > 1 && !imageFormat.supportsMultipleFrames()) {
@@ -660,7 +664,7 @@
                 featureData.put(tValues.get(i), tsData.get(i));
             }
         }
-
+        
         if (request.getOutputFormat().equals(FEATURE_INFO_XML_FORMAT)) {
             Map<String, Object> models = new HashMap<String, Object>();
             models.put("longitude", lonLat.getLongitude());
@@ -743,6 +747,7 @@
     /**
      * Outputs a transect (data value versus distance along a path) in PNG or
      * XML format.
+     * @todo this method is too long, refactor!
      */
     protected ModelAndView getTransect(RequestParams params, LayerFactory 
layerFactory,
             HttpServletResponse response, UsageLogEntry usageLogEntry)
@@ -952,7 +957,7 @@
         }
         
         return null;
-    }
+    }    
   
     private static JFreeChart createVerticalSectionChart(RequestParams params,
             Layer layer, DateTime tValue, LineString lineString,
@@ -1024,7 +1029,7 @@
             scaleRange = Ranges.newRange(min, max);
         }
         
-        double zValue = getElevationValue(params.getString("elevation"), 
layer);
+         double zValue = getElevationValue(params.getString("elevation"), 
layer);
          
         return Charting.createVerticalSectionChart(layer, lineString,
                 zValues, sectionData, scaleRange, palette, numColourBands, 
logScale,zValue);
Index: java/uk/ac/rdg/resc/ncwms/wms/AbstractScalarLayer.java
===================================================================
--- java/uk/ac/rdg/resc/ncwms/wms/AbstractScalarLayer.java      (revisión: 919)
+++ java/uk/ac/rdg/resc/ncwms/wms/AbstractScalarLayer.java      (copia de 
trabajo)
@@ -57,6 +57,8 @@
  */
 public abstract class AbstractScalarLayer implements ScalarLayer
 {
+        
+    
     /** TODO: may belong elsewhere (e.g. a CDM package), in which case should 
be made unmodifiable */
     private static final Set<String> PRESSURE_UNITS =
         CollectionUtils.setOf("Pa", "hPa", "bar", "millibar", "decibar", 
"atmosphere", "atm", "pascal");
@@ -213,7 +215,7 @@
     public int findAndCheckTimeIndex(DateTime target) throws 
InvalidDimensionValueException
     {
         if (!this.hasTimeAxis()) return -1;
-        int index = WmsUtils.findTimeIndex(this.getTimeValues(), target);
+        int index = WmsUtils.findTimeIndex(this.getTimeValues(), target);      
  
         if (index >= 0) return index;
         throw new InvalidDimensionValueException("time", 
WmsUtils.dateTimeToISO8601(target));
     }
Index: src/main/java/thredds/server/config/CdmInit.java
===================================================================
--- src/main/java/thredds/server/config/CdmInit.java    (revisión: 13794)
+++ src/main/java/thredds/server/config/CdmInit.java    (copia de trabajo)
@@ -81,7 +81,7 @@
     int jvmPercent = ThreddsConfig.getInt("FeatureCollection.jvmPercent", 2);
 
     try {
-      thredds.inventory.bdb.MetadataManager.setCacheDirectory(fcCache, 
maxSizeBytes, jvmPercent);
+      thredds.inventory.bdb.MetadataManager.setCacheDirectory(fcCache, 
maxSizeBytes, jvmPercent);             
       thredds.inventory.DatasetCollectionManager.enableMetadataManager();
       startupLog.info("CdmInit: FeatureCollection.cacheDirectory= "+fcCache);
     } catch (Exception e) {
Index: src/main/java/thredds/server/wms/ThreddsScalarLayer.java
===================================================================
--- src/main/java/thredds/server/wms/ThreddsScalarLayer.java    (revisión: 
13794)
+++ src/main/java/thredds/server/wms/ThreddsScalarLayer.java    (copia de 
trabajo)
@@ -40,18 +40,28 @@
 import org.joda.time.Chronology;
 import org.joda.time.DateTime;
 import ucar.nc2.dt.GridDatatype;
-import uk.ac.rdg.resc.ncwms.cdm.CdmUtils;
-import uk.ac.rdg.resc.ncwms.cdm.DataReadingStrategy;
-import uk.ac.rdg.resc.ncwms.coords.HorizontalPosition;
-import uk.ac.rdg.resc.ncwms.coords.PointList;
+//import uk.ac.rdg.resc.ncwms.cdm.CdmUtils;
+//import uk.ac.rdg.resc.ncwms.cdm.DataReadingStrategy;
+//import uk.ac.rdg.resc.ncwms.coords.HorizontalPosition;
+//import uk.ac.rdg.resc.ncwms.coords.PointList;
+import uk.ac.rdg.resc.edal.cdm.DataReadingStrategy;
+import uk.ac.rdg.resc.edal.geometry.HorizontalPosition;
 import uk.ac.rdg.resc.ncwms.exceptions.InvalidDimensionValueException;
-import uk.ac.rdg.resc.ncwms.util.Range;
+//import uk.ac.rdg.resc.ncwms.util.Range;
+import uk.ac.rdg.resc.edal.util.Range;
 import uk.ac.rdg.resc.ncwms.wms.AbstractScalarLayer;
+import uk.ac.rdg.resc.edal.coverage.CoverageMetadata;
+import uk.ac.rdg.resc.edal.coverage.domain.Domain;
 
 import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Arrays;
 import java.util.List;
 import thredds.server.wms.config.LayerSettings;
+import thredds.server.wms.config.WmsDetailedConfig;
 import ucar.nc2.Attribute;
+import uk.ac.rdg.resc.edal.cdm.CdmUtils;
+import uk.ac.rdg.resc.edal.coverage.domain.impl.HorizontalDomain;
 import uk.ac.rdg.resc.ncwms.graphics.ColorPalette;
 
 /**
@@ -69,8 +79,8 @@
     // Will be set in ThreddsWmsController.ThreddsLayerFactory
     private LayerSettings layerSettings;
 
-    public ThreddsScalarLayer(String id) {
-        super(id);
+    public ThreddsScalarLayer(CoverageMetadata cm){
+        super(cm);
     }
 
   @Override
@@ -109,26 +119,20 @@
     public Float readSinglePoint(DateTime time, double elevation, 
HorizontalPosition xy)
             throws InvalidDimensionValueException, IOException
     {
-        PointList singlePoint = PointList.fromPoint(xy);
-        return this.readPointList(time, elevation, singlePoint).get(0);
+        Domain singlePoint = new HorizontalDomain(xy);
+        return this.readHorizontalPoints(time, elevation, singlePoint).get(0);
     }
 
     @Override
-    public List<Float> readPointList(DateTime time, double elevation, 
PointList pointList)
+    public List<Float> readHorizontalPoints(DateTime time, double elevation, 
Domain domain)            
             throws InvalidDimensionValueException, IOException
     {
         int tIndex = this.findAndCheckTimeIndex(time);
         int zIndex = this.findAndCheckElevationIndex(elevation);
-        return CdmUtils.readPointList(
-            this.grid,
-            this.getHorizontalCoordSys(),
-            tIndex,
-            zIndex,
-            pointList,
-            this.dataReadingStrategy
-        );
+        Domain<HorizontalPosition> targetDomain = domain;        
+        return CdmUtils.readHorizontalPoints(dataReadingStrategy, grid,  
tIndex, zIndex, targetDomain);                
     }
-
+ 
     public String getStandardName() {
         if (this.grid == null) return null;
         Attribute stdNameAtt = 
this.grid.findAttributeIgnoreCase("standard_name");
@@ -170,4 +174,44 @@
         return this.layerSettings.getDefaultNumColorBands();
     }
 
+    public List<List<Float>> readVerticalSection(DateTime dt, List<Double> 
list, Domain<HorizontalPosition> domain) throws InvalidDimensionValueException, 
IOException {
+        
+        int tIndex = this.findAndCheckTimeIndex( dt );        
+        Domain<HorizontalPosition> targetDomain = domain;
+
+        // Defend against null values
+        List<Integer> zIndices;
+        if (list == null) {
+            zIndices = Arrays.asList(-1);
+        } else {
+            zIndices = new ArrayList<Integer>(list.size());
+            for (Double el : list) {
+                zIndices.add(this.findAndCheckElevationIndex(el));
+            }
+        }        
+        
+        return CdmUtils.readVerticalSection( this.dataReadingStrategy, grid, 
tIndex, zIndices, targetDomain);
+    }   
+    
+    /**
+     *
+     * Static factory method. Builds a new ThreddsScalarLayer and set several 
properties
+     * 
+     * @param cm
+     * @param gdt
+     * @param drStrategy
+     * @param ds
+     * @param wmsConfig
+     * @return 
+     */
+    public static ThreddsScalarLayer getNewLayer( CoverageMetadata cm, 
GridDatatype gdt, DataReadingStrategy drStrategy, ThreddsDataset ds, 
WmsDetailedConfig wmsConfig ){
+        
+        ThreddsScalarLayer tsl = new ThreddsScalarLayer(cm);                
+        tsl.setGridDatatype(gdt);    
+        tsl.setTimeValues(cm.getTimeValues());
+        tsl.setDataReadingStrategy(drStrategy);
+        tsl.setDataset(ds);
+        tsl.setLayerSettings(wmsConfig.getSettings(tsl));
+        return tsl;
+    }
 }
\ No newline at end of file
Index: src/main/java/thredds/server/wms/ThreddsDataset.java
===================================================================
--- src/main/java/thredds/server/wms/ThreddsDataset.java        (revisión: 
13794)
+++ src/main/java/thredds/server/wms/ThreddsDataset.java        (copia de 
trabajo)
@@ -32,14 +32,16 @@
 
 package thredds.server.wms;
 
+import java.io.IOException;
 import org.joda.time.DateTime;
 import ucar.nc2.dataset.NetcdfDataset;
 import ucar.nc2.dt.GridDataset;
-import ucar.nc2.dt.GridDatatype;
-import uk.ac.rdg.resc.ncwms.cdm.AbstractScalarLayerBuilder;
-import uk.ac.rdg.resc.ncwms.cdm.CdmUtils;
-import uk.ac.rdg.resc.ncwms.cdm.DataReadingStrategy;
-import uk.ac.rdg.resc.ncwms.cdm.LayerBuilder;
+//import uk.ac.rdg.resc.ncwms.cdm.AbstractScalarLayerBuilder;
+//import uk.ac.rdg.resc.ncwms.cdm.CdmUtils;
+//import uk.ac.rdg.resc.ncwms.cdm.DataReadingStrategy;
+//import uk.ac.rdg.resc.ncwms.cdm.LayerBuilder;
+import uk.ac.rdg.resc.edal.cdm.CdmUtils;
+import uk.ac.rdg.resc.edal.cdm.DataReadingStrategy;
 import uk.ac.rdg.resc.ncwms.util.WmsUtils;
 import uk.ac.rdg.resc.ncwms.wms.Dataset;
 import uk.ac.rdg.resc.ncwms.wms.Layer;
@@ -47,6 +49,8 @@
 
 import java.util.*;
 import thredds.server.wms.config.WmsDetailedConfig;
+import ucar.nc2.dt.GridDatatype;
+import uk.ac.rdg.resc.edal.coverage.CoverageMetadata;
 
 /**
  * A {@link uk.ac.rdg.resc.ncwms.wms.Dataset} that provides access to layers 
read from
@@ -56,7 +60,7 @@
  */
 public class ThreddsDataset implements Dataset
 {
-
+     
   private final String urlPath;
   private final String title;
   private final Map<String, ThreddsScalarLayer> scalarLayers =
@@ -67,7 +71,7 @@
   /**
    * LayerBuilder used to create ThreddsLayers in CdmUtils.findAndUpdateLayers
    */
-  private static final LayerBuilder<ThreddsScalarLayer> THREDDS_LAYER_BUILDER 
= new AbstractScalarLayerBuilder<ThreddsScalarLayer>()
+  /*private static final LayerBuilder<ThreddsScalarLayer> 
THREDDS_LAYER_BUILDER = new AbstractScalarLayerBuilder<ThreddsScalarLayer>()
   {
     @Override
     public ThreddsScalarLayer newLayer( String id )
@@ -86,30 +90,38 @@
     {
       layer.setGridDatatype( grid );
     }
-  };
+  };*/
 
   /**
    * Creates a new ThreddsDataset with the given id from the given 
NetcdfDataset
    */
-  public ThreddsDataset( String urlPath, GridDataset gridDataset, 
WmsDetailedConfig wmsConfig )
+  public ThreddsDataset( String urlPath, GridDataset gridDataset, 
WmsDetailedConfig wmsConfig ) throws IOException
   {
     this.urlPath = urlPath;
-    this.title = gridDataset.getTitle();
+    this.title = gridDataset.getTitle();         
     
-    NetcdfDataset ncDataset = (NetcdfDataset) gridDataset.getNetcdfFile();
-
+    NetcdfDataset ncDataset = (NetcdfDataset)gridDataset.getNetcdfFile();    
+    //this.location = ncDataset.getLocation();
+        
     // Get the most appropriate data-reading strategy for this dataset
-    DataReadingStrategy drStrategy = CdmUtils.getOptimumDataReadingStrategy( 
ncDataset );
-
-    // Now load the scalar layers
-    CdmUtils.findAndUpdateLayers( gridDataset, THREDDS_LAYER_BUILDER, 
this.scalarLayers );
+    DataReadingStrategy drStrategy = CdmUtils.getOptimumDataReadingStrategy( 
ncDataset );     
+    // Now load the scalar layers    
+   Collection<CoverageMetadata> ccm 
=CdmUtils.readCoverageMetadata(gridDataset);    
+   Iterator<CoverageMetadata> icm = ccm.iterator();
+   while ( icm.hasNext()  ){    
+    CoverageMetadata cm  = icm.next();    
+    GridDatatype gdt = gridDataset.findGridDatatype(cm.getId());   
+    ThreddsScalarLayer tsl = ThreddsScalarLayer.getNewLayer(cm, gdt, 
drStrategy, this, wmsConfig);
+    this.scalarLayers.put( tsl.getName()  , tsl);
+  }
+    //CdmUtils.findAndUpdateLayers( gridDataset, THREDDS_LAYER_BUILDER, 
this.scalarLayers );
     // Set the extra properties of each layer
-    for ( ThreddsScalarLayer layer : this.scalarLayers.values() )
+    /*for ( ThreddsScalarLayer layer : this.scalarLayers.values() )
     {
       layer.setDataReadingStrategy( drStrategy );
       layer.setDataset( this );
       layer.setLayerSettings(wmsConfig.getSettings(layer));
-    }
+    }*/
 
     // Find the vector quantities
     Collection<VectorLayer> vectorLayersColl = WmsUtils.findVectorLayers( 
this.scalarLayers.values() );
@@ -234,5 +246,5 @@
   {
     return false;
   }
-
+  
 }
\ No newline at end of file
Index: src/main/java/thredds/server/wms/config/StandardNameSettings.java
===================================================================
--- src/main/java/thredds/server/wms/config/StandardNameSettings.java   
(revisión: 13794)
+++ src/main/java/thredds/server/wms/config/StandardNameSettings.java   (copia 
de trabajo)
@@ -30,8 +30,7 @@
 
 import org.jdom.Element;
 import ucar.nc2.units.SimpleUnit;
-import ucar.units.ConversionException;
-import uk.ac.rdg.resc.ncwms.util.Range;
+//import uk.ac.rdg.resc.ncwms.util.Range;
 
 /**
  * Encapsulates the setting per standard name
Index: src/main/java/thredds/server/wms/config/WmsDetailedConfig.java
===================================================================
--- src/main/java/thredds/server/wms/config/WmsDetailedConfig.java      
(revisión: 13794)
+++ src/main/java/thredds/server/wms/config/WmsDetailedConfig.java      (copia 
de trabajo)
@@ -42,8 +42,10 @@
 import org.jdom.xpath.XPath;
 import thredds.server.wms.ThreddsLayer;
 import ucar.nc2.units.SimpleUnit;
-import uk.ac.rdg.resc.ncwms.util.Range;
-import uk.ac.rdg.resc.ncwms.util.Ranges;
+//import uk.ac.rdg.resc.ncwms.util.Range;
+//import uk.ac.rdg.resc.ncwms.util.Ranges;
+import uk.ac.rdg.resc.edal.util.Range;
+import uk.ac.rdg.resc.edal.util.Ranges;
 
 /**
  * Encapsulates the sysadmin's settings of the detailed configuration of the 
WMS
Index: src/main/java/thredds/server/wms/config/LayerSettings.java
===================================================================
--- src/main/java/thredds/server/wms/config/LayerSettings.java  (revisión: 
13794)
+++ src/main/java/thredds/server/wms/config/LayerSettings.java  (copia de 
trabajo)
@@ -30,8 +30,10 @@
 
 import org.jdom.Element;
 import uk.ac.rdg.resc.ncwms.graphics.ColorPalette;
-import uk.ac.rdg.resc.ncwms.util.Range;
-import uk.ac.rdg.resc.ncwms.util.Ranges;
+//import uk.ac.rdg.resc.ncwms.util.Range;
+//import uk.ac.rdg.resc.ncwms.util.Ranges;
+import uk.ac.rdg.resc.edal.util.Range;
+import uk.ac.rdg.resc.edal.util.Ranges;
 
 /**
  * Simple Java bean encapsulating the settings (allowFeatureInfo,
Index: src/main/java/thredds/server/wms/ThreddsWmsController.java
===================================================================
--- src/main/java/thredds/server/wms/ThreddsWmsController.java  (revisión: 
13794)
+++ src/main/java/thredds/server/wms/ThreddsWmsController.java  (copia de 
trabajo)
@@ -1,265 +1,273 @@
-/*
- * Copyright (c) 2007 The University of Reading
- * All rights reserved.
- *
- * Redistribution and use in source and binary forms, with or without
- * modification, are permitted provided that the following conditions
- * are met:
- * 1. Redistributions of source code must retain the above copyright
- *    notice, this list of conditions and the following disclaimer.
- * 2. Redistributions in binary form must reproduce the above copyright
- *    notice, this list of conditions and the following disclaimer in the
- *    documentation and/or other materials provided with the distribution.
- * 3. Neither the name of the University of Reading, nor the names of the
- *    authors or contributors may be used to endorse or promote products
- *    derived from this software without specific prior written permission.
- *
- * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``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 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 thredds.server.wms;
-
-import java.io.File;
-import java.io.FileNotFoundException;
-import java.io.IOException;
-import java.util.Arrays;
-import java.util.Collection;
-import javax.servlet.http.HttpServletRequest;
-import javax.servlet.http.HttpServletResponse;
-
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-import org.springframework.web.servlet.ModelAndView;
-import thredds.server.dataset.TdsRequestedDataset;
-import thredds.server.wms.config.WmsDetailedConfig;
-import thredds.servlet.ServletUtil;
-import thredds.servlet.UsageLog;
-import ucar.nc2.dt.GridDataset;
-import uk.ac.rdg.resc.ncwms.controller.AbstractWmsController;
-import uk.ac.rdg.resc.ncwms.controller.RequestParams;
-import uk.ac.rdg.resc.ncwms.exceptions.LayerNotDefinedException;
-import uk.ac.rdg.resc.ncwms.exceptions.OperationNotSupportedException;
-import uk.ac.rdg.resc.ncwms.exceptions.WmsException;
-import uk.ac.rdg.resc.ncwms.usagelog.UsageLogEntry;
-import uk.ac.rdg.resc.ncwms.wms.Dataset;
-import uk.ac.rdg.resc.ncwms.wms.Layer;
-
-/**
- * <p>WmsController for THREDDS</p>
- *
- * @author Jon Blower
- */
-public final class ThreddsWmsController extends AbstractWmsController
-{
-  private static final Logger log = LoggerFactory.getLogger( 
ThreddsWmsController.class );
-  private final Logger logServerStartup = org.slf4j.LoggerFactory.getLogger( 
"serverStartup" );
-
-  private WmsDetailedConfig wmsConfig;
-
-  private static final class ThreddsLayerFactory implements LayerFactory
-  {
-    private ThreddsDataset ds;
-
-    public ThreddsLayerFactory( ThreddsDataset ds )
-    {
-      this.ds = ds;
-    }
-
-    @Override
-    public Layer getLayer( String layerName ) throws LayerNotDefinedException
-    {
-      ThreddsLayer layer = ds.getLayerById( layerName );
-      if ( layer == null ) throw new LayerNotDefinedException( layerName );
-      return layer;
-    }
-  }
-
-  /**
-   * Called by Spring to initialize the controller. Loads the WMS configuration
-   * from /content/thredds/wmsConfig.xml.
-   * @throws Exception if the config file could not be loaded for some reason.
-   */
-  @Override
-  public void init() throws Exception
-  {
-    super.init();
-    ThreddsServerConfig tdsWmsServerConfig = (ThreddsServerConfig) 
this.serverConfig;
-    logServerStartup.info( "WMS:allow= " + tdsWmsServerConfig.isAllow() );
-    if ( tdsWmsServerConfig.isAllow() )
-    {
-      logServerStartup.info( "WMS:allowRemote= " + 
tdsWmsServerConfig.isAllowRemote() );
-      File wmsConfigFile = 
tdsWmsServerConfig.getTdsContext().getConfigFileSource().getFile( 
"wmsConfig.xml" );
-      if ( wmsConfigFile == null || !wmsConfigFile.exists() || 
!wmsConfigFile.isFile() )
-      {
-        tdsWmsServerConfig.setAllow( false );
-        logServerStartup.error( "init(): Disabling WMS: Could not find 
wmsConfig.xml. [Default version available at 
${TOMCAT_HOME}/webapps/thredds/WEB-INF/altContent/startup/wmsConfig.xml." );
-        return;
-      }
-      this.wmsConfig = WmsDetailedConfig.fromFile( wmsConfigFile );
-      logServerStartup.info( "init(): Loaded WMS configuration from 
wmsConfig.xml" );
-    }
-  }
-
-  @Override
-  protected ModelAndView dispatchWmsRequest(
-          String request,
-          RequestParams params,
-          HttpServletRequest httpServletRequest,
-          HttpServletResponse httpServletResponse,
-          UsageLogEntry usageLogEntry ) throws Exception
-  {
-    log.info( UsageLog.setupRequestContext( httpServletRequest ) );
-
-    ThreddsServerConfig tdsWmsServerConfig = (ThreddsServerConfig) 
this.serverConfig;
-    if ( ! tdsWmsServerConfig.isAllow() )
-    {
-      log.info( "dispatchWmsRequest(): WMS service not supported." );
-      log.info( UsageLog.closingMessageForRequestContext( 
HttpServletResponse.SC_FORBIDDEN, -1 ) );
-      httpServletResponse.sendError( HttpServletResponse.SC_FORBIDDEN, "WMS 
service not supported." );
-      return null;
-    }
-
-    GridDataset gd = null;
-    try
-    {
-      TdsRequestedDataset reqDataset = new TdsRequestedDataset( 
httpServletRequest );
-      if ( reqDataset.isRemote() && ! tdsWmsServerConfig.isAllowRemote() )
-      {
-        log.info( "dispatchWmsRequest(): WMS service not supported for remote 
datasets." );
-        throw new WmsException( "WMS service not supported for remote 
(non-server-resident) datasets.", "LayerNotDefined");
-      }
-
-      try {
-        gd = reqDataset.openAsGridDataset( httpServletRequest, 
httpServletResponse );
-      }
-      catch ( FileNotFoundException e ) {
-        // LOOK ToDo Instead could catch FileNotFoundExceptions below and also 
add to exceptionResolver in wms-servlet.xml 
-        log.info( "dispatchWmsRequest(): File not found [" + 
reqDataset.getPath() + "]:" + e.getMessage() + "." );
-        throw new LayerNotDefinedException( reqDataset.getPath());
-      }
-      if ( gd == null )
-      {
-        // We have sent an auth challenge to the client, so we send no
-        // further information
-        log.info( UsageLog.closingMessageForRequestContext( 
HttpServletResponse.SC_UNAUTHORIZED, -1 ) );
-        return null;
-      }
-
-      // Extract the metadata from the GridDataset to form a Dataset object
-      // TODO: what to use for the dataset ID?
-      // TODO: It can be inefficient to create an entire {@link 
ThreddsDataset} object when
-      // all we need is a single layer. This means that a lot of unnecessary 
objects will be
-      // created when only a single layer is needed, e.g. for a GetMap 
operation. Should create
-      //  a means to extract a single layer without creating a whole dataset; 
however, this
-      //  could be tricky when dealing with virtual layers (e.g. velocities).
-      ThreddsDataset ds = new ThreddsDataset( reqDataset.getPath(), gd, 
this.wmsConfig );
-      // Create an object that extracts layers from the dataset
-      ThreddsLayerFactory layerFactory = new ThreddsLayerFactory( ds );
-
-      ModelAndView modelAndView;
-      if ( request.equals( "GetCapabilities" ) )
-      {
-        // The Capabilities document will contain a single dataset
-        Collection<? extends Dataset> datasets = Arrays.asList( ds );
-        // In THREDDS we don't know the last update time so we use null
-        modelAndView = getCapabilities( datasets, null, params, 
httpServletRequest, usageLogEntry );
-      }
-      else if ( request.equals( "GetMap" ) )
-      {
-        modelAndView = getMap( params, layerFactory, httpServletResponse, 
usageLogEntry );
-      }
-      else if ( request.equals( "GetFeatureInfo" ) )
-      {
-        modelAndView = getFeatureInfo( params, layerFactory, 
httpServletRequest, httpServletResponse, usageLogEntry );
-      }
-      // The REQUESTs below are non-standard and could be refactored into
-      // a different servlet endpoint
-      else if (request.equals("GetMetadata"))
-      {
-        ThreddsMetadataController tms =
-            new ThreddsMetadataController(layerFactory, tdsWmsServerConfig, 
ds);
-        // This is a request for non-standard metadata.  (This will one
-        // day be replaced by queries to Capabilities fragments, if possible.)
-        // Delegate to the ThreddsMetadataController
-        modelAndView = tms.handleRequest( httpServletRequest, 
httpServletResponse, usageLogEntry );
-      }
-      else if ( request.equals( "GetLegendGraphic" ) )
-      {
-        // This is a request for an image that contains the colour scale
-        // and range for a given layer
-        modelAndView = getLegendGraphic( params, layerFactory, 
httpServletResponse );
-      }
-      else if ( request.equals( "GetTransect" ) )
-      {
-        modelAndView = getTransect( params, layerFactory, httpServletResponse, 
usageLogEntry );
-      }
-      else
-      {
-        throw new OperationNotSupportedException( request );
-      }
-
-      log.info( UsageLog.closingMessageForRequestContext( 
HttpServletResponse.SC_OK, -1 ) );
-      return modelAndView;
-    }
-    catch ( LayerNotDefinedException e ) {
-      log.debug( "dispatchWmsRequest(): LayerNotDefinedException: " + 
e.getMessage());
-      log.info( UsageLog.closingMessageForRequestContext( 
HttpServletResponse.SC_NOT_FOUND, -1 ) );
-      throw e;
-    }
-    catch ( WmsException e ) {
-      log.debug( "dispatchWmsRequest(): WmsException: "  + e.getMessage() );
-      log.info( UsageLog.closingMessageForRequestContext( 
HttpServletResponse.SC_BAD_REQUEST, -1 ) );
-      throw e;
-    }
-    catch ( thredds.server.dataset.DatasetException e ) {
-      log.debug( "dispatchWmsRequest(): DatasetException: " + e.getMessage() );
-      log.info( UsageLog.closingMessageForRequestContext( 
HttpServletResponse.SC_BAD_REQUEST, -1 ) );
-      throw new WmsException( e.getMessage() );
-    }
-    catch ( java.net.SocketException e ) {
-      log.debug( "dispatchWmsRequest(): SocketException: " + e.getMessage());
-      log.info( UsageLog.closingMessageForRequestContext( 
ServletUtil.STATUS_CLIENT_ABORT, -1 ) );
-      httpServletResponse.setStatus(ServletUtil.STATUS_CLIENT_ABORT);
-      return null;
-    }
-    catch ( IOException e ) {
-      if ( e.getClass().getName().equals( 
"org.apache.catalina.connector.ClientAbortException")) {
-        log.debug( "dispatchWmsRequest(): ClientAbortException: " + 
e.getMessage() );
-        log.info( UsageLog.closingMessageForRequestContext( 
ServletUtil.STATUS_CLIENT_ABORT, -1 ) );
-        return null;
-      }
-      log.error( "dispatchWmsRequest(): IOException: ", e );
-      log.info( UsageLog.closingMessageForRequestContext( 
HttpServletResponse.SC_INTERNAL_SERVER_ERROR, -1 ) );
-      if ( httpServletResponse.isCommitted() )
-        return null;
-      throw e;
-    }
-    catch ( Exception e ) {
-      log.error( "dispatchWmsRequest(): Exception: ", e );
-      log.info( UsageLog.closingMessageForRequestContext( 
HttpServletResponse.SC_INTERNAL_SERVER_ERROR, -1 ) );
-      if ( httpServletResponse.isCommitted() )
-        return null;
-      throw e;
-    }
-    catch ( Error e ) {
-      log.error( "dispatchWmsRequest(): Error: ", e );
-      log.info( UsageLog.closingMessageForRequestContext( 
HttpServletResponse.SC_INTERNAL_SERVER_ERROR, -1 ) );
-      if ( httpServletResponse.isCommitted() )
-        return null;
-      throw e;
-    }
-    finally {
-      // We ensure that the GridDataset object is closed
-      if ( gd != null) gd.close();
-    }
-  }
-
-}
+/*
+ * Copyright (c) 2007 The University of Reading
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the University of Reading, nor the names of the
+ *    authors or contributors may be used to endorse or promote products
+ *    derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``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 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 thredds.server.wms;
+
+import java.io.File;
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.util.Arrays;
+import java.util.Collection;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.web.servlet.ModelAndView;
+import thredds.server.dataset.TdsRequestedDataset;
+import thredds.server.wms.config.WmsDetailedConfig;
+import thredds.servlet.ServletUtil;
+import thredds.servlet.UsageLog;
+import ucar.nc2.dt.GridDataset;
+import uk.ac.rdg.resc.ncwms.controller.AbstractWmsController;
+import uk.ac.rdg.resc.ncwms.controller.RequestParams;
+import uk.ac.rdg.resc.ncwms.exceptions.LayerNotDefinedException;
+import uk.ac.rdg.resc.ncwms.exceptions.OperationNotSupportedException;
+import uk.ac.rdg.resc.ncwms.exceptions.WmsException;
+import uk.ac.rdg.resc.ncwms.usagelog.UsageLogEntry;
+import uk.ac.rdg.resc.ncwms.wms.Dataset;
+import uk.ac.rdg.resc.ncwms.wms.Layer;
+
+/**
+ * <p>WmsController for THREDDS</p>
+ *
+ * @author Jon Blower
+ */
+public final class ThreddsWmsController extends AbstractWmsController
+{
+  private static final Logger log = LoggerFactory.getLogger( 
ThreddsWmsController.class );
+  private final Logger logServerStartup = org.slf4j.LoggerFactory.getLogger( 
"serverStartup" );
+
+  private WmsDetailedConfig wmsConfig;
+
+  private static final class ThreddsLayerFactory implements LayerFactory
+  {
+    private ThreddsDataset ds;
+
+    public ThreddsLayerFactory( ThreddsDataset ds )
+    {
+      this.ds = ds;
+    }
+
+    @Override
+    public Layer getLayer( String layerName ) throws LayerNotDefinedException
+    {
+      ThreddsLayer layer = ds.getLayerById( layerName );
+      if ( layer == null ) throw new LayerNotDefinedException( layerName );
+      return layer;
+    }
+  }
+
+  /**
+   * Called by Spring to initialize the controller. Loads the WMS configuration
+   * from /content/thredds/wmsConfig.xml.
+   * @throws Exception if the config file could not be loaded for some reason.
+   */
+  @Override
+  public void init() throws Exception
+  {
+    super.init();
+    ThreddsServerConfig tdsWmsServerConfig = (ThreddsServerConfig) 
this.serverConfig;
+    logServerStartup.info( "WMS:allow= " + tdsWmsServerConfig.isAllow() );
+    if ( tdsWmsServerConfig.isAllow() )
+    {
+      logServerStartup.info( "WMS:allowRemote= " + 
tdsWmsServerConfig.isAllowRemote() );
+      File wmsConfigFile = 
tdsWmsServerConfig.getTdsContext().getConfigFileSource().getFile( 
"wmsConfig.xml" );
+      if ( wmsConfigFile == null || !wmsConfigFile.exists() || 
!wmsConfigFile.isFile() )
+      {
+        tdsWmsServerConfig.setAllow( false );
+        logServerStartup.error( "init(): Disabling WMS: Could not find 
wmsConfig.xml. [Default version available at 
${TOMCAT_HOME}/webapps/thredds/WEB-INF/altContent/startup/wmsConfig.xml." );
+        return;
+      }
+      this.wmsConfig = WmsDetailedConfig.fromFile( wmsConfigFile );
+      logServerStartup.info( "init(): Loaded WMS configuration from 
wmsConfig.xml" );
+    }
+  }
+
+  @Override
+  protected ModelAndView dispatchWmsRequest(
+          String request,
+          RequestParams params,
+          HttpServletRequest httpServletRequest,
+          HttpServletResponse httpServletResponse,
+          UsageLogEntry usageLogEntry ) throws Exception
+  {
+    log.info( UsageLog.setupRequestContext( httpServletRequest ) );
+
+    ThreddsServerConfig tdsWmsServerConfig = (ThreddsServerConfig) 
this.serverConfig;
+    if ( ! tdsWmsServerConfig.isAllow() )
+    {
+      log.info( "dispatchWmsRequest(): WMS service not supported." );
+      log.info( UsageLog.closingMessageForRequestContext( 
HttpServletResponse.SC_FORBIDDEN, -1 ) );
+      httpServletResponse.sendError( HttpServletResponse.SC_FORBIDDEN, "WMS 
service not supported." );
+      return null;
+    }
+
+    GridDataset gd = null;
+    try
+    {
+      TdsRequestedDataset reqDataset = new TdsRequestedDataset( 
httpServletRequest );
+      if ( reqDataset.isRemote() && ! tdsWmsServerConfig.isAllowRemote() )
+      {
+        log.info( "dispatchWmsRequest(): WMS service not supported for remote 
datasets." );
+        throw new WmsException( "WMS service not supported for remote 
(non-server-resident) datasets.", "LayerNotDefined");
+      }
+
+      try {
+        gd = reqDataset.openAsGridDataset( httpServletRequest, 
httpServletResponse );
+      }
+      catch ( FileNotFoundException e ) {
+        // LOOK ToDo Instead could catch FileNotFoundExceptions below and also 
add to exceptionResolver in wms-servlet.xml 
+        log.info( "dispatchWmsRequest(): File not found [" + 
reqDataset.getPath() + "]:" + e.getMessage() + "." );
+        throw new LayerNotDefinedException( reqDataset.getPath());
+      }
+      if ( gd == null )
+      {
+        // We have sent an auth challenge to the client, so we send no
+        // further information
+        log.info( UsageLog.closingMessageForRequestContext( 
HttpServletResponse.SC_UNAUTHORIZED, -1 ) );
+        return null;
+      }
+
+      // Extract the metadata from the GridDataset to form a Dataset object
+      // TODO: what to use for the dataset ID?
+      // TODO: It can be inefficient to create an entire {@link 
ThreddsDataset} object when
+      // all we need is a single layer. This means that a lot of unnecessary 
objects will be
+      // created when only a single layer is needed, e.g. for a GetMap 
operation. Should create
+      //  a means to extract a single layer without creating a whole dataset; 
however, this
+      //  could be tricky when dealing with virtual layers (e.g. velocities).
+      ThreddsDataset ds = new ThreddsDataset( reqDataset.getPath(), gd, 
this.wmsConfig );
+      // Create an object that extracts layers from the dataset
+      ThreddsLayerFactory layerFactory = new ThreddsLayerFactory( ds );
+
+      ModelAndView modelAndView;
+      if ( request.equals( "GetCapabilities" ) )
+      {
+        // The Capabilities document will contain a single dataset
+        Collection<? extends Dataset> datasets = Arrays.asList( ds );
+        // In THREDDS we don't know the last update time so we use null
+        modelAndView = getCapabilities( datasets, null, params, 
httpServletRequest, usageLogEntry );
+      }
+      else if ( request.equals( "GetMap" ) )
+      {
+        modelAndView = getMap( params, layerFactory, httpServletResponse, 
usageLogEntry );
+      }
+      else if ( request.equals( "GetFeatureInfo" ) )
+      {
+        modelAndView = getFeatureInfo( params, layerFactory, 
httpServletRequest, httpServletResponse, usageLogEntry );
+      }
+      // The REQUESTs below are non-standard and could be refactored into
+      // a different servlet endpoint
+      else if (request.equals("GetMetadata"))
+      {
+        ThreddsMetadataController tms =
+            new ThreddsMetadataController(layerFactory, tdsWmsServerConfig, 
ds);
+        // This is a request for non-standard metadata.  (This will one
+        // day be replaced by queries to Capabilities fragments, if possible.)
+        // Delegate to the ThreddsMetadataController
+        modelAndView = tms.handleRequest( httpServletRequest, 
httpServletResponse, usageLogEntry );
+      }
+      else if ( request.equals( "GetLegendGraphic" ) )
+      {
+        // This is a request for an image that contains the colour scale
+        // and range for a given layer
+        modelAndView = getLegendGraphic( params, layerFactory, 
httpServletResponse );
+      }
+      else if ( request.equals( "GetTransect" ) )
+      {
+        modelAndView = getTransect( params, layerFactory, httpServletResponse, 
usageLogEntry );
+      }
+      else if (request.equals("GetVerticalProfile"))
+      {
+            modelAndView = getVerticalProfile(params, layerFactory, 
httpServletResponse, usageLogEntry);
+      }
+      else if (request.equals("GetVerticalSection"))
+      {
+           modelAndView = getVerticalSection(params, layerFactory, 
httpServletResponse, usageLogEntry);
+      }              
+      else
+      {
+        throw new OperationNotSupportedException( request );
+      }
+
+      log.info( UsageLog.closingMessageForRequestContext( 
HttpServletResponse.SC_OK, -1 ) );
+      return modelAndView;
+    }
+    catch ( LayerNotDefinedException e ) {
+      log.debug( "dispatchWmsRequest(): LayerNotDefinedException: " + 
e.getMessage());
+      log.info( UsageLog.closingMessageForRequestContext( 
HttpServletResponse.SC_NOT_FOUND, -1 ) );
+      throw e;
+    }
+    catch ( WmsException e ) {
+      log.debug( "dispatchWmsRequest(): WmsException: "  + e.getMessage() );
+      log.info( UsageLog.closingMessageForRequestContext( 
HttpServletResponse.SC_BAD_REQUEST, -1 ) );
+      throw e;
+    }
+    catch ( thredds.server.dataset.DatasetException e ) {
+      log.debug( "dispatchWmsRequest(): DatasetException: " + e.getMessage() );
+      log.info( UsageLog.closingMessageForRequestContext( 
HttpServletResponse.SC_BAD_REQUEST, -1 ) );
+      throw new WmsException( e.getMessage() );
+    }
+    catch ( java.net.SocketException e ) {
+      log.debug( "dispatchWmsRequest(): SocketException: " + e.getMessage());
+      log.info( UsageLog.closingMessageForRequestContext( 
ServletUtil.STATUS_CLIENT_ABORT, -1 ) );
+      httpServletResponse.setStatus(ServletUtil.STATUS_CLIENT_ABORT);
+      return null;
+    }
+    catch ( IOException e ) {
+      if ( e.getClass().getName().equals( 
"org.apache.catalina.connector.ClientAbortException")) {
+        log.debug( "dispatchWmsRequest(): ClientAbortException: " + 
e.getMessage() );
+        log.info( UsageLog.closingMessageForRequestContext( 
ServletUtil.STATUS_CLIENT_ABORT, -1 ) );
+        return null;
+      }
+      log.error( "dispatchWmsRequest(): IOException: ", e );
+      log.info( UsageLog.closingMessageForRequestContext( 
HttpServletResponse.SC_INTERNAL_SERVER_ERROR, -1 ) );
+      if ( httpServletResponse.isCommitted() )
+        return null;
+      throw e;
+    }
+    catch ( Exception e ) {
+      log.error( "dispatchWmsRequest(): Exception: ", e );
+      log.info( UsageLog.closingMessageForRequestContext( 
HttpServletResponse.SC_INTERNAL_SERVER_ERROR, -1 ) );
+      if ( httpServletResponse.isCommitted() )
+        return null;
+      throw e;
+    }
+    catch ( Error e ) {
+      log.error( "dispatchWmsRequest(): Error: ", e );
+      log.info( UsageLog.closingMessageForRequestContext( 
HttpServletResponse.SC_INTERNAL_SERVER_ERROR, -1 ) );
+      if ( httpServletResponse.isCommitted() )
+        return null;
+      throw e;
+    }
+    finally {
+      // We ensure that the GridDataset object is closed
+      if ( gd != null) gd.close();
+    }
+  }
+
+}
Index: src/main/java/thredds/server/wms/ThreddsServerConfig.java
===================================================================
--- src/main/java/thredds/server/wms/ThreddsServerConfig.java   (revisión: 
13794)
+++ src/main/java/thredds/server/wms/ThreddsServerConfig.java   (copia de 
trabajo)
@@ -57,7 +57,7 @@
   private TdsContext tdsContext;
 
   private String defaultPaletteLocation;
-
+  
   private ThreddsServerConfig() {}
 
   public void setTdsContext( TdsContext tdsContext ) {
@@ -161,10 +161,11 @@
     }
 
     @Override
-    public String getAbstract() {
+    public String getServerAbstract() {        
       return this.tdsContext.getServerInfo().getSummary();
     }
-
+    
+    
     @Override
     public Set<String> getKeywords() {
       String[] keysArray = 
this.tdsContext.getServerInfo().getKeywords().split( ",\\s*" );
Index: src/main/java/thredds/server/wms/ThreddsVectorLayer.java
===================================================================
--- src/main/java/thredds/server/wms/ThreddsVectorLayer.java    (revisión: 
13794)
+++ src/main/java/thredds/server/wms/ThreddsVectorLayer.java    (copia de 
trabajo)
@@ -33,9 +33,11 @@
 import org.joda.time.DateTime;
 import org.opengis.metadata.extent.GeographicBoundingBox;
 import thredds.server.wms.config.LayerSettings;
-import uk.ac.rdg.resc.ncwms.coords.HorizontalCoordSys;
+//import uk.ac.rdg.resc.ncwms.coords.HorizontalCoordSys;
+import uk.ac.rdg.resc.edal.coverage.grid.HorizontalGrid;
 import uk.ac.rdg.resc.ncwms.graphics.ColorPalette;
-import uk.ac.rdg.resc.ncwms.util.Range;
+//import uk.ac.rdg.resc.ncwms.util.Range;
+import uk.ac.rdg.resc.edal.util.Range;
 import uk.ac.rdg.resc.ncwms.wms.ScalarLayer;
 import uk.ac.rdg.resc.ncwms.wms.VectorLayer;
 
@@ -95,9 +97,12 @@
         return this.wrappedLayer.getTitle();
     }
 
-    public String getAbstract() {
+    /*public String getAbstract() {
         return this.wrappedLayer.getAbstract();
-    }
+    }*/
+    public String getLayerAbstract() {
+        return this.wrappedLayer.getLayerAbstract();
+    }    
 
     public String getUnits() {
         return this.wrappedLayer.getUnits();
@@ -107,9 +112,12 @@
         return this.wrappedLayer.getGeographicBoundingBox();
     }
 
-    public HorizontalCoordSys getHorizontalCoordSys() {
+    /*public HorizontalCoordSys getHorizontalCoordSys() {
         return this.wrappedLayer.getHorizontalCoordSys();
-    }
+    }*/
+    public HorizontalGrid getHorizontalGrid() {
+        return this.wrappedLayer.getHorizontalGrid();
+    }    
 
     public Chronology getChronology() {
         return this.wrappedLayer.getChronology();
@@ -143,6 +151,10 @@
         return this.wrappedLayer.isElevationPositive();
     }
 
+    public boolean isElevationPressure() { 
+        return this.wrappedLayer.isElevationPressure(); 
+    }    
+    
     public void setLayerSettings(LayerSettings layerSettings) {
         this.layerSettings = layerSettings;
     }
Index: src/main/webapp/WEB-INF/jsp/wms/showFeatureInfo_xml.jsp
===================================================================
--- src/main/webapp/WEB-INF/jsp/wms/showFeatureInfo_xml.jsp     (revisión: 
13794)
+++ src/main/webapp/WEB-INF/jsp/wms/showFeatureInfo_xml.jsp     (copia de 
trabajo)
@@ -18,8 +18,8 @@
     <longitude>${longitude}</longitude>
     <latitude>${latitude}</latitude>
     <c:if test="${not empty gridCoords}">
-        <iIndex>${gridCoords[0]}</iIndex>
-        <jIndex>${gridCoords[1]}</jIndex>
+        <iIndex>${gridCoords.coordinateValues[0]}</iIndex>
+        <jIndex>${gridCoords.coordinateValues[1]}</jIndex>
         <gridCentreLon>${gridCentre.longitude}</gridCentreLon>
         <gridCentreLat>${gridCentre.latitude}</gridCentreLat>
     </c:if>
Index: src/main/webapp/WEB-INF/jsp/wms/capabilities_xml_1_1_1.jsp
===================================================================
--- src/main/webapp/WEB-INF/jsp/wms/capabilities_xml_1_1_1.jsp  (revisión: 
13794)
+++ src/main/webapp/WEB-INF/jsp/wms/capabilities_xml_1_1_1.jsp  (copia de 
trabajo)
@@ -30,7 +30,7 @@
         <!-- Human-readable title for pick lists -->
         <Title><c:out value="${config.title}"/></Title>
         <!-- Narrative description providing additional information -->
-        <Abstract><c:out value="${config.abstract}"/></Abstract>
+        <Abstract><c:out value="${config.serverAbstract}"/></Abstract>
         <KeywordList>
             <%-- forEach recognizes that keywords is a comma-delimited String 
--%>
             <c:forEach var="keyword" items="${config.keywords}">
@@ -109,7 +109,7 @@
                 <Layer<c:if test="${layer.queryable}"> queryable="1"</c:if>>
                     <Name>${layer.name}</Name>
                     <Title><c:out value="${layer.title}"/></Title>
-                    <Abstract><c:out value="${layer.abstract}"/></Abstract>
+                    <Abstract><c:out 
value="${layer.layerAbstract}"/></Abstract>
                     <c:set var="bbox" value="${layer.geographicBoundingBox}"/>
                     <LatLonBoundingBox minx="${bbox.westBoundLongitude}" 
maxx="${bbox.eastBoundLongitude}" miny="${bbox.southBoundLatitude}" 
maxy="${bbox.northBoundLatitude}"/>
                     <BoundingBox SRS="EPSG:4326" 
minx="${bbox.westBoundLongitude}" maxx="${bbox.eastBoundLongitude}" 
miny="${bbox.southBoundLatitude}" maxy="${bbox.northBoundLatitude}"/>
Index: src/main/webapp/WEB-INF/jsp/wms/capabilities_xml.jsp
===================================================================
--- src/main/webapp/WEB-INF/jsp/wms/capabilities_xml.jsp        (revisión: 
13794)
+++ src/main/webapp/WEB-INF/jsp/wms/capabilities_xml.jsp        (copia de 
trabajo)
@@ -30,7 +30,7 @@
     <Service>
         <Name>WMS</Name>
         <Title><c:out value="${config.title}"/></Title>
-        <Abstract><c:out value="${config.abstract}"/></Abstract>
+        <Abstract><c:out value="${config.serverAbstract}"/></Abstract>
         <KeywordList>
             <%-- forEach recognizes that keywords is a comma-delimited String 
--%>
             <c:forEach var="keyword" items="${config.keywords}">
@@ -87,7 +87,7 @@
                 <Layer<c:if test="${layer.queryable}"> queryable="1"</c:if>>
                     <Name>${layer.name}</Name>
                     <Title><c:out value="${layer.title}"/></Title>
-                    <Abstract><c:out value="${layer.abstract}"/></Abstract>
+                    <Abstract><c:out 
value="${layer.layerAbstract}"/></Abstract>
                     <c:set var="bbox" value="${layer.geographicBoundingBox}"/>
                     <EX_GeographicBoundingBox>
                         
<westBoundLongitude>${bbox.westBoundLongitude}</westBoundLongitude>
Index: src/main/webapp/WEB-INF/classes/thredds/server/tds.properties
===================================================================
--- src/main/webapp/WEB-INF/classes/thredds/server/tds.properties       
(revisión: 13794)
+++ src/main/webapp/WEB-INF/classes/thredds/server/tds.properties       (copia 
de trabajo)
@@ -1,7 +1,7 @@
-# DO NOT EDIT THIS FILE: It is automatically generated from 
C:\dev\tds4.2\thredds\tds\src\main\template\thredds\server\tds.properties.TEMPLATE
-tds.version=4.2.20110521.2245
+# DO NOT EDIT THIS FILE: It is automatically generated from 
E:\src\TDS_SRC_HOME\tds\src\main\template\thredds\server\tds.properties.TEMPLATE
+tds.version=4.2.20110617.0613
 tds.version.brief=4.2
-tds.version.builddate=20110521.2245
+tds.version.builddate=20110617.0613
 
 tds.url=http://www.unidata.ucar.edu/projects/THREDDS/tech/TDS.html
 
tds.documentation.url=http://www.unidata.ucar.edu/projects/THREDDS/tech/TDS.html
Index: src/main/webapp/godiva2/js/godiva2.js
===================================================================
--- src/main/webapp/godiva2/js/godiva2.js       (revisión: 13794)
+++ src/main/webapp/godiva2/js/godiva2.js       (copia de trabajo)
@@ -75,13 +75,19 @@
         line = line.substring(11, line.length - 1);
         // Load an image of the transect
         var server = activeLayer.server == '' ? 'wms' : activeLayer.server;
+        var logscale = $('scaleSpacing').value == 'logarithmic';
         var transectUrl = server + '?REQUEST=GetTransect' +
             '&LAYER=' + activeLayer.id +
             '&CRS=' + map.baseLayer.projection.toString() +
             '&ELEVATION=' + getZValue() +
             '&TIME=' + isoTValue +
             '&LINESTRING=' + line +
-            '&FORMAT=image/png';
+            '&FORMAT=image/png' +
+            // Styling parameters are needed if we create a vertical section 
plot
+            '&COLORSCALERANGE=' + scaleMinVal + ',' + scaleMaxVal +
+            '&NUMCOLORBANDS=' + $('numColorBands').value +
+            '&LOGSCALE' + logscale +
+            '&PALETTE=' + paletteName;
         popUp(transectUrl, 450, 350);
     });
 
@@ -138,13 +144,10 @@
         "http://wms-basemaps.appspot.com/wms";, {layers: 'bluemarble_file', 
format: 'image/jpeg'});
     var srtm_dem = new OpenLayers.Layer.WMS( "SRTM DEM",
         "http://iceds.ge.ucl.ac.uk/cgi-bin/icedswms?";, 
{layers:'bluemarble,srtm30'}, {wrapDateLine: true});
-
-    var ol_wms = new OpenLayers.Layer.WMS1_1_1( "OpenLayers WMS", 
+    var ol_wms = new OpenLayers.Layer.WMS1_1_1( "OpenLayers WMS",
         "http://labs.metacarta.com/wms-c/Basic.py?";, {layers: 'basic'});
-    var osm_wms = new OpenLayers.Layer.WMS1_1_1( "Openstreetmap",
-        "http://labs.metacarta.com/wms-c/Basic.py?";, {layers: 'osm-map' });
     var human_wms = new OpenLayers.Layer.WMS1_1_1( "Human Footprint",
-        "http://labs.metacarta.com/wms-c/Basic.py?";, {layers: 'hfoot' });
+        "http://labs.metacarta.com/wms-c/Basic.py?";, {layers: 'hfoot'});
 
     // Now for the polar stereographic layers, one for each pole.  We do this
     // as an Untiled layer because, for some reason, if we use a tiled layer
@@ -164,39 +167,31 @@
     var windowLow = centre - 2 * halfSideLength;
     var windowHigh = centre + 2 * halfSideLength;
     var polarWindow = new OpenLayers.Bounds(windowLow, windowLow, windowHigh, 
windowHigh);
-    var northPoleBaseLayer = new OpenLayers.Layer.WMS.Untiled(
+    var northPoleBaseLayer = new OpenLayers.Layer.WMS1_1_1(
         "North polar stereographic",
-        "http://nsidc.org/cgi-bin/atlas_north";,
+        "http://wms-basemaps.appspot.com/wms";,
         {
-            layers: 'country_borders,arctic_circle',
-            format: 'image/png'
+            layers: 'bluemarble_file',
+            format: 'image/jpeg'
         },
         {
             wrapDateLine: false,
             transitionEffect: 'resize',
-            /*/projection: 'EPSG:3408', // NSIDC EASE-Grid North
-            maxExtent: new OpenLayers.Bounds(-9036842.762, -9036842.762,
-                9036842.762, 9036842.762),
-            maxResolution: 2 * 9036842.762 / 256*/
             projection: 'EPSG:32661',
             maxExtent: polarWindow,
             maxResolution: polarMaxResolution
         }
     );
-    var southPoleBaseLayer = new OpenLayers.Layer.WMS.Untiled(
+    var southPoleBaseLayer = new OpenLayers.Layer.WMS1_1_1(
         "South polar stereographic",
-        "http://nsidc.org/cgi-bin/atlas_south";,
+        "http://wms-basemaps.appspot.com/wms";,
         {
-            layers: 'country_borders,antarctic_circle',
-            format: 'image/png'
+            layers: 'bluemarble_file',
+            format: 'image/jpeg'
         },
         {
             wrapDateLine: false,
             transitionEffect: 'resize',
-            /*/projection: 'EPSG:3409', // NSIDC EASE-Grid South
-            maxExtent: new OpenLayers.Bounds(-9036842.762, -9036842.762,
-                9036842.762, 9036842.762),
-            maxResolution: 2 * 9036842.762 / 256*/
             projection: 'EPSG:32761',
             maxExtent: polarWindow,
             maxResolution: polarMaxResolution
@@ -204,18 +199,18 @@
     );
 
     // ESSI WMS (see Stefano Nativi's email to me, Feb 15th)
-    /*var essi_wms = new OpenLayers.Layer.WMS.Untiled( "ESSI WMS", 
+    /*var essi_wms = new OpenLayers.Layer.WMS.Untiled( "ESSI WMS",
         "http://athena.pin.unifi.it:8080/ls/servlet/LayerService?";,
         {layers: 'sst(time-lat-lon)-T0', transparent: 'true' } );
     essi_wms.setVisibility(false);*/
-            
+
     // The SeaZone Web Map server
     /*var seazone_wms = new OpenLayers.Layer.WMS1_3("SeaZone bathymetry", 
"http://ws.cadcorp.com/seazone/wms.exe?";,
         {layers: 'Bathymetry___Elevation.bds', transparent: 'true'});
     seazone_wms.setVisibility(false);*/
-    
-    map.addLayers([demis_wms, bluemarble_demis_wms, bluemarble_wms, ol_wms, 
osm_wms, human_wms, northPoleBaseLayer, southPoleBaseLayer, drawinglayer/*, 
seazone_wms, essi_wms*/]);
-    
+
+    map.addLayers([demis_wms, bluemarble_demis_wms, bluemarble_wms, srtm_dem, 
ol_wms, human_wms, northPoleBaseLayer, southPoleBaseLayer, drawinglayer/*, 
seazone_wms, essi_wms*/]);
+
     map.setBaseLayer(demis_wms);
     projectionCode = map.baseLayer.projection.getCode();
 
@@ -225,25 +220,25 @@
     map.events.register('moveend', map, setPermalinkURL);
     // Register an event for when the base layer of the map is changed
     map.events.register('changebaselayer', map, baseLayerChanged);
-    
+
     // If we have loaded Google Maps and the browser is compatible, add it as 
a base layer
     if (typeof GBrowserIsCompatible == 'function' && GBrowserIsCompatible()) {
         var gmapLayer = new OpenLayers.Layer.Google("Google Maps (satellite)", 
{type: G_SATELLITE_MAP});
         var gmapLayer2 = new OpenLayers.Layer.Google("Google Maps 
(political)", {type: G_NORMAL_MAP});
         map.addLayers([gmapLayer, gmapLayer2]);
     }
-    
+
     layerSwitcher = new OpenLayers.Control.LayerSwitcher()
     map.addControl(layerSwitcher);
 
     //map.addControl(new OpenLayers.Control.MousePosition({prefix: 'Lon: ', 
separator: ' Lat:'}));
     map.zoomTo(1);
-    
+
     // Add a listener for changing the base map
     //map.events.register("changebaselayer", map, function() { 
alert(this.projection) });
     // Add a listener for GetFeatureInfo
     map.events.register('click', map, getFeatureInfo);
-    
+
     // Set up the autoload object
     // Note that we must get the query string from the top-level frame
     // strip off the leading question mark
@@ -252,9 +247,9 @@
         // We're in an iframe so we must also use the query string from the 
top frame
         populateAutoLoad(window.top.location);
     }
-    
+
     // Set up the palette selector pop-up
-    paletteSelector = new YAHOO.widget.Panel("paletteSelector", { 
+    paletteSelector = new YAHOO.widget.Panel("paletteSelector", {
         width:"400px",
         constraintoviewport: true,
         fixedcenter: true,
@@ -339,7 +334,7 @@
         // Clear the contents of the tree
         tree.removeChildren(tree.getRoot());
     }
-    
+
     // The servers can be specified using the global "servers" array above
     // but if not, we'll just use the default server
     if (typeof servers == 'undefined' || servers == null) {
@@ -478,7 +473,7 @@
         );
         tempPopup.autoSize = true;
         map.addPopup(tempPopup);
-        
+
         var params = {
             REQUEST: "GetFeatureInfo",
             BBOX: map.getExtent().toBBOX(),
@@ -495,10 +490,9 @@
         }
         featureInfoUrl = ncwms.getFullRequestString(
             params //,
-            //'wms' // We must always load from the home server
+            //'wms'  //We must always load from the home server
         );
         // Now make the call to GetFeatureInfo
-               // TODO: why not do this via server.js?
         OpenLayers.loadURL(featureInfoUrl, '', this, function(response) {
             var xmldoc = response.responseXML;
             var lon = parseFloat(getElementValue(xmldoc, 'longitude'));
@@ -529,6 +523,22 @@
                         "title='Sets the maximum of the colour scale to " + 
truncVal + "'>" +
                         "Set colour max</a>";
                 }
+                if (activeLayer.zaxis != null && activeLayer.zaxis.values != 
null &&
+                    activeLayer.zaxis.values.length > 1) {
+                    // This layer has a vertical axis with > 1 values
+                    // TODO Construct the URL of the vertical profile plot
+                    var profilePlotURL = activeLayer.server == '' ? 'wms' : 
activeLayer.server;
+                    profilePlotURL += '?REQUEST=GetVerticalProfile' +
+                                      '&LAYER=' + activeLayer.id +
+                                      '&CRS=CRS:84' + // We frame the request 
in lon/lat coordinates
+                                      '&TIME=' + isoTValue +
+                                      '&POINT=' + lon + '%20' + lat +
+                                      '&FORMAT=image/png';
+
+                    html += "<br /><a href='#' onclick=popUp('" + 
profilePlotURL + "',550,450)"
+                         + " title='Creates a vertical profile plot at this 
point'>"
+                         + "Create vertical profile plot</a>";
+                }
                 if (timeSeriesSelected()) {
                     // Construct a GetFeatureInfo request for the timeseries 
plot
                     var serverAndParams = featureInfoUrl.split('?');
@@ -599,14 +609,14 @@
 }
 
 // Called when the user clicks on the name of a displayable layer in the 
left-hand menu
-// Gets the details (units, grid etc) of the given layer. 
+// Gets the details (units, grid etc) of the given layer.
 function layerSelected(layerDetails)
 {
     clearPopups();
     activeLayer = layerDetails;
     gotScaleRange = false;
     resetAnimation();
-    
+
     // Units are ncWMS-specific
     var isNcWMS = false;
     if (typeof layerDetails.units != 'undefined') {
@@ -626,11 +636,15 @@
 
     var zAxis = layerDetails.zaxis;
     if (zAxis == null) {
-        $('zAxis').innerHTML = ''
+        $('zAxis').style.visibility = 'hidden';
         $('zValues').style.visibility = 'hidden';
     } else {
-        var axisLabel = zAxis.positive ? 'Elevation' : 'Depth';
-        $('zAxis').innerHTML = '<b>' + axisLabel + ' (' + zAxis.units + '): 
</b>';
+        var zAxisLabel = getZAxisLabel(zAxis);
+        var isDepth = zAxisLabel == 'Depth';        
+        $('zAxisLabel').innerHTML = zAxisLabel;
+        $('zAxisUnits').innerHTML = zAxis.units;
+        $('zAxis').style.visibility = 'visible';
+        $('zValues').style.visibility = 'visible';
         // Populate the drop-down list of z values
         // Make z range selector invisible if there are no z values
         var zValues = zAxis.values;
@@ -638,7 +652,9 @@
         var nearestIndex = 0;
         for (var j = 0; j < zValues.length; j++) {
             // Create an item in the drop-down list for this z level
-            var zLabel = zAxis.positive ? zValues[j] : -zValues[j];
+            // If this is a depth axis we reverse the sign of the displayed 
values.
+            // See getZAxisLabel
+            var zLabel = isDepth ? -zValues[j] : zValues[j];
             $('zValues').options[j] = new Option(zLabel, zValues[j]);
             // Find the nearest value to the currently-selected
             // depth level
@@ -650,7 +666,7 @@
         }
         $('zValues').selectedIndex = nearestIndex;
     }
-    
+
     // Only show the scale bar if the data are coming from an ncWMS server
     var scaleVisibility = isNcWMS ? 'visible' : 'hidden';
     $('scaleBar').style.visibility = scaleVisibility;
@@ -658,7 +674,7 @@
     $('scaleMax').style.visibility = scaleVisibility;
     $('scaleControls').style.visibility = scaleVisibility;
     $('autoScale').style.visibility = scaleLocked ? 'hidden' : scaleVisibility;
-    
+
     // Set the scale value if this is present in the metadata
     if (typeof layerDetails.scaleRange != 'undefined' &&
             layerDetails.scaleRange != null &&
@@ -722,7 +738,7 @@
     } else {
         $('moreInfo').innerHTML = '';
     }
-    
+
     // Set up the copyright statement
     $('copyright').innerHTML = layerDetails.copyright;
 
@@ -807,7 +823,7 @@
             // Widen the panel text to accommodate the calendar
             $('panelText').style.width = "512px";
             $('date').innerHTML = '<b>Date/time: </b>';
-            
+
             if (basicDateSelector == null) {
                 basicDateSelector = new BasicDateSelector({
                     el: $('date'),
@@ -827,6 +843,26 @@
     }
 }
 
+// Gets the string to use as the z axis label
+function getZAxisLabel(zAxis)
+{
+    // Check for units of pressure: see 
http://cf-pcmdi.llnl.gov/documents/cf-conventions/1.4/cf-conventions.html#id2982284
+    if (['Pa', 'hPa', 'bar', 'millibar', 'decibar', 'atmosphere', 'atm', 
'pascal'].indexOf(zAxis.units) >= 0) {
+        return 'Pressure';
+    }
+    // This will be a normal elevation (e.g. in metres).  Check to see if all
+    // values are negative: if so we'll call this a depth axis
+    var allNegative = true;
+    var zVals = zAxis.values;
+    for (var i = 0; i < zVals.length; i++) {
+        if (zVals[i] >= 0) {
+            allNegative = false;
+            break;
+        }
+    }
+    return allNegative ? 'Depth' : 'Height';
+}
+
 // Function that is used by the calendar to see whether a date should be 
disabled
 function isDateDisabled(date, year, month, day)
 {
@@ -927,7 +963,7 @@
     }
     s += '</select>';
     $('time').innerHTML = s;
-    
+
     timeSelect = $('tValues');
     // If there was a previously-selected time, select it
     if (selectedTimeStr) {
@@ -986,7 +1022,7 @@
         isoTValue = $('tValues').value;
     }
     if (newVariable) {
-        // We use the bounding box of the whole layer 
+        // We use the bounding box of the whole layer
         dataBounds = bbox[0] + ',' + bbox[1] + ',' + bbox[2] + ',' + bbox[3];
     } else {
         // Use the intersection of the viewport and the layer's bounding box
@@ -1249,9 +1285,9 @@
     // because it seems that the calendar.show() method can change the 
visibility
     // unexpectedly
     $('zValues').style.visibility = $('zValues').options.length == 0 ? 
'hidden' : 'visible';
-    
+
     var logscale = $('scaleSpacing').value == 'logarithmic';
-    
+
     // Update the intermediate scale markers
     var min = logscale ? Math.log(parseFloat(scaleMinVal)) : 
parseFloat(scaleMinVal);
     var max = logscale ? Math.log(parseFloat(scaleMaxVal)) : 
parseFloat(scaleMaxVal);
@@ -1260,20 +1296,20 @@
     var scaleTwoThirds = logscale ? Math.exp(min + 2 * third) : min + 2 * 
third;
     $('scaleOneThird').innerHTML = scaleOneThird.toPrecision(4);
     $('scaleTwoThirds').innerHTML = scaleTwoThirds.toPrecision(4);
-    
+
     if ($('tValues')) {
         isoTValue = $('tValues').value;
     }
-    
+
     // Set the map bounds automatically
     if (typeof autoLoad.bbox != 'undefined') {
         map.zoomToExtent(getBounds(autoLoad.bbox));
     }
-    
+
     // Make sure the autoLoad object is cleared
     autoLoad = new Object();
-    
-    // Get the default style for this layer.  There is some defensive 
programming here to 
+
+    // Get the default style for this layer.  There is some defensive 
programming here to
     // take old servers into account that don't advertise the supported styles
     var style = typeof activeLayer.supportedStyles == 'undefined' ? 'boxfill' 
: activeLayer.supportedStyles[0];
     if (paletteName != null) {
@@ -1298,12 +1334,12 @@
         // Buffer is set to 1 to avoid loading a large halo of tiles outside 
the
         // current viewport
         ncwms_tiled = new OpenLayers.Layer.WMS1_3("ncWMS",
-            activeLayer.server == '' ? 'wms' : activeLayer.server, 
+            activeLayer.server == '' ? 'wms' : activeLayer.server,
             params,
             {buffer: 1, wrapDateLine: map.baseLayer.projection == 'EPSG:4326'}
         );
         ncwms_untiled = new OpenLayers.Layer.WMS1_3("ncWMS",
-            activeLayer.server == '' ? 'wms' : activeLayer.server, 
+            activeLayer.server == '' ? 'wms' : activeLayer.server,
             params,
             {buffer: 1, ratio: 1.5, singleTile: true, wrapDateLine: 
map.baseLayer.projection == 'EPSG:4326'}
         );
@@ -1311,7 +1347,7 @@
         map.addLayers([ncwms_tiled, ncwms_untiled]);
         // Create a layer for coastlines
         // TOOD: only works at low res (zoomed out)
-        //var coastline_wms = new OpenLayers.Layer.WMS( "Coastlines", 
+        //var coastline_wms = new OpenLayers.Layer.WMS( "Coastlines",
         //    "http://labs.metacarta.com/wms/vmap0?";, {layers: 'coastline_01', 
transparent: 'true' } );
         //map.addLayers([ncwms, coastline_wms]);
         //map.addLayers([ncwms_tiled, ncwms_untiled]);
@@ -1320,7 +1356,7 @@
         ncwms.url = activeLayer.server == '' ? 'wms' : activeLayer.server;
         ncwms.mergeNewParams(params);
     }
-    
+
     var imageURL = ncwms.getURL(new OpenLayers.Bounds(bbox[0], bbox[1], 
bbox[2], bbox[3]));
     $('testImage').innerHTML = '<a target="_blank" href="' + imageURL + 
'">test image</a>';
     $('screenshot').style.visibility = 'visible'; // TODO: enable this when 
working properly
@@ -1347,7 +1383,7 @@
         $('paletteDiv').innerHTML = 'There are no alternative palettes for 
this layer';
         return;
     }
-    
+
     // TODO test if coming from a different server
     var width = 50;
     var height = 200;
@@ -1384,7 +1420,7 @@
 // Updates the colour scale bar URL
 function updateScaleBar()
 {
-    $('scaleBar').src = activeLayer.server + 
'?REQUEST=GetLegendGraphic&COLORBARONLY=true&WIDTH=1&HEIGHT=398'
+    $('scaleBar').src = 
activeLayer.server+'?REQUEST=GetLegendGraphic&COLORBARONLY=true&WIDTH=1&HEIGHT=398'
         + '&PALETTE=' + paletteName + '&NUMCOLORBANDS=' + 
$('numColorBands').value;
 }
 
@@ -1523,7 +1559,7 @@
         // Add the elevation, time and units if this layer has them
         var zAxis = activeLayer.zaxis;
         if (zAxis != null) {
-            var axisLabel = zAxis.positive ? 'Elevation' : 'Depth';
+            var axisLabel = $('zAxisLabel').innerHTML;
             var zValue = $('zValues').options[$('zValues').selectedIndex].text;
             params.elevation = axisLabel + ': ' + zValue + ' ' + zAxis.units;
         }
@@ -1558,7 +1594,7 @@
 }
 
 // Returns a bounding box as a string in format "minlon,minlat,maxlon,maxlat"
-// that represents the intersection of the currently-visible map layer's 
+// that represents the intersection of the currently-visible map layer's
 // bounding box and the viewport's bounding box.
 function getIntersectionBBOX()
 {
Index: src/main/webapp/godiva2/godiva2.html
===================================================================
--- src/main/webapp/godiva2/godiva2.html        (revisión: 13794)
+++ src/main/webapp/godiva2/godiva2.html        (copia de trabajo)
@@ -66,7 +66,7 @@
             <div id="panelText">
                 <b>Layer:</b> <span id="layerPath">Please select from the left 
panel</span><br />
                 <span id="units"></span><br />
-                <span id="zAxis"></span><select id="zValues" 
onchange="javascript:updateMap()"><option value="0">dummy</option></select><br 
/>
+                <span id="zAxis"><b><span id="zAxisLabel"></span> (<span 
id="zAxisUnits"></span>): </b></span><select id="zValues" 
onchange="javascript:updateMap()"><option value="0">dummy</option></select><br 
/>
                 <span id="date"></span>&nbsp;<span id="time"></span> <span 
id="utc">UTC</span>
                 <span id="setFrames"><span id="setFirstFrame"><a href="#" 
title="Set the current frame as the first frame of an animation" 
onclick="javascript:setFirstAnimationFrame()">first frame</a></span>
                 <span id="setLastFrame"><a href="#" title="Set the current 
frame as the last frame of an animation" 
onclick="javascript:setLastAnimationFrame()">last frame</a></span></span><br />
Index: src/main/webapp/unidataIcon.gif
===================================================================
No se puede mostrar: el archivo está marcado como binario.
svn:mime-type = application/octet-stream
Index: build.xml
===================================================================
--- build.xml   (revisión: 13794)
+++ build.xml   (copia de trabajo)
@@ -110,12 +110,12 @@
   <!-- Third-party libraries needed for runtime. -->
   <property name="commons-io.jar" value="external/commons-io-1.4.jar"/>
   <property name="epsg-wkt.jar" value="external/epsg-wkt.jar"/>
-  <property name="geotk-bundle-referencing.jar" 
value="external/geotk-bundle-referencing-3.04.jar"/>
+  <property name="geotk-bundle-referencing.jar" 
value="external/geotk-bundle-referencing-3.17.jar"/>
   <property name="http-codec.jar" value="external/commons-codec-1.3.jar"/>
   <property name="http-logging.jar" value="external/commons-logging-1.1.jar"/>
   <property name="jaxen.jar" value="external/jaxen-1.1.2.jar"/>
   <property name="json-taglib.jar" value="external/json-taglib-0.4.1.jar"/>
-
+  <property name="prtree.jar" value="external/prtree.jar"/>
   <!-- The servlet jar file is needed to compile the servlet but is not needed
     by the servlet engine, therefore, it is not needed in the war file. -->
   <property name="servlet.jar" value="external/servlet-api.jar"/>
@@ -176,6 +176,7 @@
       <include name="${jstl-standard.jar}"/>
       <include name="${log4j.jar}"/>
       <include name="${slf4jImpl.jar}"/>
+      <include name="${prtree.jar}"/>
     </patternset>
   </fileset>
 
@@ -683,7 +684,8 @@
 
 
   <!-- ************* DEPLOYMENT ******************** -->
-  <property name="username" value="admin"/>
+  <property name="username" value="tomcat"/>
+  <property name="local.password" value="tomcat"/>
   <target name="deploy-local" description="Deploy web application to 
localhost">
     <antcall target="deploy">
       <param name="server_url" value="http://localhost:8080"/>
@@ -759,7 +761,7 @@
   </target>
   <!-- Webapp lifecycle control -->
   <target name="start" description="Start web application">
-    <start url="${server_url}/manager" username="${username}" 
password="${tomcat.password}" path="${contextPath}"/>
+    <start url="http://localhost:8080/manager"; username="tomcat" 
password="tomcat" path="/thredds"/>
   </target>
   <target name="reload" description="Reload web application">
     <reload url="${server_url}/manager" username="${username}" 
password="${tomcat.password}" path="${contextPath}"/>