001    //$HeadURL: svn+ssh://rbezema@svn.wald.intevation.org/deegree/base/tags/2.1/src/org/deegree/model/csct/pt/AngleFormat.java $
002    /*
003     * Geotools 2 - OpenSource mapping toolkit
004     * (C) 2003, Geotools Project Managment Committee (PMC)
005     * (C) 2001, Institut de Recherche pour le D�veloppement
006     * (C) 1999, Fisheries and Oceans Canada
007     *
008     *    This library is free software; you can redistribute it and/or
009     *    modify it under the terms of the GNU Lesser General Public
010     *    License as published by the Free Software Foundation; either
011     *    version 2.1 of the License, or (at your option) any later version.
012     *
013     *    This library is distributed in the hope that it will be useful,
014     *    but WITHOUT ANY WARRANTY; without even the implied warranty of
015     *    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
016     *    Lesser General Public License for more details.
017     *
018     *    You should have received a copy of the GNU Lesser General Public
019     *    License along with this library; if not, write to the Free Software
020     *    Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
021     *
022     *
023     * Contacts:
024     *     UNITED KINGDOM: James Macgill
025     *             mailto:j.macgill@geog.leeds.ac.uk
026     *
027     *     FRANCE: Surveillance de l'Environnement Assist�e par Satellite
028     *             Institut de Recherche pour le D�veloppement / US-Espace
029     *             mailto:seasnet@teledetection.fr
030     *
031     *     CANADA: Observatoire du Saint-Laurent
032     *             Institut Maurice-Lamontagne
033     *             mailto:osl@osl.gc.ca
034     */
035    package org.deegree.model.csct.pt;
036    
037    // Input/output
038    import java.io.IOException;
039    import java.io.ObjectInputStream;
040    import java.text.DecimalFormat;
041    import java.text.DecimalFormatSymbols;
042    import java.text.FieldPosition;
043    import java.text.Format;
044    import java.text.ParseException;
045    import java.text.ParsePosition;
046    import java.util.Locale;
047    
048    import org.deegree.model.csct.resources.Utilities;
049    import org.deegree.model.csct.resources.XMath;
050    import org.deegree.model.csct.resources.css.ResourceKeys;
051    import org.deegree.model.csct.resources.css.Resources;
052    
053    
054    /**
055     * Parse and format angle according a specified pattern. The pattern is a string
056     * containing any characters, with a special meaning for the following characters:
057     *
058     * <blockquote><table cellpadding="3">
059     *     <tr><td><code>D</code></td><td>&nbsp;&nbsp;The integer part of degrees</td></tr>
060     *     <tr><td><code>d</code></td><td>&nbsp;&nbsp;The fractional part of degrees</td></tr>
061     *     <tr><td><code>M</code></td><td>&nbsp;&nbsp;The integer part of minutes</td></tr>
062     *     <tr><td><code>m</code></td><td>&nbsp;&nbsp;The fractional part of minutes</td></tr>
063     *     <tr><td><code>S</code></td><td>&nbsp;&nbsp;The integer part of seconds</td></tr>
064     *     <tr><td><code>s</code></td><td>&nbsp;&nbsp;The fractional part of seconds</td></tr>
065     *     <tr><td><code>.</code></td><td>&nbsp;&nbsp;The decimal separator</td></tr>
066     * </table></blockquote>
067     * <br>
068     * Upper-case letters <code>D</code>, <code>M</code> and <code>S</code> are for the integer
069     * parts of degrees, minutes and seconds respectively. They must appear in this order (e.g.
070     * "<code>M'D</code>" is illegal because "M" and "S" are inverted; "<code>D�S</code>" is
071     * illegal too because there is no "M" between "D" and "S"). Lower-case letters <code>d</code>,
072     * <code>m</code> and <code>s</code> are for fractional parts of degrees, minutes and seconds
073     * respectively. Only one of those may appears in a pattern, and it must be the last special
074     * symbol (e.g. "<code>D.dd�MM'</code>" is illegal because "d" is followed by "M";
075     * "<code>D.mm</code>" is illegal because "m" is not the fractional part of "D").
076     * <br><br>
077     * The number of occurrence of <code>D</code>, <code>M</code>, <code>S</code> and their
078     * lower-case counterpart is the number of digits to format. For example, "DD.ddd" will
079     * format angle with two digits for the integer part and three digits for the fractional
080     * part (e.g. 4.4578 will be formatted as "04.458"). Separator characters like <code>�</code>,
081     * <code>'</code> and <code>"</code> and inserted "as-is" in the formatted string (except the
082     * decimal separator dot ("<code>.</code>"), which is replaced by the local-dependent decimal
083     * separator). Separator characters may be completely omitted; <code>AngleFormat</code> will
084     * still differentiate degrees, minutes and seconds fields according the pattern. For example,
085     * "<code>0480439</code>" with the pattern "<code>DDDMMmm</code>" will be parsed as 48�04.39'.
086     * <br><br>
087     * The following table gives some examples of legal patterns.
088     *
089     * <blockquote><table cellpadding="3">
090     * <tr><th>Pattern                </th>  <th>Example   </th></tr>
091     * <tr><td><code>DD�MM'SS" </code></td>  <td>48�30'00" </td></tr>
092     * <tr><td><code>DD�MM'    </code></td>  <td>48�30'    </td></tr>
093     * <tr><td><code>DD.ddd    </code></td>  <td>48.500    </td></tr>
094     * <tr><td><code>DDMM      </code></td>  <td>4830      </td></tr>
095     * <tr><td><code>DDMMSS    </code></td>  <td>483000    </td></tr>
096     * </table></blockquote>
097     *
098     * @see Angle
099     * @see Latitude
100     * @see Longitude
101     *
102     * @version 1.0
103     * @author Martin Desruisseaux
104     */
105    public class AngleFormat extends Format {
106        /**
107         * Caract�re repr�sentant l'h�misph�re nord.
108         * Il doit obligatoirement �tre en majuscule.
109         */
110        private static final char NORTH='N';
111        
112        /**
113         * Caract�re repr�sentant l'h�misph�re sud.
114         * Il doit obligatoirement �tre en majuscule.
115         */
116        private static final char SOUTH='S';
117        
118        /**
119         * Caract�re repr�sentant l'h�misph�re est.
120         * Il doit obligatoirement �tre en majuscule.
121         */
122        private static final char EAST='E';
123        
124        /**
125         * Caract�re repr�sentant l'h�misph�re ouest.
126         * Il doit obligatoirement �tre en majuscule.
127         */
128        private static final char WEST='W';
129        
130        /**
131         * Constante indique que l'angle
132         * � formater est une longitude.
133         */
134        static final int LONGITUDE=0;
135        
136        /**
137         * Constante indique que l'angle
138         * � formater est une latitude.
139         */
140        static final int LATITUDE=1;
141        
142        /**
143         * Constante indique que le nombre
144         * � formater est une altitude.
145         */
146        static final int ALTITUDE=2;
147        
148        /**
149         * A constant for the symbol to appears before the degrees fields.
150         * Fields PREFIX, DEGREES, MINUTES and SECONDS <strong>must</strong>
151         * have increasing values (-1, 0, +1, +2, +3).
152         */
153        private static final int PREFIX_FIELD = -1;
154        
155        /**
156         * Constant for degrees field. When formatting a string, this value may be
157         * specified to the {@link java.text.FieldPosition} constructor in order to
158         * get the bounding index where degrees have been written.
159         */
160        public static final int DEGREES_FIELD = 0;
161        
162        /**
163         * Constant for minutes field. When formatting a string, this value may be
164         * specified to the {@link java.text.FieldPosition} constructor in order to
165         * get the bounding index where minutes have been written.
166         */
167        public static final int MINUTES_FIELD = 1;
168        
169        /**
170         * Constant for seconds field. When formatting a string, this value may be
171         * specified to the {@link java.text.FieldPosition} constructor in order to
172         * get the bounding index where seconds have been written.
173         */
174        public static final int SECONDS_FIELD = 2;
175        
176        /**
177         * Constant for hemisphere field. When formatting a string, this value may be
178         * specified to the {@link java.text.FieldPosition} constructor in order to
179         * get the bounding index where the hemisphere synbol has been written.
180         */
181        public static final int HEMISPHERE_FIELD = 3;
182        
183        /**
184         * Symboles repr�sentant les degr�s (0),
185         * minutes (1) et les secondes (2).
186         */
187        private static final char[] SYMBOLS = {'D', 'M', 'S'};
188        
189        /**
190         * Nombre minimal d'espaces que doivent occuper les parties
191         * enti�res des degr�s (0), minutes (1) et secondes (2). Le
192         * champs <code>widthDecimal</code> indique la largeur fixe
193         * que doit avoir la partie d�cimale. Il s'appliquera au
194         * dernier champs non-zero dans <code>width0..2</code>.
195         */
196        private int width0=1, width1=2, width2=0, widthDecimal=0;
197        
198        /**
199         * Caract�res � ins�rer au d�but (<code>prefix</code>) et � la
200         * suite des degr�s, minutes et secondes (<code>suffix0..2</code>).
201         * Ces champs doivent �tre <code>null</code> s'il n'y a rien � ins�rer.
202         */
203        private String prefix=null, suffix0="�", suffix1="'", suffix2="\"";
204        
205        /**
206         * Indique s'il faut utiliser le s�parateur d�cimal pour s�parer la partie
207         * enti�re de la partie fractionnaire. La valeur <code>false</code> indique
208         * que les parties enti�res et fractionnaires doivent �tre �crites ensembles
209         * (par exemple 34867 pour 34.867). La valeur par d�faut est <code>true</code>.
210         */
211        private boolean decimalSeparator=true;
212        
213        /**
214         * Format � utiliser pour �crire les nombres
215         * (degr�s, minutes ou secondes) � l'int�rieur
216         * de l'�criture d'un angle.
217         */
218        private final DecimalFormat numberFormat;
219        
220        /**
221         * Objet � transmetre aux m�thodes <code>DecimalFormat.format</code>.
222         * Ce param�tre existe simplement pour �viter de cr�er cet objet trop
223         * souvent, alors qu'on ne s'y int�resse pas.
224         */
225        private transient FieldPosition dummy = new FieldPosition(0);
226        
227        /**
228         * Restore fields after deserialization.
229         */
230        private void readObject(final ObjectInputStream in)
231            throws IOException, ClassNotFoundException
232        {
233            in.defaultReadObject();
234            dummy = new FieldPosition(0);
235        }
236        
237        /**
238         * Returns the width of the specified field.
239         */
240        private int getWidth(final int index) {
241            switch (index) {
242                case DEGREES_FIELD:  return width0;
243                case MINUTES_FIELD:  return width1;
244                case SECONDS_FIELD:  return width2;
245                default:             return 0; // Must be 0 (important!)
246            }
247        }
248        
249        /**
250         * Set the width for the specified field.
251         * All folowing fields will be set to 0.
252         */
253        private void setWidth(final int index, int width) {
254            switch (index) {
255                case DEGREES_FIELD: width0=width; width=0; // fall through
256                case MINUTES_FIELD: width1=width; width=0; // fall through
257                case SECONDS_FIELD: width2=width;          // fall through
258            }
259        }
260        
261        /**
262         * Returns the suffix for the specified field.
263         */
264        private String getSuffix(final int index) {
265            switch (index) {
266                case  PREFIX_FIELD: return prefix;
267                case DEGREES_FIELD: return suffix0;
268                case MINUTES_FIELD: return suffix1;
269                case SECONDS_FIELD: return suffix2;
270                default:            return null;
271            }
272        }
273        
274        /**
275         * Set the suffix for the specified field. Suffix
276         * for all following fields will be set to their
277         * default value.
278         */
279        private void setSuffix(final int index, String s) {
280            switch (index) {
281                case  PREFIX_FIELD:  prefix=s; s="�";  // fall through
282                case DEGREES_FIELD: suffix0=s; s="'";  // fall through
283                case MINUTES_FIELD: suffix1=s; s="\""; // fall through
284                case SECONDS_FIELD: suffix2=s;         // fall through
285            }
286        }
287        
288        /**
289         * Construct a new <code>AngleFormat</code> using
290         * the current default locale and a default pattern.
291         */
292        public AngleFormat() {
293            this("D�MM.m'");
294        }
295        
296        /**
297         * Construct a new <code>AngleFormat</code> using the
298         * current default locale and the specified pattern.
299         *
300         * @param  pattern Pattern to use for parsing and formatting angle.
301         *         See class description for an explanation of how this pattern work.
302         * @throws IllegalArgumentException If the specified pattern is not legal.
303         */
304        public AngleFormat(final String pattern) throws IllegalArgumentException {
305            this(pattern, new DecimalFormatSymbols());
306        }
307        
308        /**
309         * Construct a new <code>AngleFormat</code>
310         * using the specified pattern and locale.
311         *
312         * @param  pattern Pattern to use for parsing and formatting angle.
313         *         See class description for an explanation of how this pattern work.
314         * @param  locale Locale to use.
315         * @throws IllegalArgumentException If the specified pattern is not legal.
316         */
317        public AngleFormat(final String pattern, final Locale locale) throws IllegalArgumentException {
318            this(pattern, new DecimalFormatSymbols(locale));
319        }
320        
321        /**
322         * Construct a new <code>AngleFormat</code>
323         * using the specified pattern and decimal symbols.
324         *
325         * @param  pattern Pattern to use for parsing and formatting angle.
326         *         See class description for an explanation of how this pattern work.
327         * @param  symbols The symbols to use for parsing and formatting numbers.
328         * @throws IllegalArgumentException If the specified pattern is not legal.
329         */
330        public AngleFormat(final String pattern, final DecimalFormatSymbols symbols) {
331            // NOTE: pour cette routine, il ne faut PAS que DecimalFormat
332            //       reconnaisse la notation exponentielle, parce que �a
333            //       risquerait d'�tre confondu avec le "E" de "Est".
334            numberFormat=new DecimalFormat("#0", symbols);
335            applyPattern(pattern);
336        }
337        
338        /**
339         * Set the pattern to use for parsing and formatting angle.
340         * See class description for an explanation of how patterns work.
341         *
342         * @param  pattern Pattern to use for parsing and formatting angle.
343         * @throws IllegalArgumentException If the specified pattern is not legal.
344         */
345        public synchronized void applyPattern(final String pattern) throws IllegalArgumentException {
346            widthDecimal = 0;
347            decimalSeparator = true;
348            int startPrefix = 0;
349            int symbolIndex = 0;
350            boolean parseFinished = false;
351            final int length = pattern.length();
352            for (int i=0; i<length; i++) {
353                /*
354                 * On examine un � un tous les caract�res du patron en
355                 * sautant ceux qui ne sont pas r�serv�s ("D", "M", "S"
356                 * et leur �quivalents en minuscules). Les caract�res
357                 * non-reserv�s seront m�moris�s comme suffix plus tard.
358                 */
359                final char c = pattern.charAt(i);
360                final char upperCaseC = Character.toUpperCase(c);
361                for (int field=DEGREES_FIELD; field<SYMBOLS.length; field++) {
362                    if (upperCaseC == SYMBOLS[field]) {
363                        /*
364                         * Un caract�re r�serv� a �t� trouv�. V�rifie maintenant
365                         * s'il est valide. Par exemple il serait illegal d'avoir
366                         * comme patron "MM.mm" sans qu'il soit pr�c�d� des degr�s.
367                         * On attend les lettres "D", "M" et "S" dans l'ordre. Si
368                         * le caract�re est en lettres minuscules, il doit �tre le
369                         * m�me que le dernier code (par exemple "DD.mm" est illegal).
370                         */
371                        if (c==upperCaseC) {
372                            symbolIndex++;
373                        }
374                        if (field!=symbolIndex-1 || parseFinished) {
375                            setWidth(DEGREES_FIELD, 1);
376                            setSuffix(PREFIX_FIELD, null);
377                            widthDecimal=0;
378                            decimalSeparator=true;
379                            throw new IllegalArgumentException(Resources.format(ResourceKeys.ERROR_ILLEGAL_ANGLE_PATTERN_$1, pattern));
380                        }
381                        if (c==upperCaseC) {
382                            /*
383                             * M�morise les caract�res qui pr�c�daient ce code comme suffix
384                             * du champs pr�c�dent. Puis on comptera le nombre de fois que le
385                             * code se r�p�te, en m�morisant cette information comme largeur
386                             * de ce champ.
387                             */
388                            setSuffix(field-1, (i>startPrefix) ? pattern.substring(startPrefix, i) : null);
389                            int w=1; while (++i<length && pattern.charAt(i)==c) w++;
390                            setWidth(field, w);
391                        } else {
392                            /*
393                             * Si le caract�re est une minuscule, ce qui le pr�c�dait sera le
394                             * s�parateur d�cimal plut�t qu'un suffix. On comptera le nombre
395                             * d'occurences du caract�res pour obtenir la pr�cision.
396                             */
397                            switch (i-startPrefix) {
398                                case 0: decimalSeparator=false; break;
399                                case 1: if (pattern.charAt(startPrefix)=='.') {
400                                    decimalSeparator=true;
401                                    break;
402                                }
403                                default: throw new IllegalArgumentException(Resources.format(
404                                             ResourceKeys.ERROR_ILLEGAL_ANGLE_PATTERN_$1, pattern));
405                            }
406                            int w=1; while (++i<length && pattern.charAt(i)==c) w++;
407                            widthDecimal=w;
408                            parseFinished=true;
409                        }
410                        startPrefix = i--;
411                        break; // Break 'j' and continue 'i'.
412                    }
413                }
414            }
415            setSuffix(symbolIndex-1, (startPrefix<length) ? pattern.substring(startPrefix) : null);
416        }
417        
418        /**
419         * Returns the pattern used for parsing and formatting angles.
420         * See class description for an explanation of how patterns work.
421         */
422        public synchronized String toPattern() {
423            char symbol='#';
424            final StringBuffer buffer=new StringBuffer();
425            for (int field=DEGREES_FIELD; field<=SYMBOLS.length; field++) {
426                final String previousSuffix=getSuffix(field-1);
427                int w=getWidth(field);
428                if (w>0) {
429                    /*
430                     * Proc�de � l'�criture de la partie enti�re des degr�s,
431                     * minutes ou secondes. Le suffix du champs pr�c�dent
432                     * sera �crit avant les degr�s, minutes ou secondes.
433                     */
434                    if (previousSuffix!=null) {
435                        buffer.append(previousSuffix);
436                    }
437                    symbol=SYMBOLS[field];
438                    do buffer.append(symbol);
439                    while (--w>0);
440                } else {
441                    /*
442                     * Proc�de � l'�criture de la partie d�cimale des
443                     * degr�s, minutes ou secondes. Le suffix du ce
444                     * champs sera �crit apr�s cette partie fractionnaire.
445                     */
446                    w=widthDecimal;
447                    if (w>0) {
448                        if (decimalSeparator) buffer.append('.');
449                        symbol=Character.toLowerCase(symbol);
450                        do buffer.append(symbol);
451                        while (--w>0);
452                    }
453                    if (previousSuffix!=null) {
454                        buffer.append(previousSuffix);
455                    }
456                    break;
457                }
458            }
459            return buffer.toString();
460        }
461        
462        /**
463         * Format an angle. The string will be formatted according
464         * the pattern set in the last call to {@link #applyPattern}.
465         *
466         * @param  angle Angle to format, in degrees.
467         * @return The formatted string.
468         */
469        public final String format(final double angle) {
470            return format(angle, new StringBuffer(), null).toString();
471        }
472        
473        /**
474         * Formats an angle and appends the resulting text to a given string buffer.
475         * The string will be formatted according the pattern set in the last call
476         * to {@link #applyPattern}.
477         *
478         * @param  angle      Angle to format, in degrees.
479         * @param  toAppendTo Where the text is to be appended.
480         * @param  pos        An optional {@link FieldPosition} identifying a field
481         *                    in the formatted text, or <code>null</code> if this
482         *                    information is not wanted. This field position shall
483         *                    be constructed with one of the following constants:
484         *                    {@link #DEGREES_FIELD},
485         *                    {@link #MINUTES_FIELD},
486         *                    {@link #SECONDS_FIELD} or
487         *                    {@link #HEMISPHERE_FIELD}.
488         *
489         * @return The string buffer passed in as <code>toAppendTo</code>, with formatted text appended.
490         */
491        public synchronized StringBuffer format(final double angle,
492                                                StringBuffer toAppendTo,
493                                                final FieldPosition pos)
494        {
495            double degrees = angle;
496            /*
497             * Calcule � l'avance les minutes et les secondes. Si les minutes et secondes
498             * ne doivent pas �tre �crits, on m�morisera NaN. Notez que pour extraire les
499             * parties enti�res, on utilise (int) au lieu de 'Math.floor' car (int) arrondie
500             * vers 0 (ce qui est le comportement souhait�) alors que 'floor' arrondie vers
501             * l'entier inf�rieur.
502             */
503            double minutes  = Double.NaN;
504            double secondes = Double.NaN;
505            if (width1!=0 && !Double.isNaN(angle)) {
506                int tmp = (int) degrees; // Arrondie vers 0 m�me si n�gatif.
507                minutes = Math.abs(degrees-tmp)*60;
508                degrees = tmp;
509                if (minutes<0 || minutes>60) {
510                    // Erreur d'arrondissement (parce que l'angle est trop �lev�)
511                    throw new IllegalArgumentException(Resources.format(ResourceKeys.ERROR_ANGLE_OVERFLOW_$1, new Double(angle)));
512                }
513                if (width2 != 0) {
514                    tmp      = (int) minutes; // Arrondie vers 0 m�me si n�gatif.
515                    secondes = (minutes-tmp)*60;
516                    minutes  = tmp;
517                    if (secondes<0 || secondes>60) {
518                        // Erreur d'arrondissement (parce que l'angle est trop �lev�)
519                        throw new IllegalArgumentException(Resources.format(ResourceKeys.ERROR_ANGLE_OVERFLOW_$1, new Double(angle)));
520                    }
521                    /*
522                     * On applique maintenant une correction qui tiendra
523                     * compte des probl�mes d'arrondissements.
524                     */
525                    final double puissance=XMath.pow10(widthDecimal);
526                    secondes=Math.rint(secondes*puissance)/puissance;
527                    tmp = (int) (secondes/60);
528                    secondes -= 60*tmp;
529                    minutes += tmp;
530                } else {
531                    final double puissance=XMath.pow10(widthDecimal);
532                    minutes = Math.rint(minutes*puissance)/puissance;
533                }
534                tmp = (int) (minutes/60); // Arrondie vers 0 m�me si n�gatif.
535                minutes -= 60*tmp;
536                degrees += tmp;
537            }
538            /*
539             * Les variables 'degr�s', 'minutes' et 'secondes' contiennent
540             * maintenant les valeurs des champs � �crire, en principe �pur�s
541             * des probl�mes d'arrondissements. Proc�de maintenant � l'�criture
542             * de l'angle.
543             */
544            if (prefix != null) {
545                toAppendTo.append(prefix);
546            }
547            final int field;
548            if (pos != null) {
549                field = pos.getField();
550                pos.setBeginIndex(0);
551                pos.setEndIndex(0);
552            } else {
553                field=PREFIX_FIELD;
554            }
555            toAppendTo = formatField(degrees, toAppendTo,
556                                     field==DEGREES_FIELD ? pos : null,
557                                     width0, width1==0, suffix0);
558            if (!Double.isNaN(minutes)) {
559                toAppendTo=formatField(minutes, toAppendTo,
560                                       field==MINUTES_FIELD ? pos : null,
561                                       width1, width2==0, suffix1);
562            }
563            if (!Double.isNaN(secondes)) {
564                toAppendTo=formatField(secondes, toAppendTo,
565                                       field==SECONDS_FIELD ? pos : null,
566                                       width2, true, suffix2);
567            }
568            return toAppendTo;
569        }
570        
571        /**
572         * Proc�de � l'�criture d'un champ de l'angle.
573         *
574         * @param value Valeur � �crire.
575         * @param toAppendTo Buffer dans lequel �crire le champs.
576         * @param pos Objet dans lequel m�moriser les index des premiers
577         *        et derniers caract�res �crits, ou <code>null</code>
578         *        pour ne pas m�moriser ces index.
579         * @param w Nombre de minimal caract�res de la partie enti�re.
580         * @param last <code>true</code> si ce champs est le dernier,
581         *        et qu'il faut donc �crire la partie d�cimale.
582         * @param s Suffix � �crire apr�s le nombre (peut �tre nul).
583         */
584        private StringBuffer formatField(double value,
585                                         StringBuffer toAppendTo, final FieldPosition pos,
586                                         final int w, final boolean last, final String s)
587        {
588            final int startPosition=toAppendTo.length();
589            if (!last) {
590                numberFormat.setMinimumIntegerDigits(w);
591                numberFormat.setMaximumFractionDigits(0);
592                toAppendTo = numberFormat.format(value, toAppendTo, dummy);
593            } else if (decimalSeparator) {
594                numberFormat.setMinimumIntegerDigits(w);
595                numberFormat.setMinimumFractionDigits(widthDecimal);
596                numberFormat.setMaximumFractionDigits(widthDecimal);
597                toAppendTo = numberFormat.format(value, toAppendTo, dummy);
598            } else {
599                value *= XMath.pow10(widthDecimal);
600                numberFormat.setMaximumFractionDigits(0);
601                numberFormat.setMinimumIntegerDigits(w+widthDecimal);
602                toAppendTo = numberFormat.format(value, toAppendTo, dummy);
603            }
604            if (s!=null) {
605                toAppendTo.append(s);
606            }
607            if (pos!=null) {
608                pos.setBeginIndex(startPosition);
609                pos.setEndIndex(toAppendTo.length()-1);
610            }
611            return toAppendTo;
612        }
613        
614        /**
615         * Formats an angle, a latitude or a longitude and appends the resulting text
616         * to a given string buffer. The string will be formatted according the pattern
617         * set in the last call to {@link #applyPattern}. The argument <code>obj</code>
618         * shall be an {@link Angle} object or one of its derived class ({@link Latitude},
619         * {@link Longitude}). If <code>obj</code> is a {@link Latitude} object, then a
620         * symbol "N" or "S" will be appended to the end of the string (the symbol will
621         * be choosen according the angle's sign). Otherwise, if <code>obj</code> is a
622         * {@link Longitude} object, then a symbol "E" or "W" will be appended to the
623         * end of the string. Otherwise, no hemisphere symbol will be appended.
624         * <br><br>
625         * Strictly speaking, formatting ordinary numbers is not the
626         * <code>AngleFormat</code>'s job. Nevertheless, this method
627         * accept {@link Number} objects. This capability is provided
628         * only as a convenient way to format altitude numbers together
629         * with longitude and latitude angles.
630         *
631         * @param  obj        {@link Angle} or {@link Number} object to format.
632         * @param  toAppendTo Where the text is to be appended.
633         * @param  pos        An optional {@link FieldPosition} identifying a field
634         *                    in the formatted text, or <code>null</code> if this
635         *                    information is not wanted. This field position shall
636         *                    be constructed with one of the following constants:
637         *                    {@link #DEGREES_FIELD},
638         *                    {@link #MINUTES_FIELD},
639         *                    {@link #SECONDS_FIELD} or
640         *                    {@link #HEMISPHERE_FIELD}.
641         *
642         * @return The string buffer passed in as <code>toAppendTo</code>, with
643         *         formatted text appended.
644         * @throws IllegalArgumentException if <code>obj</code> if not an object
645         *         of class {@link Angle} or {@link Number}.
646         */
647        public synchronized StringBuffer format(final Object obj,
648                                                StringBuffer toAppendTo,
649                                                final FieldPosition pos)
650            throws IllegalArgumentException
651        {
652            if (obj instanceof Latitude) {
653                return format(((Latitude) obj).degrees(), toAppendTo, pos, NORTH, SOUTH);
654            }
655            if (obj instanceof Longitude) {
656                return format(((Longitude) obj).degrees(), toAppendTo, pos, EAST, WEST);
657            }
658            if (obj instanceof Angle) {
659                return format(((Angle) obj).degrees(), toAppendTo, pos);
660            }
661            if (obj instanceof Number) {
662                numberFormat.setMinimumIntegerDigits(1);
663                numberFormat.setMinimumFractionDigits(0);
664                numberFormat.setMaximumFractionDigits(2);
665                return numberFormat.format(obj, toAppendTo, (pos!=null) ? pos : dummy);
666            }
667            throw new IllegalArgumentException(Resources.format(
668                                               ResourceKeys.ERROR_NOT_AN_ANGLE_OBJECT_$1,
669                                               Utilities.getShortClassName(obj)));
670        }
671        
672        /**
673         * Proc�de � l'�criture d'un angle, d'une latitude ou d'une longitude.
674         *
675         * @param  type       Type de l'angle ou du nombre:
676         *                    {@link #LONGITUDE},
677         *                    {@link #LATITUDE} ou
678         *                    {@link #ALTITUDE}.
679         * @param  toAppendTo Buffer dans lequel �crire l'angle.
680         * @param  pos        En entr�, le code du champs dont on d�sire les index
681         *                    ({@link #DEGREES_FIELD},
682         *                     {@link #MINUTES_FIELD},
683         *                     {@link #SECONDS_FIELD} ou
684         *                     {@link #HEMISPHERE_FIELD}).
685         *                    En sortie, les index du champs demand�. Ce param�tre
686         *                    peut �tre nul si cette information n'est pas d�sir�e.
687         *
688         * @return Le buffer <code>toAppendTo</code> par commodit�.
689         */
690        synchronized StringBuffer format(final double number, final int type,
691                                         StringBuffer toAppendTo,
692                                         final FieldPosition pos)
693        {
694            switch (type) {
695                default:        throw new IllegalArgumentException(Integer.toString(type)); // Should not happen.
696                case LATITUDE:  return format(number, toAppendTo, pos, NORTH, SOUTH);
697                case LONGITUDE: return format(number, toAppendTo, pos, EAST,  WEST );
698                case ALTITUDE: {
699                    numberFormat.setMinimumIntegerDigits(1);
700                    numberFormat.setMinimumFractionDigits(0);
701                    numberFormat.setMaximumFractionDigits(2);
702                    return numberFormat.format(number, toAppendTo, (pos!=null) ? pos : dummy);
703                }
704            }
705        }
706        
707        /**
708         * Proc�de � l'�criture d'un angle suivit d'un suffix 'N','S','E' ou 'W'.
709         * L'angle sera format� en utilisant comme mod�le le patron sp�cifi� lors
710         * du dernier appel de la m�thode {@link #applyPattern}.
711         *
712         * @param  angle      Angle � �crire, en degr�s.
713         * @param  toAppendTo Buffer dans lequel �crire l'angle.
714         * @param  pos        En entr�, le code du champs dont on d�sire les index
715         *                    ({@link #DEGREES_FIELD},
716         *                     {@link #MINUTES_FIELD},
717         *                     {@link #SECONDS_FIELD} ou
718         *                     {@link #HEMISPHERE_FIELD}).
719         *                    En sortie, les index du champs demand�. Ce param�tre
720         *                    peut �tre nul si cette information n'est pas d�sir�e.
721         * @param north       Caract�res � �crire si l'angle est positif ou nul.
722         * @param south       Caract�res � �crire si l'angle est n�gatif.
723         *
724         * @return Le buffer <code>toAppendTo</code> par commodit�.
725         */
726        private StringBuffer format(final double angle,
727                                    StringBuffer toAppendTo,
728                                    final FieldPosition pos,
729                                    final char north, final char south)
730        {
731            toAppendTo = format(Math.abs(angle), toAppendTo, pos);
732            final int start = toAppendTo.length();
733            toAppendTo.append(angle<0 ? south : north);
734            if (pos!=null && pos.getField()==HEMISPHERE_FIELD) {
735                pos.setBeginIndex(start);
736                pos.setEndIndex(toAppendTo.length()-1);
737            }
738            return toAppendTo;
739        }
740        
741        /**
742         * Ignore le suffix d'un nombre. Cette m�thode est appell�e par la m�thode
743         * {@link #parse} pour savoir quel champs il vient de lire. Par exemple si
744         * l'on vient de lire les degr�s dans "48�12'", alors cette m�thode extraira
745         * le "�" et retournera 0 pour indiquer que l'on vient de lire des degr�s.
746         *
747         * Cette m�thode se chargera d'ignorer les espaces qui pr�c�dent le suffix.
748         * Elle tentera ensuite de d'abord interpr�ter le suffix selon les symboles
749         * du patron (sp�cifi� avec {@link #applyPattern}. Si le suffix n'a pas �t�
750         * reconnus, elle tentera ensuite de le comparer aux symboles standards
751         * (� ' ").
752         *
753         * @param  source Cha�ne dans laquelle on doit sauter le suffix.
754         * @param  pos En entr�, l'index du premier caract�re � consid�rer dans la
755         *         cha�ne <code>pos</code>. En sortie, l'index du premier caract�re
756         *         suivant le suffix (c'est-�-dire index � partir d'o� continuer la
757         *         lecture apr�s l'appel de cette m�thode). Si le suffix n'a pas �t�
758         *         reconnu, alors cette m�thode retourne par convention <code>SYMBOLS.length</code>.
759         * @param  field Champs � v�rifier de pr�f�rences. Par exemple la valeur 1 signifie que les
760         *         suffix des minutes et des secondes devront �tre v�rifi�s avant celui des degr�s.
761         * @return Le num�ro du champs correspondant au suffix qui vient d'�tre extrait:
762         *         -1 pour le pr�fix de l'angle, 0 pour le suffix des degr�s, 1 pour le
763         *         suffix des minutes et 2 pour le suffix des secondes. Si le texte n'a
764         *         pas �t� reconnu, retourne <code>SYMBOLS.length</code>.
765         */
766        private int skipSuffix(final String source, final ParsePosition pos, int field) {
767            /*
768             * Essaie d'abord de sauter les suffix qui
769             * avaient �t� sp�cifi�s dans le patron.
770             */
771            final int length=source.length();
772            int start=pos.getIndex();
773            for (int j=SYMBOLS.length; j>=0; j--) { // C'est bien j>=0 et non j>0.
774                int index=start;
775                final String toSkip=getSuffix(field);
776                if (toSkip!=null) {
777                    final int toSkipLength=toSkip.length();
778                    do {
779                        if (source.regionMatches(index, toSkip, 0, toSkipLength)) {
780                            pos.setIndex(index+toSkipLength);
781                            return field;
782                        }
783                    }
784                    while (index<length && Character.isSpaceChar(source.charAt(index++)));
785                }
786                if (++field >= SYMBOLS.length) field=-1;
787            }
788            /*
789             * Le texte trouv� ne correspondant � aucun suffix du patron,
790             * essaie maintenant de sauter un des suffix standards (apr�s
791             * avoir ignor� les espaces qui le pr�c�daient).
792             */
793            char c;
794            do {
795                if (start>=length) {
796                    return SYMBOLS.length;
797                }
798            }
799            while (Character.isSpaceChar(c=source.charAt(start++)));
800            switch (c) {
801                case '�' : pos.setIndex(start); return DEGREES_FIELD;
802                case '\'': pos.setIndex(start); return MINUTES_FIELD;
803                case '"' : pos.setIndex(start); return SECONDS_FIELD;
804                default  : return SYMBOLS.length; // Unknow field.
805            }
806        }
807        
808        /**
809         * Parse a string as an angle. This method can parse an angle even if it
810         * doesn't comply exactly to the expected pattern. For example, this method
811         * will parse correctly string "<code>48�12.34'</code>" even if the expected
812         * pattern was "<code>DDMM.mm</code>" (i.e. the string should have been
813         * "<code>4812.34</code>"). Spaces between degrees, minutes and secondes
814         * are ignored. If the string ends with an hemisphere symbol "N" or "S",
815         * then this method returns an object of class {@link Latitude}. Otherwise,
816         * if the string ends with an hemisphere symbol "E" or "W", then this method
817         * returns an object of class {@link Longitude}. Otherwise, this method
818         * returns an object of class {@link Angle}.
819         *
820         * @param source A String whose beginning should be parsed.
821         * @param pos    Position where to start parsing.
822         * @return       The parsed string as an {@link Angle}, {@link Latitude}
823         *               or {@link Longitude} object.
824         */
825        public Angle parse(final String source, final ParsePosition pos) {
826            return parse(source, pos, false);
827        }
828        
829        /**
830         * Interpr�te une cha�ne de caract�res repr�sentant un angle. Les r�gles
831         * d'interpr�tation de cette m�thode sont assez souples. Par exemple cettte
832         * m�thode interpr�tera correctement la cha�ne "48�12.34'" m�me si le patron
833         * attendu �tait "DDMM.mm" (c'est-�-dire que la cha�ne aurait du �tre "4812.34").
834         * Les espaces entre les degr�s, minutes et secondes sont accept�s. Si l'angle
835         * est suivit d'un symbole "N" ou "S", alors l'objet retourn� sera de la classe
836         * {@link Latitude}. S'il est plutot suivit d'un symbole "E" ou "W", alors l'objet
837         * retourn� sera de la classe {@link Longitude}. Sinon, il sera de la classe
838         * {@link Angle}.
839         *
840         * @param source           Cha�ne de caract�res � lire.
841         * @param pos              Position � partir d'o� interpr�ter la cha�ne.
842         * @param spaceAsSeparator Indique si l'espace est accept� comme s�parateur
843         *                         � l'int�rieur d'un angle. La valeur <code>true</code>
844         *                         fait que l'angle "45 30" sera interpr�t� comme "45�30".
845         * @return L'angle lu.
846         */
847        private synchronized Angle parse(final String source,
848                                         final ParsePosition pos,
849                                         final boolean spaceAsSeparator)
850        {
851            double degrees   = Double.NaN;
852            double minutes  = Double.NaN;
853            double secondes = Double.NaN;
854            final int length=source.length();
855            ///////////////////////////////////////////////////////////////////////////////
856            // BLOC A: Analyse la cha�ne de caract�res 'source' et affecte aux variables //
857            //         'degr�s', 'minutes' et 'secondes' les valeurs appropri�es.        //
858            //         Les premi�res accolades ne servent qu'� garder locales            //
859            //         les variables sans int�r�t une fois la lecture termin�e.          //
860            ///////////////////////////////////////////////////////////////////////////////
861            {
862                /*
863                 * Extrait le pr�fix, s'il y en avait un. Si on tombe sur un symbole des
864                 * degr�s, minutes ou secondes alors qu'on n'a pas encore lu de nombre,
865                 * on consid�rera que la lecture a �chou�e.
866                 */
867                final int indexStart=pos.getIndex();
868                int index=skipSuffix(source, pos, PREFIX_FIELD);
869                if (index>=0 && index<SYMBOLS.length) {
870                    pos.setErrorIndex(indexStart);
871                    pos.setIndex(indexStart);
872                    return null;
873                }
874                /*
875                 * Saute les espaces blancs qui
876                 * pr�c�dent le champs des degr�s.
877                 */
878                index=pos.getIndex();
879                while (index<length && Character.isSpaceChar(source.charAt(index))) index++;
880                pos.setIndex(index);
881                /*
882                 * Lit les degr�s. Notez que si aucun s�parateur ne s�parait les degr�s
883                 * des minutes des secondes, alors cette lecture pourra inclure plusieurs
884                 * champs (exemple: "DDDMMmmm"). La s�paration sera faite plus tard.
885                 */
886                Number fieldObject=numberFormat.parse(source, pos);
887                if (fieldObject==null) {
888                    pos.setIndex(indexStart);
889                    if (pos.getErrorIndex()<indexStart) {
890                        pos.setErrorIndex(index);
891                    }
892                    return null;
893                }
894                degrees=fieldObject.doubleValue();
895                int indexEndField=pos.getIndex();
896                boolean swapDM=true;
897    BigBoss:    switch (skipSuffix(source, pos, DEGREES_FIELD)) {
898                    /* ----------------------------------------------
899                     * ANALYSE DU SYMBOLE SUIVANT LES PR�SUM�S DEGR�S
900                     * ----------------------------------------------
901                     * Les degr�s �taient suivit du pr�fix d'un autre angle. Le pr�fix sera donc
902                     * retourn� dans le buffer pour un �ventuel traitement par le prochain appel
903                     * � la m�thode 'parse' et on n'ira pas plus loin dans l'analyse de la cha�ne.
904                     */
905                    case PREFIX_FIELD: {
906                        pos.setIndex(indexEndField);
907                        break BigBoss;
908                    }
909                    /* ----------------------------------------------
910                     * ANALYSE DU SYMBOLE SUIVANT LES PR�SUM�S DEGR�S
911                     * ----------------------------------------------
912                     * On a trouv� le symbole des secondes au lieu de celui des degr�s. On fait
913                     * la correction dans les variables 'degr�s' et 'secondes' et on consid�re
914                     * que la lecture est termin�e.
915                     */
916                    case SECONDS_FIELD: {
917                        secondes = degrees;
918                        degrees = Double.NaN;
919                        break BigBoss;
920                    }
921                    /* ----------------------------------------------
922                     * ANALYSE DU SYMBOLE SUIVANT LES PR�SUM�S DEGR�S
923                     * ----------------------------------------------
924                     * Aucun symbole ne suit les degr�s. Des minutes sont-elles attendues?
925                     * Si oui, on fera comme si le symbole des degr�s avait �t� l�. Sinon,
926                     * on consid�rera que la lecture est termin�e.
927                     */
928                    default: {
929                        if (width1==0)         break BigBoss;
930                        if (!spaceAsSeparator) break BigBoss;
931                        // fall through
932                    }
933                    /* ----------------------------------------------
934                     * ANALYSE DU SYMBOLE SUIVANT LES PR�SUM�S DEGR�S
935                     * ----------------------------------------------
936                     * Un symbole des degr�s a �t� explicitement trouv�. Les degr�s sont peut-�tre
937                     * suivit des minutes. On proc�dera donc � la lecture du prochain nombre, puis
938                     * � l'analyse du symbole qui le suit.
939                     */
940                    case DEGREES_FIELD: {
941                        final int indexStartField = index = pos.getIndex();
942                        while (index<length && Character.isSpaceChar(source.charAt(index))) {
943                            index++;
944                        }
945                        if (!spaceAsSeparator && index!=indexStartField) {
946                            break BigBoss;
947                        }
948                        pos.setIndex(index);
949                        fieldObject=numberFormat.parse(source, pos);
950                        if (fieldObject==null) {
951                            pos.setIndex(indexStartField);
952                            break BigBoss;
953                        }
954                        indexEndField = pos.getIndex();
955                        minutes = fieldObject.doubleValue();
956                        switch (skipSuffix(source, pos, (width1!=0) ? MINUTES_FIELD : PREFIX_FIELD)) {
957                            /* ------------------------------------------------
958                             * ANALYSE DU SYMBOLE SUIVANT LES PR�SUM�ES MINUTES
959                             * ------------------------------------------------
960                             * Le symbole trouv� est bel et bien celui des minutes.
961                             * On continuera le bloc pour tenter de lire les secondes.
962                             */
963                            case MINUTES_FIELD: {
964                                break; // continue outer switch
965                            }
966                            /* ------------------------------------------------
967                             * ANALYSE DU SYMBOLE SUIVANT LES PR�SUM�ES MINUTES
968                             * ------------------------------------------------
969                             * Un symbole des secondes a �t� trouv� au lieu du symbole des minutes
970                             * attendu. On fera la modification dans les variables 'secondes' et
971                             * 'minutes' et on consid�rera la lecture termin�e.
972                             */
973                            case SECONDS_FIELD: {
974                                secondes = minutes;
975                                minutes = Double.NaN;
976                                break BigBoss;
977                            }
978                            /* ------------------------------------------------
979                             * ANALYSE DU SYMBOLE SUIVANT LES PR�SUM�ES MINUTES
980                             * ------------------------------------------------
981                             * Aucun symbole n'a �t� trouv�. Les minutes �taient-elles attendues?
982                             * Si oui, on les acceptera et on tentera de lire les secondes. Si non,
983                             * on retourne le texte lu dans le buffer et on termine la lecture.
984                             */
985                            default: {
986                                if (width1!=0) break; // Continue outer switch
987                                // fall through
988                            }
989                            /* ------------------------------------------------
990                             * ANALYSE DU SYMBOLE SUIVANT LES PR�SUM�ES MINUTES
991                             * ------------------------------------------------
992                             * Au lieu des minutes, le symbole lu est celui des degr�s. On consid�re
993                             * qu'il appartient au prochain angle. On retournera donc le texte lu dans
994                             * le buffer et on terminera la lecture.
995                             */
996                            case DEGREES_FIELD: {
997                                pos.setIndex(indexStartField);
998                                minutes=Double.NaN;
999                                break BigBoss;
1000                            }
1001                            /* ------------------------------------------------
1002                             * ANALYSE DU SYMBOLE SUIVANT LES PR�SUM�ES MINUTES
1003                             * ------------------------------------------------
1004                             * Apr�s les minutes (qu'on accepte), on a trouv� le pr�fix du prochain
1005                             * angle � lire. On retourne ce pr�fix dans le buffer et on consid�re la
1006                             * lecture termin�e.
1007                             */
1008                            case PREFIX_FIELD: {
1009                                pos.setIndex(indexEndField);
1010                                break BigBoss;
1011                            }
1012                        }
1013                        swapDM=false;
1014                        // fall through
1015                    }
1016                    /* ----------------------------------------------
1017                     * ANALYSE DU SYMBOLE SUIVANT LES PR�SUM�S DEGR�S
1018                     * ----------------------------------------------
1019                     * Un symbole des minutes a �t� trouv� au lieu du symbole des degr�s attendu.
1020                     * On fera donc la modification dans les variables 'degr�s' et 'minutes'. Ces
1021                     * minutes sont peut-�tre suivies des secondes. On tentera donc de lire le
1022                     * prochain nombre.
1023                     */
1024                    case MINUTES_FIELD: {
1025                        if (swapDM) {
1026                            minutes = degrees;
1027                            degrees = Double.NaN;
1028                        }
1029                        final int indexStartField = index = pos.getIndex();
1030                        while (index<length && Character.isSpaceChar(source.charAt(index))) {
1031                            index++;
1032                        }
1033                        if (!spaceAsSeparator && index!=indexStartField) {
1034                            break BigBoss;
1035                        }
1036                        pos.setIndex(index);
1037                        fieldObject=numberFormat.parse(source, pos);
1038                        if (fieldObject==null) {
1039                            pos.setIndex(indexStartField);
1040                            break;
1041                        }
1042                        indexEndField = pos.getIndex();
1043                        secondes = fieldObject.doubleValue();
1044                        switch (skipSuffix(source, pos, (width2!=0) ? MINUTES_FIELD : PREFIX_FIELD)) {
1045                            /* -------------------------------------------------
1046                             * ANALYSE DU SYMBOLE SUIVANT LES PR�SUM�ES SECONDES
1047                             * -------------------------------------------------
1048                             * Un symbole des secondes explicite a �t� trouv�e.
1049                             * La lecture est donc termin�e.
1050                             */
1051                            case SECONDS_FIELD: {
1052                                break;
1053                            }
1054                            /* -------------------------------------------------
1055                             * ANALYSE DU SYMBOLE SUIVANT LES PR�SUM�ES SECONDES
1056                             * -------------------------------------------------
1057                             * Aucun symbole n'a �t� trouv�e. Attendait-on des secondes? Si oui, les
1058                             * secondes seront accept�es. Sinon, elles seront retourn�es au buffer.
1059                             */
1060                            default: {
1061                                if (width2!=0) break;
1062                                // fall through
1063                            }
1064                            /* -------------------------------------------------
1065                             * ANALYSE DU SYMBOLE SUIVANT LES PR�SUM�ES SECONDES
1066                             * -------------------------------------------------
1067                             * Au lieu des degr�s, on a trouv� un symbole des minutes ou des
1068                             * secondes. On renvoie donc le nombre et son symbole dans le buffer.
1069                             */
1070                            case MINUTES_FIELD:
1071                            case DEGREES_FIELD: {
1072                                pos.setIndex(indexStartField);
1073                                secondes=Double.NaN;
1074                                break;
1075                            }
1076                            /* -------------------------------------------------
1077                             * ANALYSE DU SYMBOLE SUIVANT LES PR�SUM�ES SECONDES
1078                             * -------------------------------------------------
1079                             * Apr�s les secondes (qu'on accepte), on a trouv� le pr�fix du prochain
1080                             * angle � lire. On retourne ce pr�fix dans le buffer et on consid�re la
1081                             * lecture termin�e.
1082                             */
1083                            case PREFIX_FIELD: {
1084                                pos.setIndex(indexEndField);
1085                                break BigBoss;
1086                            }
1087                        }
1088                        break;
1089                    }
1090                }
1091            }
1092            ////////////////////////////////////////////////////////////////////
1093            // BLOC B: Prend en compte l'�ventualit� ou le s�parateur d�cimal //
1094            //         aurrait �t� absent, puis calcule l'angle en degr�s.    //
1095            ////////////////////////////////////////////////////////////////////
1096            if (minutes<0) {
1097                secondes = -secondes;
1098            }
1099            if (degrees<0) {
1100                minutes = -minutes;
1101                secondes = -secondes;
1102            }
1103            if (!decimalSeparator) {
1104                final double facteur=XMath.pow10(widthDecimal);
1105                if (width2!=0) {
1106                    if (suffix1==null && Double.isNaN(secondes)) {
1107                        if (suffix0==null && Double.isNaN(minutes)) {
1108                            degrees /= facteur;
1109                        } else {
1110                            minutes /= facteur;
1111                        }
1112                    } else {
1113                        secondes /= facteur;
1114                    }
1115                } else if (Double.isNaN(secondes)) {
1116                    if (width1!=0) {
1117                        if (suffix0==null && Double.isNaN(minutes)) {
1118                            degrees /= facteur;
1119                        } else {
1120                            minutes /= facteur;
1121                        }
1122                    } else if (Double.isNaN(minutes)) {
1123                        degrees /= facteur;
1124                    }
1125                }
1126            }
1127            /*
1128             * S'il n'y a rien qui permet de s�parer les degr�s des minutes (par exemple si
1129             * le patron est "DDDMMmmm"), alors la variable 'degr�s' englobe � la fois les
1130             * degr�s, les minutes et d'�ventuelles secondes. On applique une correction ici.
1131             */
1132            if (suffix1==null && width2!=0 && Double.isNaN(secondes)) {
1133                double facteur = XMath.pow10(width2);
1134                if (suffix0==null && width1!=0 && Double.isNaN(minutes)) {
1135                    ///////////////////
1136                    //// DDDMMSS.s ////
1137                    ///////////////////
1138                    secondes = degrees;
1139                    minutes  = (int) (degrees/facteur); // Arrondie vers 0
1140                    secondes -= minutes*facteur;
1141                    facteur  = XMath.pow10(width1);
1142                    degrees   = (int) (minutes/facteur); // Arrondie vers 0
1143                    minutes -= degrees*facteur;
1144                } else {
1145                    ////////////////////
1146                    //// DDD�MMSS.s ////
1147                    ////////////////////
1148                    secondes = minutes;
1149                    minutes = (int) (minutes/facteur); // Arrondie vers 0
1150                    secondes -= minutes*facteur;
1151                }
1152            } else if (suffix0==null && width1!=0 && Double.isNaN(minutes)) {
1153                /////////////////
1154                //// DDDMM.m ////
1155                /////////////////
1156                final double facteur = XMath.pow10(width1);
1157                minutes = degrees;
1158                degrees = (int) (degrees/facteur); // Arrondie vers 0
1159                minutes -= degrees*facteur;
1160            }
1161            pos.setErrorIndex(-1);
1162            if ( Double.isNaN(degrees))   degrees=0;
1163            if (!Double.isNaN(minutes))  degrees += minutes/60;
1164            if (!Double.isNaN(secondes)) degrees += secondes/3600;
1165            /////////////////////////////////////////////////////
1166            // BLOC C: V�rifie maintenant si l'angle ne serait //
1167            //         pas suivit d'un symbole N, S, E ou W.   //
1168            /////////////////////////////////////////////////////
1169            for (int index=pos.getIndex(); index<length; index++) {
1170                final char c=source.charAt(index);
1171                switch (Character.toUpperCase(c)) {
1172                    case NORTH: pos.setIndex(index+1); return new Latitude( degrees);
1173                    case SOUTH: pos.setIndex(index+1); return new Latitude(-degrees);
1174                    case EAST : pos.setIndex(index+1); return new Longitude( degrees);
1175                    case WEST : pos.setIndex(index+1); return new Longitude(-degrees);
1176                }
1177                if (!Character.isSpaceChar(c)) {
1178                    break;
1179                }
1180            }
1181            return new Angle(degrees);
1182        }
1183        
1184        /**
1185         * Parse a string as an angle.
1186         *
1187         * @param  source The string to parse.
1188         * @return The parsed string as an {@link Angle}, {@link Latitude}
1189         *         or {@link Longitude} object.
1190         * @throws ParseException if the string has not been fully parsed.
1191         */
1192        public Angle parse(final String source) throws ParseException {
1193            final ParsePosition pos = new ParsePosition(0);
1194            final Angle         ang = parse(source, pos, true);
1195            checkComplete(source, pos, false);
1196            return ang;
1197        }
1198        
1199        /**
1200         * Parse a substring as an angle. Default implementation invokes
1201         * {@link #parse(String, ParsePosition)}.
1202         *
1203         * @param source A String whose beginning should be parsed.
1204         * @param pos    Position where to start parsing.
1205         * @return       The parsed string as an {@link Angle},
1206         *               {@link Latitude} or {@link Longitude} object.
1207         */
1208        public Object parseObject(final String source, final ParsePosition pos) {
1209            return parse(source, pos);
1210        }
1211        
1212        /**
1213         * Parse a string as an object. Default implementation invokes
1214         * {@link #parse(String)}.
1215         *
1216         * @param  source The string to parse.
1217         * @return The parsed string as an {@link Angle}, {@link Latitude} or
1218         *        {@link Longitude} object.
1219         * @throws ParseException if the string has not been fully parsed.
1220         */
1221        public Object parseObject(final String source) throws ParseException {
1222            return parse(source);
1223        }
1224        
1225        /**
1226         * Interpr�te une cha�ne de caract�res qui devrait repr�senter un nombre.
1227         * Cette m�thode est utile pour lire une altitude apr�s les angles.
1228         *
1229         * @param  source Cha�ne de caract�res � interpr�ter.
1230         * @param  pos    Position � partir d'o� commencer l'interpr�tation
1231         *                de la cha�ne <code>source</code>.
1232         * @return Le nombre lu comme objet {@link Number}.
1233         */
1234        final Number parseNumber(final String source, final ParsePosition pos) {
1235            return numberFormat.parse(source, pos);
1236        }
1237        
1238        /**
1239         * V�rifie si l'interpr�tation d'une cha�ne de caract�res a �t� compl�te.
1240         * Si ce n'�tait pas le cas, lance une exception avec un message d'erreur
1241         * soulignant les caract�res probl�matiques.
1242         *
1243         * @param  source Cha�ne de caract�res qui �tait � interpr�ter.
1244         * @param  pos Position � laquelle s'est termin�e l'interpr�tation de la
1245         *         cha�ne <code>source</code>.
1246         * @param  isCoordinate <code>false</code> si on interpr�tait un angle,
1247         *         ou <code>true</code> si on interpr�tait une coordonn�e.
1248         * @throws ParseException Si la cha�ne <code>source</code> n'a pas �t�
1249         *         interpr�t�e dans sa totalit�.
1250         */
1251        static void checkComplete(final String source,
1252                                  final ParsePosition pos,
1253                                  final boolean isCoordinate)
1254            throws ParseException
1255        {
1256            final int length=source.length();
1257            final int origin=pos.getIndex();
1258            for (int index=origin; index<length; index++) {
1259                if (!Character.isWhitespace(source.charAt(index))) {
1260                    index=pos.getErrorIndex(); if (index<0) index=origin;
1261                    int lower=index;
1262                    while (lower<length && Character.isWhitespace(source.charAt(lower))) {
1263                        lower++;
1264                    }
1265                    int upper=lower;
1266                    while (upper<length && !Character.isWhitespace(source.charAt(upper))) {
1267                        upper++;
1268                    }
1269                    throw new ParseException(Resources.format(
1270                                ResourceKeys.ERROR_PARSE_ANGLE_EXCEPTION_$2, source,
1271                                source.substring(lower, Math.min(lower+10, upper))), index);
1272                }
1273            }
1274        }
1275        
1276        /**
1277         * Returns a "hash value" for this object.
1278         */
1279        public synchronized int hashCode() {
1280            int c = 78236951;
1281            if (decimalSeparator) c^= 0xFF;
1282            if (prefix  !=null)   c^=         prefix.hashCode();
1283            if (suffix0 !=null)   c = c*37 + suffix0.hashCode();
1284            if (suffix1 !=null)   c^= c*37 + suffix1.hashCode();
1285            if (suffix2 !=null)   c^= c*37 + suffix2.hashCode();
1286            return c ^ (((((width0 << 8) ^ width1) << 8) ^ width2) << 8) ^ widthDecimal;
1287        }
1288        
1289        /**
1290         * Compare this format with the specified object for equality.
1291         */
1292        public synchronized boolean equals(final Object obj) {
1293            // On ne peut pas synchroniser "obj" si on ne veut
1294            // pas risquer un "deadlock". Voir RFE #4210659.
1295            if (obj==this) {
1296                return true;
1297            }
1298            if (obj!=null && getClass().equals(obj.getClass())) {
1299                final  AngleFormat cast = (AngleFormat) obj;
1300                return width0           == cast.width0            &&
1301                       width1           == cast.width1            &&
1302                       width2           == cast.width2            &&
1303                       widthDecimal     == cast.widthDecimal      &&
1304                       decimalSeparator == cast.decimalSeparator  &&
1305                       Utilities.equals(prefix,    cast.prefix )  &&
1306                       Utilities.equals(suffix0,   cast.suffix0)  &&
1307                       Utilities.equals(suffix1,   cast.suffix1)  &&
1308                       Utilities.equals(suffix2,   cast.suffix2)  &&
1309                       Utilities.equals(numberFormat.getDecimalFormatSymbols(),
1310                                   cast.numberFormat.getDecimalFormatSymbols());
1311            } 
1312            return false;
1313            
1314        }
1315        
1316        /**
1317         * Returns a string representation of this object.
1318         */
1319        public String toString() {
1320            return Utilities.getShortClassName(this)+'['+toPattern()+']';
1321        }
1322    }