Source Code Cross Referenced for AngleFormat.java in  » GIS » GeoTools-2.4.1 » org » geotools » measure » Java Source Code / Java DocumentationJava Source Code and Java Documentation

Java Source Code / Java Documentation
1. 6.0 JDK Core
2. 6.0 JDK Modules
3. 6.0 JDK Modules com.sun
4. 6.0 JDK Modules com.sun.java
5. 6.0 JDK Modules sun
6. 6.0 JDK Platform
7. Ajax
8. Apache Harmony Java SE
9. Aspect oriented
10. Authentication Authorization
11. Blogger System
12. Build
13. Byte Code
14. Cache
15. Chart
16. Chat
17. Code Analyzer
18. Collaboration
19. Content Management System
20. Database Client
21. Database DBMS
22. Database JDBC Connection Pool
23. Database ORM
24. Development
25. EJB Server geronimo
26. EJB Server GlassFish
27. EJB Server JBoss 4.2.1
28. EJB Server resin 3.1.5
29. ERP CRM Financial
30. ESB
31. Forum
32. GIS
33. Graphic Library
34. Groupware
35. HTML Parser
36. IDE
37. IDE Eclipse
38. IDE Netbeans
39. Installer
40. Internationalization Localization
41. Inversion of Control
42. Issue Tracking
43. J2EE
44. JBoss
45. JMS
46. JMX
47. Library
48. Mail Clients
49. Net
50. Parser
51. PDF
52. Portal
53. Profiler
54. Project Management
55. Report
56. RSS RDF
57. Rule Engine
58. Science
59. Scripting
60. Search Engine
61. Security
62. Sevlet Container
63. Source Control
64. Swing Library
65. Template Engine
66. Test Coverage
67. Testing
68. UML
69. Web Crawler
70. Web Framework
71. Web Mail
72. Web Server
73. Web Services
74. Web Services apache cxf 2.0.1
75. Web Services AXIS2
76. Wiki Engine
77. Workflow Engines
78. XML
79. XML UI
Java
Java Tutorial
Java Open Source
Jar File Download
Java Articles
Java Products
Java by API
Photoshop Tutorials
Maya Tutorials
Flash Tutorials
3ds-Max Tutorials
Illustrator Tutorials
GIMP Tutorials
C# / C Sharp
C# / CSharp Tutorial
C# / CSharp Open Source
ASP.Net
ASP.NET Tutorial
JavaScript DHTML
JavaScript Tutorial
JavaScript Reference
HTML / CSS
HTML CSS Reference
C / ANSI-C
C Tutorial
C++
C++ Tutorial
Ruby
PHP
Python
Python Tutorial
Python Open Source
SQL Server / T-SQL
SQL Server / T-SQL Tutorial
Oracle PL / SQL
Oracle PL/SQL Tutorial
PostgreSQL
SQL / MySQL
MySQL Tutorial
VB.Net
VB.Net Tutorial
Flash / Flex / ActionScript
VBA / Excel / Access / Word
XML
XML Tutorial
Microsoft Office PowerPoint 2007 Tutorial
Microsoft Office Excel 2007 Tutorial
Microsoft Office Word 2007 Tutorial
Java Source Code / Java Documentation » GIS » GeoTools 2.4.1 » org.geotools.measure 
Source Cross Referenced  Class Diagram Java Document (Java Doc) 


