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> The integer part of degrees</td></tr>
060 * <tr><td><code>d</code></td><td> The fractional part of degrees</td></tr>
061 * <tr><td><code>M</code></td><td> The integer part of minutes</td></tr>
062 * <tr><td><code>m</code></td><td> The fractional part of minutes</td></tr>
063 * <tr><td><code>S</code></td><td> The integer part of seconds</td></tr>
064 * <tr><td><code>s</code></td><td> The fractional part of seconds</td></tr>
065 * <tr><td><code>.</code></td><td> 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 }