0001:        /*
0002:         *    GeoTools - OpenSource mapping toolkit
0003:         *    http://geotools.org
0004:         *    (C) 2003-2006, GeoTools Project Managment Committee (PMC)
0005:         *    (C) 2001, Institut de Recherche pour le Développement
0006:         *    (C) 1999, Fisheries and Oceans Canada
0007:         *   
0008:         *    This library is free software; you can redistribute it and/or
0009:         *    modify it under the terms of the GNU Lesser General Public
0010:         *    License as published by the Free Software Foundation;
0011:         *    version 2.1 of the License.
0012:         *
0013:         *    This library is distributed in the hope that it will be useful,
0014:         *    but WITHOUT ANY WARRANTY; without even the implied warranty of
0015:         *    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
0016:         *    Lesser General Public License for more details.
0017:         */
0018:        package org.geotools.measure;
0019:
0020:        // J2SE dependencies
0021:        import java.io.IOException;
0022:        import java.io.ObjectInputStream;
0023:        import java.text.DecimalFormat;
0024:        import java.text.DecimalFormatSymbols;
0025:        import java.text.FieldPosition;
0026:        import java.text.Format;
0027:        import java.text.ParseException;
0028:        import java.text.ParsePosition;
0029:        import java.util.Locale;
0030:
0031:        // Geotools dependencies
0032:        import org.geotools.resources.Utilities;
0033:        import org.geotools.resources.XMath;
0034:        import org.geotools.resources.i18n.Errors;
0035:        import org.geotools.resources.i18n.ErrorKeys;
0036:
0037:        /**
0038:         * Parse and format angle according a specified pattern. The pattern is a string
0039:         * containing any characters, with a special meaning for the following characters:
0040:         *
0041:         * <blockquote><table cellpadding="3">
0042:         *     <tr><td>{@code D}</td><td>&nbsp;&nbsp;The integer part of degrees</td></tr>
0043:         *     <tr><td>{@code d}</td><td>&nbsp;&nbsp;The fractional part of degrees</td></tr>
0044:         *     <tr><td>{@code M}</td><td>&nbsp;&nbsp;The integer part of minutes</td></tr>
0045:         *     <tr><td>{@code m}</td><td>&nbsp;&nbsp;The fractional part of minutes</td></tr>
0046:         *     <tr><td>{@code S}</td><td>&nbsp;&nbsp;The integer part of seconds</td></tr>
0047:         *     <tr><td>{@code s}</td><td>&nbsp;&nbsp;The fractional part of seconds</td></tr>
0048:         *     <tr><td>{@code .}</td><td>&nbsp;&nbsp;The decimal separator</td></tr>
0049:         * </table></blockquote>
0050:         * <br>
0051:         * Upper-case letters {@code D}, {@code M} and {@code S} are for the integer
0052:         * parts of degrees, minutes and seconds respectively. They must appear in this order (e.g.
0053:         * "<code>M'D</code>" is illegal because "M" and "S" are inverted; "<code>D°S</code>" is
0054:         * illegal too because there is no "M" between "D" and "S"). Lower-case letters {@code d},
0055:         * {@code m} and {@code s} are for fractional parts of degrees, minutes and seconds
0056:         * respectively. Only one of those may appears in a pattern, and it must be the last special
0057:         * symbol (e.g. "<code>D.dd°MM'</code>" is illegal because "d" is followed by "M";
0058:         * "{@code D.mm}" is illegal because "m" is not the fractional part of "D").
0059:         * <br><br>
0060:         * The number of occurrence of {@code D}, {@code M}, {@code S} and their
0061:         * lower-case counterpart is the number of digits to format. For example, "DD.ddd" will
0062:         * format angle with two digits for the integer part and three digits for the fractional
0063:         * part (e.g. 4.4578 will be formatted as "04.458"). Separator characters like <code>°</code>,
0064:         * <code>'</code> and <code>"</code> and inserted "as-is" in the formatted string (except the
0065:         * decimal separator dot ("{@code .}"), which is replaced by the local-dependent decimal
0066:         * separator). Separator characters may be completely omitted; {@code AngleFormat} will
0067:         * still differentiate degrees, minutes and seconds fields according the pattern. For example,
0068:         * "{@code 0480439}" with the pattern "{@code DDDMMmm}" will be parsed as 48°04.39'.
0069:         * <br><br>
0070:         * The following table gives some examples of legal patterns.
0071:         *
0072:         * <blockquote><table cellpadding="3">
0073:         * <tr><th>Pattern                </th>  <th>Example   </th></tr>
0074:         * <tr><td><code>DD°MM'SS" </code></td>  <td>48°30'00" </td></tr>
0075:         * <tr><td><code>DD°MM'    </code></td>  <td>48°30'    </td></tr>
0076:         * <tr><td>{@code DD.ddd    }</td>  <td>48.500    </td></tr>
0077:         * <tr><td>{@code DDMM      }</td>  <td>4830      </td></tr>
0078:         * <tr><td>{@code DDMMSS    }</td>  <td>483000    </td></tr>
0079:         * </table></blockquote>
0080:         *
0081:         * @see Angle
0082:         * @see Latitude
0083:         * @see Longitude
0084:         *
0085:         * @since 2.0
0086:         * @source $URL: http://svn.geotools.org/geotools/tags/2.4.1/modules/library/referencing/src/main/java/org/geotools/measure/AngleFormat.java $
0087:         * @version $Id: AngleFormat.java 20874 2006-08-07 10:00:01Z jgarnett $
0088:         * @author Martin Desruisseaux
0089:         */
0090:        public class AngleFormat extends Format {
0091:            /**
0092:             * Caractère représentant l'hémisphère nord.
0093:             * Il doit obligatoirement être en majuscule.
0094:             */
0095:            private static final char NORTH = 'N';
0096:
0097:            /**
0098:             * Caractère représentant l'hémisphère sud.
0099:             * Il doit obligatoirement être en majuscule.
0100:             */
0101:            private static final char SOUTH = 'S';
0102:
0103:            /**
0104:             * Caractère représentant l'hémisphère est.
0105:             * Il doit obligatoirement être en majuscule.
0106:             */
0107:            private static final char EAST = 'E';
0108:
0109:            /**
0110:             * Caractère représentant l'hémisphère ouest.
0111:             * Il doit obligatoirement être en majuscule.
0112:             */
0113:            private static final char WEST = 'W';
0114:
0115:            /**
0116:             * Constante indique que l'angle
0117:             * à formater est une longitude.
0118:             */
0119:            static final int LONGITUDE = 0;
0120:
0121:            /**
0122:             * Constante indique que l'angle
0123:             * à formater est une latitude.
0124:             */
0125:            static final int LATITUDE = 1;
0126:
0127:            /**
0128:             * Constante indique que le nombre
0129:             * à formater est une altitude.
0130:             */
0131:            static final int ALTITUDE = 2;
0132:
0133:            /**
0134:             * A constant for the symbol to appears before the degrees fields.
0135:             * Fields PREFIX, DEGREES, MINUTES and SECONDS <strong>must</strong>
0136:             * have increasing values (-1, 0, +1, +2, +3).
0137:             */
0138:            private static final int PREFIX_FIELD = -1;
0139:
0140:            /**
0141:             * Constant for degrees field. When formatting a string, this value may be
0142:             * specified to the {@link java.text.FieldPosition} constructor in order to
0143:             * get the bounding index where degrees have been written.
0144:             */
0145:            public static final int DEGREES_FIELD = 0;
0146:
0147:            /**
0148:             * Constant for minutes field. When formatting a string, this value may be
0149:             * specified to the {@link java.text.FieldPosition} constructor in order to
0150:             * get the bounding index where minutes have been written.
0151:             */
0152:            public static final int MINUTES_FIELD = 1;
0153:
0154:            /**
0155:             * Constant for seconds field. When formatting a string, this value may be
0156:             * specified to the {@link java.text.FieldPosition} constructor in order to
0157:             * get the bounding index where seconds have been written.
0158:             */
0159:            public static final int SECONDS_FIELD = 2;
0160:
0161:            /**
0162:             * Constant for hemisphere field. When formatting a string, this value may be
0163:             * specified to the {@link java.text.FieldPosition} constructor in order to
0164:             * get the bounding index where the hemisphere synbol has been written.
0165:             */
0166:            public static final int HEMISPHERE_FIELD = 3;
0167:
0168:            /**
0169:             * Symboles représentant les degrés (0),
0170:             * minutes (1) et les secondes (2).
0171:             */
0172:            private static final char[] SYMBOLS = { 'D', 'M', 'S' };
0173:
0174:            /**
0175:             * Nombre minimal d'espaces que doivent occuper les parties
0176:             * entières des degrés (0), minutes (1) et secondes (2). Le
0177:             * champs {@code widthDecimal} indique la largeur fixe
0178:             * que doit avoir la partie décimale. Il s'appliquera au
0179:             * dernier champs non-zero dans {@code width0..2}.
0180:             */
0181:            private int width0 = 1, width1 = 2, width2 = 0, widthDecimal = 0;
0182:
0183:            /**
0184:             * Caractères à insérer au début ({@code prefix}) et à la
0185:             * suite des degrés, minutes et secondes ({@code suffix0..2}).
0186:             * Ces champs doivent être {@code null} s'il n'y a rien à insérer.
0187:             */
0188:            private String prefix = null, suffix0 = "\u00B0", suffix1 = "'",
0189:                    suffix2 = "\"";
0190:
0191:            /**
0192:             * Indique s'il faut utiliser le séparateur décimal pour séparer la partie
0193:             * entière de la partie fractionnaire. La valeur {@code false} indique
0194:             * que les parties entières et fractionnaires doivent être écrites ensembles
0195:             * (par exemple 34867 pour 34.867). La valeur par défaut est {@code true}.
0196:             */
0197:            private boolean decimalSeparator = true;
0198:
0199:            /**
0200:             * Format à utiliser pour écrire les nombres
0201:             * (degrés, minutes ou secondes) à l'intérieur
0202:             * de l'écriture d'un angle.
0203:             */
0204:            private final DecimalFormat numberFormat;
0205:
0206:            /**
0207:             * Objet à transmetre aux méthodes {@code DecimalFormat.format}.
0208:             * Ce paramètre existe simplement pour éviter de créer cet objet trop
0209:             * souvent, alors qu'on ne s'y intéresse pas.
0210:             */
0211:            private transient FieldPosition dummy = new FieldPosition(0);
0212:
0213:            /**
0214:             * Restore fields after deserialization.
0215:             */
0216:            private void readObject(final ObjectInputStream in)
0217:                    throws IOException, ClassNotFoundException {
0218:                in.defaultReadObject();
0219:                dummy = new FieldPosition(0);
0220:            }
0221:
0222:            /**
0223:             * Returns the width of the specified field.
0224:             */
0225:            private int getWidth(final int index) {
0226:                switch (index) {
0227:                case DEGREES_FIELD:
0228:                    return width0;
0229:                case MINUTES_FIELD:
0230:                    return width1;
0231:                case SECONDS_FIELD:
0232:                    return width2;
0233:                default:
0234:                    return 0; // Must be 0 (important!)
0235:                }
0236:            }
0237:
0238:            /**
0239:             * Set the width for the specified field.
0240:             * All folowing fields will be set to 0.
0241:             */
0242:            private void setWidth(final int index, int width) {
0243:                switch (index) {
0244:                case DEGREES_FIELD:
0245:                    width0 = width;
0246:                    width = 0; // fall through
0247:                case MINUTES_FIELD:
0248:                    width1 = width;
0249:                    width = 0; // fall through
0250:                case SECONDS_FIELD:
0251:                    width2 = width; // fall through
0252:                }
0253:            }
0254:
0255:            /**
0256:             * Returns the suffix for the specified field.
0257:             */
0258:            private String getSuffix(final int index) {
0259:                switch (index) {
0260:                case PREFIX_FIELD:
0261:                    return prefix;
0262:                case DEGREES_FIELD:
0263:                    return suffix0;
0264:                case MINUTES_FIELD:
0265:                    return suffix1;
0266:                case SECONDS_FIELD:
0267:                    return suffix2;
0268:                default:
0269:                    return null;
0270:                }
0271:            }
0272:
0273:            /**
0274:             * Set the suffix for the specified field. Suffix
0275:             * for all following fields will be set to their
0276:             * default value.
0277:             */
0278:            private void setSuffix(final int index, String s) {
0279:                switch (index) {
0280:                case PREFIX_FIELD:
0281:                    prefix = s;
0282:                    s = "\u00B0"; // fall through
0283:                case DEGREES_FIELD:
0284:                    suffix0 = s;
0285:                    s = "'"; // fall through
0286:                case MINUTES_FIELD:
0287:                    suffix1 = s;
0288:                    s = "\""; // fall through
0289:                case SECONDS_FIELD:
0290:                    suffix2 = s; // fall through
0291:                }
0292:            }
0293:
0294:            /**
0295:             * Construct a new {@code AngleFormat} for the specified locale.
0296:             */
0297:            public static AngleFormat getInstance(final Locale locale) {
0298:                return new AngleFormat("D\u00B0MM.m'", locale);
0299:            }
0300:
0301:            /**
0302:             * Construct a new {@code AngleFormat} using
0303:             * the current default locale and a default pattern.
0304:             */
0305:            public AngleFormat() {
0306:                this ("D\u00B0MM.m'");
0307:            }
0308:
0309:            /**
0310:             * Construct a new {@code AngleFormat} using the
0311:             * current default locale and the specified pattern.
0312:             *
0313:             * @param  pattern Pattern to use for parsing and formatting angle.
0314:             *         See class description for an explanation of how this pattern work.
0315:             * @throws IllegalArgumentException If the specified pattern is not legal.
0316:             */
0317:            public AngleFormat(final String pattern)
0318:                    throws IllegalArgumentException {
0319:                this (pattern, new DecimalFormatSymbols());
0320:            }
0321:
0322:            /**
0323:             * Construct a new {@code AngleFormat}
0324:             * using the specified pattern and locale.
0325:             *
0326:             * @param  pattern Pattern to use for parsing and formatting angle.
0327:             *         See class description for an explanation of how this pattern work.
0328:             * @param  locale Locale to use.
0329:             * @throws IllegalArgumentException If the specified pattern is not legal.
0330:             */
0331:            public AngleFormat(final String pattern, final Locale locale)
0332:                    throws IllegalArgumentException {
0333:                this (pattern, new DecimalFormatSymbols(locale));
0334:            }
0335:
0336:            /**
0337:             * Construct a new {@code AngleFormat}
0338:             * using the specified pattern and decimal symbols.
0339:             *
0340:             * @param  pattern Pattern to use for parsing and formatting angle.
0341:             *         See class description for an explanation of how this pattern work.
0342:             * @param  symbols The symbols to use for parsing and formatting numbers.
0343:             * @throws IllegalArgumentException If the specified pattern is not legal.
0344:             */
0345:            public AngleFormat(final String pattern,
0346:                    final DecimalFormatSymbols symbols) {
0347:                // NOTE: pour cette routine, il ne faut PAS que DecimalFormat
0348:                //       reconnaisse la notation exponentielle, parce que ça
0349:                //       risquerait d'être confondu avec le "E" de "Est".
0350:                numberFormat = new DecimalFormat("#0", symbols);
0351:                applyPattern(pattern);
0352:            }
0353:
0354:            /**
0355:             * Set the pattern to use for parsing and formatting angle.
0356:             * See class description for an explanation of how patterns work.
0357:             *
0358:             * @param  pattern Pattern to use for parsing and formatting angle.
0359:             * @throws IllegalArgumentException If the specified pattern is not legal.
0360:             */
0361:            public synchronized void applyPattern(final String pattern)
0362:                    throws IllegalArgumentException {
0363:                widthDecimal = 0;
0364:                decimalSeparator = true;
0365:                int startPrefix = 0;
0366:                int symbolIndex = 0;
0367:                boolean parseFinished = false;
0368:                final int length = pattern.length();
0369:                for (int i = 0; i < length; i++) {
0370:                    /*
0371:                     * On examine un à un tous les caractères du patron en
0372:                     * sautant ceux qui ne sont pas réservés ("D", "M", "S"
0373:                     * et leur équivalents en minuscules). Les caractères
0374:                     * non-reservés seront mémorisés comme suffix plus tard.
0375:                     */
0376:                    final char c = pattern.charAt(i);
0377:                    final char upperCaseC = Character.toUpperCase(c);
0378:                    for (int field = DEGREES_FIELD; field < SYMBOLS.length; field++) {
0379:                        if (upperCaseC == SYMBOLS[field]) {
0380:                            /*
0381:                             * Un caractère réservé a été trouvé. Vérifie maintenant
0382:                             * s'il est valide. Par exemple il serait illegal d'avoir
0383:                             * comme patron "MM.mm" sans qu'il soit précédé des degrés.
0384:                             * On attend les lettres "D", "M" et "S" dans l'ordre. Si
0385:                             * le caractère est en lettres minuscules, il doit être le
0386:                             * même que le dernier code (par exemple "DD.mm" est illegal).
0387:                             */
0388:                            if (c == upperCaseC) {
0389:                                symbolIndex++;
0390:                            }
0391:                            if (field != symbolIndex - 1 || parseFinished) {
0392:                                setWidth(DEGREES_FIELD, 1);
0393:                                setSuffix(PREFIX_FIELD, null);
0394:                                widthDecimal = 0;
0395:                                decimalSeparator = true;
0396:                                throw new IllegalArgumentException(
0397:                                        Errors
0398:                                                .format(
0399:                                                        ErrorKeys.ILLEGAL_ANGLE_PATTERN_$1,
0400:                                                        pattern));
0401:                            }
0402:                            if (c == upperCaseC) {
0403:                                /*
0404:                                 * Mémorise les caractères qui précédaient ce code comme suffix
0405:                                 * du champs précédent. Puis on comptera le nombre de fois que le
0406:                                 * code se répète, en mémorisant cette information comme largeur
0407:                                 * de ce champ.
0408:                                 */
0409:                                setSuffix(field - 1,
0410:                                        (i > startPrefix) ? pattern.substring(
0411:                                                startPrefix, i) : null);
0412:                                int w = 1;
0413:                                while (++i < length && pattern.charAt(i) == c)
0414:                                    w++;
0415:                                setWidth(field, w);
0416:                            } else {
0417:                                /*
0418:                                 * Si le caractère est une minuscule, ce qui le précédait sera le
0419:                                 * séparateur décimal plutôt qu'un suffix. On comptera le nombre
0420:                                 * d'occurences du caractères pour obtenir la précision.
0421:                                 */
0422:                                switch (i - startPrefix) {
0423:                                case 0:
0424:                                    decimalSeparator = false;
0425:                                    break;
0426:                                case 1:
0427:                                    if (pattern.charAt(startPrefix) == '.') {
0428:                                        decimalSeparator = true;
0429:                                        break;
0430:                                    }
0431:                                default:
0432:                                    throw new IllegalArgumentException(
0433:                                            Errors
0434:                                                    .format(
0435:                                                            ErrorKeys.ILLEGAL_ANGLE_PATTERN_$1,
0436:                                                            pattern));
0437:                                }
0438:                                int w = 1;
0439:                                while (++i < length && pattern.charAt(i) == c)
0440:                                    w++;
0441:                                widthDecimal = w;
0442:                                parseFinished = true;
0443:                            }
0444:                            startPrefix = i--;
0445:                            break; // Break 'j' and continue 'i'.
0446:                        }
0447:                    }
0448:                }
0449:                setSuffix(symbolIndex - 1, (startPrefix < length) ? pattern
0450:                        .substring(startPrefix) : null);
0451:            }
0452:
0453:            /**
0454:             * Returns the pattern used for parsing and formatting angles.
0455:             * See class description for an explanation of how patterns work.
0456:             */
0457:            public synchronized String toPattern() {
0458:                char symbol = '#';
0459:                final StringBuffer buffer = new StringBuffer();
0460:                for (int field = DEGREES_FIELD; field <= SYMBOLS.length; field++) {
0461:                    final String previousSuffix = getSuffix(field - 1);
0462:                    int w = getWidth(field);
0463:                    if (w > 0) {
0464:                        /*
0465:                         * Procède à l'écriture de la partie entière des degrés,
0466:                         * minutes ou secondes. Le suffix du champs précédent
0467:                         * sera écrit avant les degrés, minutes ou secondes.
0468:                         */
0469:                        if (previousSuffix != null) {
0470:                            buffer.append(previousSuffix);
0471:                        }
0472:                        symbol = SYMBOLS[field];
0473:                        do
0474:                            buffer.append(symbol);
0475:                        while (--w > 0);
0476:                    } else {
0477:                        /*
0478:                         * Procède à l'écriture de la partie décimale des
0479:                         * degrés, minutes ou secondes. Le suffix du ce
0480:                         * champs sera écrit après cette partie fractionnaire.
0481:                         */
0482:                        w = widthDecimal;
0483:                        if (w > 0) {
0484:                            if (decimalSeparator)
0485:                                buffer.append('.');
0486:                            symbol = Character.toLowerCase(symbol);
0487:                            do
0488:                                buffer.append(symbol);
0489:                            while (--w > 0);
0490:                        }
0491:                        if (previousSuffix != null) {
0492:                            buffer.append(previousSuffix);
0493:                        }
0494:                        break;
0495:                    }
0496:                }
0497:                return buffer.toString();
0498:            }
0499:
0500:            /**
0501:             * Format an angle. The string will be formatted according
0502:             * the pattern set in the last call to {@link #applyPattern}.
0503:             *
0504:             * @param  angle Angle to format, in degrees.
0505:             * @return The formatted string.
0506:             */
0507:            public final String format(final double angle) {
0508:                return format(angle, new StringBuffer(), null).toString();
0509:            }
0510:
0511:            /**
0512:             * Formats an angle and appends the resulting text to a given string buffer.
0513:             * The string will be formatted according the pattern set in the last call
0514:             * to {@link #applyPattern}.
0515:             *
0516:             * @param  angle      Angle to format, in degrees.
0517:             * @param  toAppendTo Where the text is to be appended.
0518:             * @param  pos        An optional {@link FieldPosition} identifying a field
0519:             *                    in the formatted text, or {@code null} if this
0520:             *                    information is not wanted. This field position shall
0521:             *                    be constructed with one of the following constants:
0522:             *                    {@link #DEGREES_FIELD},
0523:             *                    {@link #MINUTES_FIELD},
0524:             *                    {@link #SECONDS_FIELD} or
0525:             *                    {@link #HEMISPHERE_FIELD}.
0526:             *
0527:             * @return The string buffer passed in as {@code toAppendTo}, with formatted text appended.
0528:             */
0529:            public synchronized StringBuffer format(final double angle,
0530:                    StringBuffer toAppendTo, final FieldPosition pos) {
0531:                double degrees = angle;
0532:                /*
0533:                 * Calcule à l'avance les minutes et les secondes. Si les minutes et secondes
0534:                 * ne doivent pas être écrits, on mémorisera NaN. Notez que pour extraire les
0535:                 * parties entières, on utilise (int) au lieu de 'Math.floor' car (int) arrondie
0536:                 * vers 0 (ce qui est le comportement souhaité) alors que 'floor' arrondie vers
0537:                 * l'entier inférieur.
0538:                 */
0539:                double minutes = Double.NaN;
0540:                double secondes = Double.NaN;
0541:                if (width1 != 0 && !Double.isNaN(angle)) {
0542:                    int tmp = (int) degrees; // Arrondie vers 0 même si négatif.
0543:                    minutes = Math.abs(degrees - tmp) * 60;
0544:                    degrees = tmp;
0545:                    if (minutes < 0 || minutes > 60) {
0546:                        // Erreur d'arrondissement (parce que l'angle est trop élevé)
0547:                        throw new IllegalArgumentException(Errors.format(
0548:                                ErrorKeys.ANGLE_OVERFLOW_$1, new Double(angle)));
0549:                    }
0550:                    if (width2 != 0) {
0551:                        tmp = (int) minutes; // Arrondie vers 0 même si négatif.
0552:                        secondes = (minutes - tmp) * 60;
0553:                        minutes = tmp;
0554:                        if (secondes < 0 || secondes > 60) {
0555:                            // Erreur d'arrondissement (parce que l'angle est trop élevé)
0556:                            throw new IllegalArgumentException(Errors.format(
0557:                                    ErrorKeys.ANGLE_OVERFLOW_$1, new Double(
0558:                                            angle)));
0559:                        }
0560:                        /*
0561:                         * On applique maintenant une correction qui tiendra
0562:                         * compte des problèmes d'arrondissements.
0563:                         */
0564:                        final double puissance = XMath.pow10(widthDecimal);
0565:                        secondes = Math.rint(secondes * puissance) / puissance;
0566:                        tmp = (int) (secondes / 60);
0567:                        secondes -= 60 * tmp;
0568:                        minutes += tmp;
0569:                    } else {
0570:                        final double puissance = XMath.pow10(widthDecimal);
0571:                        minutes = Math.rint(minutes * puissance) / puissance;
0572:                    }
0573:                    tmp = (int) (minutes / 60); // Arrondie vers 0 même si négatif.
0574:                    minutes -= 60 * tmp;
0575:                    degrees += tmp;
0576:                }
0577:                /*
0578:                 * Les variables 'degrés', 'minutes' et 'secondes' contiennent
0579:                 * maintenant les valeurs des champs à écrire, en principe épurés
0580:                 * des problèmes d'arrondissements. Procède maintenant à l'écriture
0581:                 * de l'angle.
0582:                 */
0583:                if (prefix != null) {
0584:                    toAppendTo.append(prefix);
0585:                }
0586:                final int field;
0587:                if (pos != null) {
0588:                    field = pos.getField();
0589:                    pos.setBeginIndex(0);
0590:                    pos.setEndIndex(0);
0591:                } else {
0592:                    field = PREFIX_FIELD;
0593:                }
0594:                toAppendTo = formatField(degrees, toAppendTo,
0595:                        field == DEGREES_FIELD ? pos : null, width0,
0596:                        width1 == 0, suffix0);
0597:                if (!Double.isNaN(minutes)) {
0598:                    toAppendTo = formatField(minutes, toAppendTo,
0599:                            field == MINUTES_FIELD ? pos : null, width1,
0600:                            width2 == 0, suffix1);
0601:                }
0602:                if (!Double.isNaN(secondes)) {
0603:                    toAppendTo = formatField(secondes, toAppendTo,
0604:                            field == SECONDS_FIELD ? pos : null, width2, true,
0605:                            suffix2);
0606:                }
0607:                return toAppendTo;
0608:            }
0609:
0610:            /**
0611:             * Procède à l'écriture d'un champ de l'angle.
0612:             *
0613:             * @param value Valeur à écrire.
0614:             * @param toAppendTo Buffer dans lequel écrire le champs.
0615:             * @param pos Objet dans lequel mémoriser les index des premiers
0616:             *        et derniers caractères écrits, ou {@code null}
0617:             *        pour ne pas mémoriser ces index.
0618:             * @param w Nombre de minimal caractères de la partie entière.
0619:             * @param last {@code true} si ce champs est le dernier,
0620:             *        et qu'il faut donc écrire la partie décimale.
0621:             * @param s Suffix à écrire après le nombre (peut être nul).
0622:             */
0623:            private StringBuffer formatField(double value,
0624:                    StringBuffer toAppendTo, final FieldPosition pos,
0625:                    final int w, final boolean last, final String s) {
0626:                final int startPosition = toAppendTo.length();
0627:                if (!last) {
0628:                    numberFormat.setMinimumIntegerDigits(w);
0629:                    numberFormat.setMaximumFractionDigits(0);
0630:                    toAppendTo = numberFormat.format(value, toAppendTo, dummy);
0631:                } else if (decimalSeparator) {
0632:                    numberFormat.setMinimumIntegerDigits(w);
0633:                    numberFormat.setMinimumFractionDigits(widthDecimal);
0634:                    numberFormat.setMaximumFractionDigits(widthDecimal);
0635:                    toAppendTo = numberFormat.format(value, toAppendTo, dummy);
0636:                } else {
0637:                    value *= XMath.pow10(widthDecimal);
0638:                    numberFormat.setMaximumFractionDigits(0);
0639:                    numberFormat.setMinimumIntegerDigits(w + widthDecimal);
0640:                    toAppendTo = numberFormat.format(value, toAppendTo, dummy);
0641:                }
0642:                if (s != null) {
0643:                    toAppendTo.append(s);
0644:                }
0645:                if (pos != null) {
0646:                    pos.setBeginIndex(startPosition);
0647:                    pos.setEndIndex(toAppendTo.length() - 1);
0648:                }
0649:                return toAppendTo;
0650:            }
0651:
0652:            /**
0653:             * Formats an angle, a latitude or a longitude and appends the resulting text
0654:             * to a given string buffer. The string will be formatted according the pattern
0655:             * set in the last call to {@link #applyPattern}. The argument {@code obj}
0656:             * shall be an {@link Angle} object or one of its derived class ({@link Latitude},
0657:             * {@link Longitude}). If {@code obj} is a {@link Latitude} object, then a
0658:             * symbol "N" or "S" will be appended to the end of the string (the symbol will
0659:             * be choosen according the angle's sign). Otherwise, if {@code obj} is a
0660:             * {@link Longitude} object, then a symbol "E" or "W" will be appended to the
0661:             * end of the string. Otherwise, no hemisphere symbol will be appended.
0662:             * <br><br>
0663:             * Strictly speaking, formatting ordinary numbers is not the
0664:             * {@code AngleFormat}'s job. Nevertheless, this method
0665:             * accept {@link Number} objects. This capability is provided
0666:             * only as a convenient way to format altitude numbers together
0667:             * with longitude and latitude angles.
0668:             *
0669:             * @param  obj        {@link Angle} or {@link Number} object to format.
0670:             * @param  toAppendTo Where the text is to be appended.
0671:             * @param  pos        An optional {@link FieldPosition} identifying a field
0672:             *                    in the formatted text, or {@code null} if this
0673:             *                    information is not wanted. This field position shall
0674:             *                    be constructed with one of the following constants:
0675:             *                    {@link #DEGREES_FIELD},
0676:             *                    {@link #MINUTES_FIELD},
0677:             *                    {@link #SECONDS_FIELD} or
0678:             *                    {@link #HEMISPHERE_FIELD}.
0679:             *
0680:             * @return The string buffer passed in as {@code toAppendTo}, with
0681:             *         formatted text appended.
0682:             * @throws IllegalArgumentException if {@code obj} if not an object
0683:             *         of class {@link Angle} or {@link Number}.
0684:             */
0685:            public synchronized StringBuffer format(final Object obj,
0686:                    StringBuffer toAppendTo, final FieldPosition pos)
0687:                    throws IllegalArgumentException {
0688:                if (obj instanceof  Latitude) {
0689:                    return format(((Latitude) obj).degrees(), toAppendTo, pos,
0690:                            NORTH, SOUTH);
0691:                }
0692:                if (obj instanceof  Longitude) {
0693:                    return format(((Longitude) obj).degrees(), toAppendTo, pos,
0694:                            EAST, WEST);
0695:                }
0696:                if (obj instanceof  Angle) {
0697:                    return format(((Angle) obj).degrees(), toAppendTo, pos);
0698:                }
0699:                if (obj instanceof  Number) {
0700:                    numberFormat.setMinimumIntegerDigits(1);
0701:                    numberFormat.setMinimumFractionDigits(0);
0702:                    numberFormat.setMaximumFractionDigits(2);
0703:                    return numberFormat.format(obj, toAppendTo,
0704:                            (pos != null) ? pos : dummy);
0705:                }
0706:                throw new IllegalArgumentException(Errors.format(
0707:                        ErrorKeys.NOT_AN_ANGLE_OBJECT_$1, Utilities
0708:                                .getShortClassName(obj)));
0709:            }
0710:
0711:            /**
0712:             * Procède à l'écriture d'un angle, d'une latitude ou d'une longitude.
0713:             *
0714:             * @param  number     Angle ou nombre à écrire.
0715:             * @param  type       Type de l'angle ou du nombre:
0716:             *                    {@link #LONGITUDE},
0717:             *                    {@link #LATITUDE} ou
0718:             *                    {@link #ALTITUDE}.
0719:             * @param  toAppendTo Buffer dans lequel écrire l'angle.
0720:             * @param  pos        En entré, le code du champs dont on désire les index
0721:             *                    ({@link #DEGREES_FIELD},
0722:             *                     {@link #MINUTES_FIELD},
0723:             *                     {@link #SECONDS_FIELD} ou
0724:             *                     {@link #HEMISPHERE_FIELD}).
0725:             *                    En sortie, les index du champs demandé. Ce paramètre
0726:             *                    peut être nul si cette information n'est pas désirée.
0727:             *
0728:             * @return Le buffer {@code toAppendTo} par commodité.
0729:             */
0730:            synchronized StringBuffer format(final double number,
0731:                    final int type, StringBuffer toAppendTo,
0732:                    final FieldPosition pos) {
0733:                switch (type) {
0734:                default:
0735:                    throw new IllegalArgumentException(Integer.toString(type)); // Should not happen.
0736:                case LATITUDE:
0737:                    return format(number, toAppendTo, pos, NORTH, SOUTH);
0738:                case LONGITUDE:
0739:                    return format(number, toAppendTo, pos, EAST, WEST);
0740:                case ALTITUDE: {
0741:                    numberFormat.setMinimumIntegerDigits(1);
0742:                    numberFormat.setMinimumFractionDigits(0);
0743:                    numberFormat.setMaximumFractionDigits(2);
0744:                    return numberFormat.format(number, toAppendTo,
0745:                            (pos != null) ? pos : dummy);
0746:                }
0747:                }
0748:            }
0749:
0750:            /**
0751:             * Procède à l'écriture d'un angle suivit d'un suffix 'N','S','E' ou 'W'.
0752:             * L'angle sera formaté en utilisant comme modèle le patron spécifié lors
0753:             * du dernier appel de la méthode {@link #applyPattern}.
0754:             *
0755:             * @param  angle      Angle à écrire, en degrés.
0756:             * @param  toAppendTo Buffer dans lequel écrire l'angle.
0757:             * @param  pos        En entré, le code du champs dont on désire les index
0758:             *                    ({@link #DEGREES_FIELD},
0759:             *                     {@link #MINUTES_FIELD},
0760:             *                     {@link #SECONDS_FIELD} ou
0761:             *                     {@link #HEMISPHERE_FIELD}).
0762:             *                    En sortie, les index du champs demandé. Ce paramètre
0763:             *                    peut être nul si cette information n'est pas désirée.
0764:             * @param north       Caractères à écrire si l'angle est positif ou nul.
0765:             * @param south       Caractères à écrire si l'angle est négatif.
0766:             *
0767:             * @return Le buffer {@code toAppendTo} par commodité.
0768:             */
0769:            private StringBuffer format(final double angle,
0770:                    StringBuffer toAppendTo, final FieldPosition pos,
0771:                    final char north, final char south) {
0772:                toAppendTo = format(Math.abs(angle), toAppendTo, pos);
0773:                final int start = toAppendTo.length();
0774:                toAppendTo.append(angle < 0 ? south : north);
0775:                if (pos != null && pos.getField() == HEMISPHERE_FIELD) {
0776:                    pos.setBeginIndex(start);
0777:                    pos.setEndIndex(toAppendTo.length() - 1);
0778:                }
0779:                return toAppendTo;
0780:            }
0781:
0782:            /**
0783:             * Ignore le suffix d'un nombre. Cette méthode est appellée par la méthode
0784:             * {@link #parse} pour savoir quel champs il vient de lire. Par exemple si
0785:             * l'on vient de lire les degrés dans "48°12'", alors cette méthode extraira
0786:             * le "°" et retournera 0 pour indiquer que l'on vient de lire des degrés.
0787:             *
0788:             * Cette méthode se chargera d'ignorer les espaces qui précèdent le suffix.
0789:             * Elle tentera ensuite de d'abord interpréter le suffix selon les symboles
0790:             * du patron (spécifié avec {@link #applyPattern}. Si le suffix n'a pas été
0791:             * reconnus, elle tentera ensuite de le comparer aux symboles standards
0792:             * (° ' ").
0793:             *
0794:             * @param  source Chaîne dans laquelle on doit sauter le suffix.
0795:             * @param  pos En entré, l'index du premier caractère à considérer dans la
0796:             *         chaîne {@code pos}. En sortie, l'index du premier caractère
0797:             *         suivant le suffix (c'est-à-dire index à partir d'où continuer la
0798:             *         lecture après l'appel de cette méthode). Si le suffix n'a pas été
0799:             *         reconnu, alors cette méthode retourne par convention {@code SYMBOLS.length}.
0800:             * @param  field Champs à vérifier de préférences. Par exemple la valeur 1 signifie que les
0801:             *         suffix des minutes et des secondes devront être vérifiés avant celui des degrés.
0802:             * @return Le numéro du champs correspondant au suffix qui vient d'être extrait:
0803:             *         -1 pour le préfix de l'angle, 0 pour le suffix des degrés, 1 pour le
0804:             *         suffix des minutes et 2 pour le suffix des secondes. Si le texte n'a
0805:             *         pas été reconnu, retourne {@code SYMBOLS.length}.
0806:             */
0807:            private int skipSuffix(final String source,
0808:                    final ParsePosition pos, int field) {
0809:                /*
0810:                 * Essaie d'abord de sauter les suffix qui
0811:                 * avaient été spécifiés dans le patron.
0812:                 */
0813:                final int length = source.length();
0814:                int start = pos.getIndex();
0815:                for (int j = SYMBOLS.length; j >= 0; j--) { // C'est bien j>=0 et non j>0.
0816:                    int index = start;
0817:                    final String toSkip = getSuffix(field);
0818:                    if (toSkip != null) {
0819:                        final int toSkipLength = toSkip.length();
0820:                        do {
0821:                            if (source.regionMatches(index, toSkip, 0,
0822:                                    toSkipLength)) {
0823:                                pos.setIndex(index + toSkipLength);
0824:                                return field;
0825:                            }
0826:                        } while (index < length
0827:                                && Character
0828:                                        .isSpaceChar(source.charAt(index++)));
0829:                    }
0830:                    if (++field >= SYMBOLS.length)
0831:                        field = -1;
0832:                }
0833:                /*
0834:                 * Le texte trouvé ne correspondant à aucun suffix du patron,
0835:                 * essaie maintenant de sauter un des suffix standards (après
0836:                 * avoir ignoré les espaces qui le précédaient).
0837:                 */
0838:                char c;
0839:                do {
0840:                    if (start >= length) {
0841:                        return SYMBOLS.length;
0842:                    }
0843:                } while (Character.isSpaceChar(c = source.charAt(start++)));
0844:                switch (c) {
0845:                case '\u00B0':
0846:                    pos.setIndex(start);
0847:                    return DEGREES_FIELD;
0848:                case '\'':
0849:                    pos.setIndex(start);
0850:                    return MINUTES_FIELD;
0851:                case '"':
0852:                    pos.setIndex(start);
0853:                    return SECONDS_FIELD;
0854:                default:
0855:                    return SYMBOLS.length; // Unknow field.
0856:                }
0857:            }
0858:
0859:            /**
0860:             * Parse a string as an angle. This method can parse an angle even if it
0861:             * doesn't comply exactly to the expected pattern. For example, this method
0862:             * will parse correctly string "<code>48°12.34'</code>" even if the expected
0863:             * pattern was "{@code DDMM.mm}" (i.e. the string should have been
0864:             * "{@code 4812.34}"). Spaces between degrees, minutes and secondes
0865:             * are ignored. If the string ends with an hemisphere symbol "N" or "S",
0866:             * then this method returns an object of class {@link Latitude}. Otherwise,
0867:             * if the string ends with an hemisphere symbol "E" or "W", then this method
0868:             * returns an object of class {@link Longitude}. Otherwise, this method
0869:             * returns an object of class {@link Angle}.
0870:             *
0871:             * @param source A String whose beginning should be parsed.
0872:             * @param pos    Position where to start parsing.
0873:             * @return       The parsed string as an {@link Angle}, {@link Latitude}
0874:             *               or {@link Longitude} object.
0875:             */
0876:            public Angle parse(final String source, final ParsePosition pos) {
0877:                return parse(source, pos, false);
0878:            }
0879:
0880:            /**
0881:             * Interprète une chaîne de caractères représentant un angle. Les règles
0882:             * d'interprétation de cette méthode sont assez souples. Par exemple cettte
0883:             * méthode interprétera correctement la chaîne "48°12.34'" même si le patron
0884:             * attendu était "DDMM.mm" (c'est-à-dire que la chaîne aurait du être "4812.34").
0885:             * Les espaces entre les degrés, minutes et secondes sont acceptés. Si l'angle
0886:             * est suivit d'un symbole "N" ou "S", alors l'objet retourné sera de la classe
0887:             * {@link Latitude}. S'il est plutot suivit d'un symbole "E" ou "W", alors l'objet
0888:             * retourné sera de la classe {@link Longitude}. Sinon, il sera de la classe
0889:             * {@link Angle}.
0890:             *
0891:             * @param source           Chaîne de caractères à lire.
0892:             * @param pos              Position à partir d'où interpréter la chaîne.
0893:             * @param spaceAsSeparator Indique si l'espace est accepté comme séparateur
0894:             *                         à l'intérieur d'un angle. La valeur {@code true}
0895:             *                         fait que l'angle "45 30" sera interprété comme "45°30".
0896:             * @return L'angle lu.
0897:             */
0898:            private synchronized Angle parse(final String source,
0899:                    final ParsePosition pos, final boolean spaceAsSeparator) {
0900:                double degrees = Double.NaN;
0901:                double minutes = Double.NaN;
0902:                double secondes = Double.NaN;
0903:                final int length = source.length();
0904:                ///////////////////////////////////////////////////////////////////////////////
0905:                // BLOC A: Analyse la chaîne de caractères 'source' et affecte aux variables //
0906:                //         'degrés', 'minutes' et 'secondes' les valeurs appropriées.        //
0907:                //         Les premières accolades ne servent qu'à garder locales            //
0908:                //         les variables sans intérêt une fois la lecture terminée.          //
0909:                ///////////////////////////////////////////////////////////////////////////////
0910:                {
0911:                    /*
0912:                     * Extrait le préfix, s'il y en avait un. Si on tombe sur un symbole des
0913:                     * degrés, minutes ou secondes alors qu'on n'a pas encore lu de nombre,
0914:                     * on considèrera que la lecture a échouée.
0915:                     */
0916:                    final int indexStart = pos.getIndex();
0917:                    int index = skipSuffix(source, pos, PREFIX_FIELD);
0918:                    if (index >= 0 && index < SYMBOLS.length) {
0919:                        pos.setErrorIndex(indexStart);
0920:                        pos.setIndex(indexStart);
0921:                        return null;
0922:                    }
0923:                    /*
0924:                     * Saute les espaces blancs qui
0925:                     * précèdent le champs des degrés.
0926:                     */
0927:                    index = pos.getIndex();
0928:                    while (index < length
0929:                            && Character.isSpaceChar(source.charAt(index)))
0930:                        index++;
0931:                    pos.setIndex(index);
0932:                    /*
0933:                     * Lit les degrés. Notez que si aucun séparateur ne séparait les degrés
0934:                     * des minutes des secondes, alors cette lecture pourra inclure plusieurs
0935:                     * champs (exemple: "DDDMMmmm"). La séparation sera faite plus tard.
0936:                     */
0937:                    Number fieldObject = numberFormat.parse(source, pos);
0938:                    if (fieldObject == null) {
0939:                        pos.setIndex(indexStart);
0940:                        if (pos.getErrorIndex() < indexStart) {
0941:                            pos.setErrorIndex(index);
0942:                        }
0943:                        return null;
0944:                    }
0945:                    degrees = fieldObject.doubleValue();
0946:                    int indexEndField = pos.getIndex();
0947:                    boolean swapDM = true;
0948:                    BigBoss: switch (skipSuffix(source, pos, DEGREES_FIELD)) {
0949:                    /* ----------------------------------------------
0950:                     * ANALYSE DU SYMBOLE SUIVANT LES PRÉSUMÉS DEGRÉS
0951:                     * ----------------------------------------------
0952:                     * Les degrés étaient suivit du préfix d'un autre angle. Le préfix sera donc
0953:                     * retourné dans le buffer pour un éventuel traitement par le prochain appel
0954:                     * à la méthode 'parse' et on n'ira pas plus loin dans l'analyse de la chaîne.
0955:                     */
0956:                    case PREFIX_FIELD: {
0957:                        pos.setIndex(indexEndField);
0958:                        break BigBoss;
0959:                    }
0960:                        /* ----------------------------------------------
0961:                         * ANALYSE DU SYMBOLE SUIVANT LES PRÉSUMÉS DEGRÉS
0962:                         * ----------------------------------------------
0963:                         * On a trouvé le symbole des secondes au lieu de celui des degrés. On fait
0964:                         * la correction dans les variables 'degrés' et 'secondes' et on considère
0965:                         * que la lecture est terminée.
0966:                         */
0967:                    case SECONDS_FIELD: {
0968:                        secondes = degrees;
0969:                        degrees = Double.NaN;
0970:                        break BigBoss;
0971:                    }
0972:                        /* ----------------------------------------------
0973:                         * ANALYSE DU SYMBOLE SUIVANT LES PRÉSUMÉS DEGRÉS
0974:                         * ----------------------------------------------
0975:                         * Aucun symbole ne suit les degrés. Des minutes sont-elles attendues?
0976:                         * Si oui, on fera comme si le symbole des degrés avait été là. Sinon,
0977:                         * on considèrera que la lecture est terminée.
0978:                         */
0979:                    default: {
0980:                        if (width1 == 0)
0981:                            break BigBoss;
0982:                        if (!spaceAsSeparator)
0983:                            break BigBoss;
0984:                        // fall through
0985:                    }
0986:                        /* ----------------------------------------------
0987:                         * ANALYSE DU SYMBOLE SUIVANT LES PRÉSUMÉS DEGRÉS
0988:                         * ----------------------------------------------
0989:                         * Un symbole des degrés a été explicitement trouvé. Les degrés sont peut-être
0990:                         * suivit des minutes. On procèdera donc à la lecture du prochain nombre, puis
0991:                         * à l'analyse du symbole qui le suit.
0992:                         */
0993:                    case DEGREES_FIELD: {
0994:                        final int indexStartField = index = pos.getIndex();
0995:                        while (index < length
0996:                                && Character.isSpaceChar(source.charAt(index))) {
0997:                            index++;
0998:                        }
0999:                        if (!spaceAsSeparator && index != indexStartField) {
1000:                            break BigBoss;
1001:                        }
1002:                        pos.setIndex(index);
1003:                        fieldObject = numberFormat.parse(source, pos);
1004:                        if (fieldObject == null) {
1005:                            pos.setIndex(indexStartField);
1006:                            break BigBoss;
1007:                        }
1008:                        indexEndField = pos.getIndex();
1009:                        minutes = fieldObject.doubleValue();
1010:                        switch (skipSuffix(source, pos,
1011:                                (width1 != 0) ? MINUTES_FIELD : PREFIX_FIELD)) {
1012:                        /* ------------------------------------------------
1013:                         * ANALYSE DU SYMBOLE SUIVANT LES PRÉSUMÉES MINUTES
1014:                         * ------------------------------------------------
1015:                         * Le symbole trouvé est bel et bien celui des minutes.
1016:                         * On continuera le bloc pour tenter de lire les secondes.
1017:                         */
1018:                        case MINUTES_FIELD: {
1019:                            break; // continue outer switch
1020:                        }
1021:                            /* ------------------------------------------------
1022:                             * ANALYSE DU SYMBOLE SUIVANT LES PRÉSUMÉES MINUTES
1023:                             * ------------------------------------------------
1024:                             * Un symbole des secondes a été trouvé au lieu du symbole des minutes
1025:                             * attendu. On fera la modification dans les variables 'secondes' et
1026:                             * 'minutes' et on considèrera la lecture terminée.
1027:                             */
1028:                        case SECONDS_FIELD: {
1029:                            secondes = minutes;
1030:                            minutes = Double.NaN;
1031:                            break BigBoss;
1032:                        }
1033:                            /* ------------------------------------------------
1034:                             * ANALYSE DU SYMBOLE SUIVANT LES PRÉSUMÉES MINUTES
1035:                             * ------------------------------------------------
1036:                             * Aucun symbole n'a été trouvé. Les minutes étaient-elles attendues?
1037:                             * Si oui, on les acceptera et on tentera de lire les secondes. Si non,
1038:                             * on retourne le texte lu dans le buffer et on termine la lecture.
1039:                             */
1040:                        default: {
1041:                            if (width1 != 0)
1042:                                break; // Continue outer switch
1043:                            // fall through
1044:                        }
1045:                            /* ------------------------------------------------
1046:                             * ANALYSE DU SYMBOLE SUIVANT LES PRÉSUMÉES MINUTES
1047:                             * ------------------------------------------------
1048:                             * Au lieu des minutes, le symbole lu est celui des degrés. On considère
1049:                             * qu'il appartient au prochain angle. On retournera donc le texte lu dans
1050:                             * le buffer et on terminera la lecture.
1051:                             */
1052:                        case DEGREES_FIELD: {
1053:                            pos.setIndex(indexStartField);
1054:                            minutes = Double.NaN;
1055:                            break BigBoss;
1056:                        }
1057:                            /* ------------------------------------------------
1058:                             * ANALYSE DU SYMBOLE SUIVANT LES PRÉSUMÉES MINUTES
1059:                             * ------------------------------------------------
1060:                             * Après les minutes (qu'on accepte), on a trouvé le préfix du prochain
1061:                             * angle à lire. On retourne ce préfix dans le buffer et on considère la
1062:                             * lecture terminée.
1063:                             */
1064:                        case PREFIX_FIELD: {
1065:                            pos.setIndex(indexEndField);
1066:                            break BigBoss;
1067:                        }
1068:                        }
1069:                        swapDM = false;
1070:                        // fall through
1071:                    }
1072:                        /* ----------------------------------------------
1073:                         * ANALYSE DU SYMBOLE SUIVANT LES PRÉSUMÉS DEGRÉS
1074:                         * ----------------------------------------------
1075:                         * Un symbole des minutes a été trouvé au lieu du symbole des degrés attendu.
1076:                         * On fera donc la modification dans les variables 'degrés' et 'minutes'. Ces
1077:                         * minutes sont peut-être suivies des secondes. On tentera donc de lire le
1078:                         * prochain nombre.
1079:                         */
1080:                    case MINUTES_FIELD: {
1081:                        if (swapDM) {
1082:                            minutes = degrees;
1083:                            degrees = Double.NaN;
1084:                        }
1085:                        final int indexStartField = index = pos.getIndex();
1086:                        while (index < length
1087:                                && Character.isSpaceChar(source.charAt(index))) {
1088:                            index++;
1089:                        }
1090:                        if (!spaceAsSeparator && index != indexStartField) {
1091:                            break BigBoss;
1092:                        }
1093:                        pos.setIndex(index);
1094:                        fieldObject = numberFormat.parse(source, pos);
1095:                        if (fieldObject == null) {
1096:                            pos.setIndex(indexStartField);
1097:                            break;
1098:                        }
1099:                        indexEndField = pos.getIndex();
1100:                        secondes = fieldObject.doubleValue();
1101:                        switch (skipSuffix(source, pos,
1102:                                (width2 != 0) ? MINUTES_FIELD : PREFIX_FIELD)) {
1103:                        /* -------------------------------------------------
1104:                         * ANALYSE DU SYMBOLE SUIVANT LES PRÉSUMÉES SECONDES
1105:                         * -------------------------------------------------
1106:                         * Un symbole des secondes explicite a été trouvée.
1107:                         * La lecture est donc terminée.
1108:                         */
1109:                        case SECONDS_FIELD: {
1110:                            break;
1111:                        }
1112:                            /* -------------------------------------------------
1113:                             * ANALYSE DU SYMBOLE SUIVANT LES PRÉSUMÉES SECONDES
1114:                             * -------------------------------------------------
1115:                             * Aucun symbole n'a été trouvée. Attendait-on des secondes? Si oui, les
1116:                             * secondes seront acceptées. Sinon, elles seront retournées au buffer.
1117:                             */
1118:                        default: {
1119:                            if (width2 != 0)
1120:                                break;
1121:                            // fall through
1122:                        }
1123:                            /* -------------------------------------------------
1124:                             * ANALYSE DU SYMBOLE SUIVANT LES PRÉSUMÉES SECONDES
1125:                             * -------------------------------------------------
1126:                             * Au lieu des degrés, on a trouvé un symbole des minutes ou des
1127:                             * secondes. On renvoie donc le nombre et son symbole dans le buffer.
1128:                             */
1129:                        case MINUTES_FIELD:
1130:                        case DEGREES_FIELD: {
1131:                            pos.setIndex(indexStartField);
1132:                            secondes = Double.NaN;
1133:                            break;
1134:                        }
1135:                            /* -------------------------------------------------
1136:                             * ANALYSE DU SYMBOLE SUIVANT LES PRÉSUMÉES SECONDES
1137:                             * -------------------------------------------------
1138:                             * Après les secondes (qu'on accepte), on a trouvé le préfix du prochain
1139:                             * angle à lire. On retourne ce préfix dans le buffer et on considère la
1140:                             * lecture terminée.
1141:                             */
1142:                        case PREFIX_FIELD: {
1143:                            pos.setIndex(indexEndField);
1144:                            break BigBoss;
1145:                        }
1146:                        }
1147:                        break;
1148:                    }
1149:                    }
1150:                }
1151:                ////////////////////////////////////////////////////////////////////
1152:                // BLOC B: Prend en compte l'éventualité ou le séparateur décimal //
1153:                //         aurrait été absent, puis calcule l'angle en degrés.    //
1154:                ////////////////////////////////////////////////////////////////////
1155:                if (minutes < 0) {
1156:                    secondes = -secondes;
1157:                }
1158:                if (degrees < 0) {
1159:                    minutes = -minutes;
1160:                    secondes = -secondes;
1161:                }
1162:                if (!decimalSeparator) {
1163:                    final double facteur = XMath.pow10(widthDecimal);
1164:                    if (width2 != 0) {
1165:                        if (suffix1 == null && Double.isNaN(secondes)) {
1166:                            if (suffix0 == null && Double.isNaN(minutes)) {
1167:                                degrees /= facteur;
1168:                            } else {
1169:                                minutes /= facteur;
1170:                            }
1171:                        } else {
1172:                            secondes /= facteur;
1173:                        }
1174:                    } else if (Double.isNaN(secondes)) {
1175:                        if (width1 != 0) {
1176:                            if (suffix0 == null && Double.isNaN(minutes)) {
1177:                                degrees /= facteur;
1178:                            } else {
1179:                                minutes /= facteur;
1180:                            }
1181:                        } else if (Double.isNaN(minutes)) {
1182:                            degrees /= facteur;
1183:                        }
1184:                    }
1185:                }
1186:                /*
1187:                 * S'il n'y a rien qui permet de séparer les degrés des minutes (par exemple si
1188:                 * le patron est "DDDMMmmm"), alors la variable 'degrés' englobe à la fois les
1189:                 * degrés, les minutes et d'éventuelles secondes. On applique une correction ici.
1190:                 */
1191:                if (suffix1 == null && width2 != 0 && Double.isNaN(secondes)) {
1192:                    double facteur = XMath.pow10(width2);
1193:                    if (suffix0 == null && width1 != 0 && Double.isNaN(minutes)) {
1194:                        ///////////////////
1195:                        //// DDDMMSS.s ////
1196:                        ///////////////////
1197:                        secondes = degrees;
1198:                        minutes = (int) (degrees / facteur); // Arrondie vers 0
1199:                        secondes -= minutes * facteur;
1200:                        facteur = XMath.pow10(width1);
1201:                        degrees = (int) (minutes / facteur); // Arrondie vers 0
1202:                        minutes -= degrees * facteur;
1203:                    } else {
1204:                        ////////////////////
1205:                        //// DDD°MMSS.s ////
1206:                        ////////////////////
1207:                        secondes = minutes;
1208:                        minutes = (int) (minutes / facteur); // Arrondie vers 0
1209:                        secondes -= minutes * facteur;
1210:                    }
1211:                } else if (suffix0 == null && width1 != 0
1212:                        && Double.isNaN(minutes)) {
1213:                    /////////////////
1214:                    //// DDDMM.m ////
1215:                    /////////////////
1216:                    final double facteur = XMath.pow10(width1);
1217:                    minutes = degrees;
1218:                    degrees = (int) (degrees / facteur); // Arrondie vers 0
1219:                    minutes -= degrees * facteur;
1220:                }
1221:                pos.setErrorIndex(-1);
1222:                if (Double.isNaN(degrees))
1223:                    degrees = 0;
1224:                if (!Double.isNaN(minutes))
1225:                    degrees += minutes / 60;
1226:                if (!Double.isNaN(secondes))
1227:                    degrees += secondes / 3600;
1228:                /////////////////////////////////////////////////////
1229:                // BLOC C: Vérifie maintenant si l'angle ne serait //
1230:                //         pas suivit d'un symbole N, S, E ou W.   //
1231:                /////////////////////////////////////////////////////
1232:                for (int index = pos.getIndex(); index < length; index++) {
1233:                    final char c = source.charAt(index);
1234:                    switch (Character.toUpperCase(c)) {
1235:                    case NORTH:
1236:                        pos.setIndex(index + 1);
1237:                        return new Latitude(degrees);
1238:                    case SOUTH:
1239:                        pos.setIndex(index + 1);
1240:                        return new Latitude(-degrees);
1241:                    case EAST:
1242:                        pos.setIndex(index + 1);
1243:                        return new Longitude(degrees);
1244:                    case WEST:
1245:                        pos.setIndex(index + 1);
1246:                        return new Longitude(-degrees);
1247:                    }
1248:                    if (!Character.isSpaceChar(c)) {
1249:                        break;
1250:                    }
1251:                }
1252:                return new Angle(degrees);
1253:            }
1254:
1255:            /**
1256:             * Parse a string as an angle.
1257:             *
1258:             * @param  source The string to parse.
1259:             * @return The parsed string as an {@link Angle}, {@link Latitude}
1260:             *         or {@link Longitude} object.
1261:             * @throws ParseException if the string has not been fully parsed.
1262:             */
1263:            public Angle parse(final String source) throws ParseException {
1264:                final ParsePosition pos = new ParsePosition(0);
1265:                final Angle ang = parse(source, pos, true);
1266:                checkComplete(source, pos, false);
1267:                return ang;
1268:            }
1269:
1270:            /**
1271:             * Parse a substring as an angle. Default implementation invokes
1272:             * {@link #parse(String, ParsePosition)}.
1273:             *
1274:             * @param source A String whose beginning should be parsed.
1275:             * @param pos    Position where to start parsing.
1276:             * @return       The parsed string as an {@link Angle},
1277:             *               {@link Latitude} or {@link Longitude} object.
1278:             */
1279:            public Object parseObject(final String source,
1280:                    final ParsePosition pos) {
1281:                return parse(source, pos);
1282:            }
1283:
1284:            /**
1285:             * Parse a string as an object. Default implementation invokes
1286:             * {@link #parse(String)}.
1287:             *
1288:             * @param  source The string to parse.
1289:             * @return The parsed string as an {@link Angle}, {@link Latitude} or
1290:             *        {@link Longitude} object.
1291:             * @throws ParseException if the string has not been fully parsed.
1292:             */
1293:            public Object parseObject(final String source)
1294:                    throws ParseException {
1295:                return parse(source);
1296:            }
1297:
1298:            /**
1299:             * Interprète une chaîne de caractères qui devrait représenter un nombre.
1300:             * Cette méthode est utile pour lire une altitude après les angles.
1301:             *
1302:             * @param  source Chaîne de caractères à interpréter.
1303:             * @param  pos    Position à partir d'où commencer l'interprétation
1304:             *                de la chaîne {@code source}.
1305:             * @return Le nombre lu comme objet {@link Number}.
1306:             */
1307:            final Number parseNumber(final String source,
1308:                    final ParsePosition pos) {
1309:                return numberFormat.parse(source, pos);
1310:            }
1311:
1312:            /**
1313:             * Vérifie si l'interprétation d'une chaîne de caractères a été complète.
1314:             * Si ce n'était pas le cas, lance une exception avec un message d'erreur
1315:             * soulignant les caractères problématiques.
1316:             *
1317:             * @param  source Chaîne de caractères qui était à interpréter.
1318:             * @param  pos Position à laquelle s'est terminée l'interprétation de la
1319:             *         chaîne {@code source}.
1320:             * @param  isCoordinate {@code false} si on interprétait un angle,
1321:             *         ou {@code true} si on interprétait une coordonnée.
1322:             * @throws ParseException Si la chaîne {@code source} n'a pas été
1323:             *         interprétée dans sa totalité.
1324:             */
1325:            static void checkComplete(final String source,
1326:                    final ParsePosition pos, final boolean isCoordinate)
1327:                    throws ParseException {
1328:                final int length = source.length();
1329:                final int origin = pos.getIndex();
1330:                for (int index = origin; index < length; index++) {
1331:                    if (!Character.isWhitespace(source.charAt(index))) {
1332:                        index = pos.getErrorIndex();
1333:                        if (index < 0)
1334:                            index = origin;
1335:                        int lower = index;
1336:                        while (lower < length
1337:                                && Character.isWhitespace(source.charAt(lower))) {
1338:                            lower++;
1339:                        }
1340:                        int upper = lower;
1341:                        while (upper < length
1342:                                && !Character
1343:                                        .isWhitespace(source.charAt(upper))) {
1344:                            upper++;
1345:                        }
1346:                        throw new ParseException(Errors.format(
1347:                                ErrorKeys.UNPARSABLE_STRING_$2, source, source
1348:                                        .substring(lower, Math.min(lower + 10,
1349:                                                upper))), index);
1350:                    }
1351:                }
1352:            }
1353:
1354:            /**
1355:             * Returns a "hash value" for this object.
1356:             */
1357:            public synchronized int hashCode() {
1358:                int c = 78236951;
1359:                if (decimalSeparator)
1360:                    c ^= 0xFF;
1361:                if (prefix != null)
1362:                    c ^= prefix.hashCode();
1363:                if (suffix0 != null)
1364:                    c = c * 37 + suffix0.hashCode();
1365:                if (suffix1 != null)
1366:                    c ^= c * 37 + suffix1.hashCode();
1367:                if (suffix2 != null)
1368:                    c ^= c * 37 + suffix2.hashCode();
1369:                return c ^ (((((width0 << 8) ^ width1) << 8) ^ width2) << 8)
1370:                        ^ widthDecimal;
1371:            }
1372:
1373:            /**
1374:             * Compare this format with the specified object for equality.
1375:             */
1376:            public synchronized boolean equals(final Object obj) {
1377:                // On ne peut pas synchroniser "obj" si on ne veut
1378:                // pas risquer un "deadlock". Voir RFE #4210659.
1379:                if (obj == this ) {
1380:                    return true;
1381:                }
1382:                if (obj != null && getClass().equals(obj.getClass())) {
1383:                    final AngleFormat cast = (AngleFormat) obj;
1384:                    return width0 == cast.width0
1385:                            && width1 == cast.width1
1386:                            && width2 == cast.width2
1387:                            && widthDecimal == cast.widthDecimal
1388:                            && decimalSeparator == cast.decimalSeparator
1389:                            && Utilities.equals(prefix, cast.prefix)
1390:                            && Utilities.equals(suffix0, cast.suffix0)
1391:                            && Utilities.equals(suffix1, cast.suffix1)
1392:                            && Utilities.equals(suffix2, cast.suffix2)
1393:                            && Utilities
1394:                                    .equals(numberFormat
1395:                                            .getDecimalFormatSymbols(),
1396:                                            cast.numberFormat
1397:                                                    .getDecimalFormatSymbols());
1398:                } else {
1399:                    return false;
1400:                }
1401:            }
1402:
1403:            /**
1404:             * Returns a string representation of this object.
1405:             */
1406:            public String toString() {
1407:                return Utilities.getShortClassName(this ) + '[' + toPattern()
1408:                        + ']';
1409:            }
1410:        }
www.java2java.com | Contact Us
Copyright 2009 - 12 Demo Source and Support. All rights reserved.
All other trademarks are property of their respective owners.