001 /*
002 Copyright (c) 1996-2012, Damon Hart-Davis
003 All rights reserved.
004
005 Redistribution and use in source and binary forms, with or without
006 modification, are permitted provided that the following conditions are
007 met:
008
009 * Redistributions of source code must retain the above copyright
010 notice, this list of conditions and the following disclaimer.
011
012 * Redistributions in binary form must reproduce the above copyright
013 notice, this list of conditions and the following disclaimer in the
014 documentation and/or other materials provided with the
015 distribution.
016
017 THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
018 IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
019 TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
020 PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
021 OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
022 SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
023 LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
024 DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
025 THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
026 (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
027 OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
028 */
029
030 package org.hd.d.pg2k.svrCore.location;
031
032 import java.io.BufferedInputStream;
033 import java.io.File;
034 import java.io.FileInputStream;
035 import java.io.IOException;
036 import java.io.InputStream;
037 import java.io.Serializable;
038 import java.util.Properties;
039
040 import org.hd.d.pg2k.svrCore.MemoryTools;
041 import org.hd.d.pg2k.svrCore.PGException;
042
043 /**Classes to hold (time and space) location data on an item.
044 * Such location data is immutable.
045 * <p>
046 * This is not considered an attribute of an item in the Gallery,
047 * but instead data associated with such an item. Many may not
048 * have this data at all.
049 * <p>
050 * Location data is read from a Properties map using
051 * Location.Base.buildFromProperties() to construct the
052 * appropriate subtype of Base to contain the data (or
053 * throw an exception if there is one). See the routine's
054 * description for the properties used.
055 * <p>
056 * Note that while location data read for a specific exhibit
057 * is taken to be `specific' else not, this can be overridden
058 * by use of the `specific' flag which takes a boolean ``true''
059 * or ``false'' value.
060 * <p>
061 * TODO: needs object verification after deserialisation
062 * and mutable values need to be cloned.
063 *
064 * @author Damon Hart-Davis
065 * @version 1.41 01/08/14
066 */
067 public final class Location
068 {
069 /**Don't construct instances of this container class. */
070 private Location() { }
071
072 /**Base class of location value with error bounds.
073 * The value is the mid-point of range of the value,
074 * and the error is the width of the interval, ON EITHER SIDE OF
075 * the value (the mid-point), encompassing both any actual `duration'
076 * or `size' of the item and any error or uncertainty in
077 * the value. Thus the value can be shown as +/- the error.
078 * <p>
079 * The error values returned must always be non-negative
080 * (zero is allowed).
081 * <p>
082 * This abstract class can be derived to store the units
083 * in various forms. All derived classes must be immutable,
084 * so we will only allow it to be constructed by well-behaved
085 * sub-classes that use guaranteed-immutable Number args,
086 * and that also ensure that the error value is non-negative.
087 * <p>
088 * This and any deriving classes should be immutable,
089 * serialisable, and support equals() and hashCode().
090 * <p>
091 * TODO: should validate on deserialisation.
092 */
093 public static abstract class ValueAndBounds implements Serializable
094 {
095 /**Construct a new value.
096 * The constructor visibility limits deriving classes to this package.
097 * This helps ensure that we can guarantee features sush as
098 * immutability.
099 */
100 ValueAndBounds(final Number v, final Number e)
101 {
102 if((v == null) || (e == null))
103 { throw new IllegalArgumentException("null arg"); }
104 value = v; error = e;
105 }
106
107 /**Central value; never null. */
108 public final Number value;
109 /**Error bounds; never null. */
110 public final Number error;
111
112 /**Currently depends on only value for speed. */
113 @Override
114 public int hashCode()
115 { return(value.hashCode()); }
116
117 /**Depends on equality of value and error. */
118 @Override
119 public boolean equals(final Object obj)
120 {
121 if(!(obj instanceof ValueAndBounds)) { return(false); }
122 final ValueAndBounds other = (ValueAndBounds) obj;
123 return((value.equals(other.value)) &&
124 (error.equals(other.error)));
125 }
126
127 /**Unique Serialisation class ID generated by http://random.hd.org/. */
128 private static final long serialVersionUID = 1802455018686196426L;
129 }
130
131 /**Value-and-error stored as longs, eg for time.
132 * TODO: should do validation on deserialisation.
133 */
134 public static final class LongValueAndBounds extends ValueAndBounds
135 {
136 /**Make new bounded value. */
137 public LongValueAndBounds(final long _val, final long _err)
138 {
139 super(new Long(_val), new Long(_err));
140 if(_err < 0)
141 { throw new IllegalArgumentException("-ve error"); }
142 }
143 /**Make new bounded value. */
144 public LongValueAndBounds(final Long _val, final Long _err)
145 {
146 super(_val, _err);
147 if(_err.longValue() < 0)
148 { throw new IllegalArgumentException("-ve error"); }
149 }
150
151 /**Unique Serialisation class ID generated by http://random.hd.org/. */
152 private static final long serialVersionUID = 4934329592740791248L;
153 }
154
155 /**Value-and-error stored as doubles, eg for place.
156 * TODO: should do validation on deserialisation.
157 */
158 public static final class DoubleValueAndBounds extends ValueAndBounds
159 {
160 /**Make new bounded value. */
161 public DoubleValueAndBounds(final double _val, final double _err)
162 {
163 super(new Double(_val), new Double(_err));
164 if(_err < 0)
165 { throw new IllegalArgumentException("-ve error"); }
166 }
167 /**Make new bounded value. */
168 public DoubleValueAndBounds(final Double _val, final Double _err)
169 {
170 super(_val, _err);
171 if(_err.doubleValue() < 0)
172 { throw new IllegalArgumentException("-ve error"); }
173 }
174
175 /**Unique Serialisation class ID generated by http://random.hd.org/. */
176 private static final long serialVersionUID = -666396322028089442L;
177 }
178
179 /**Base of all location classes.
180 * All such classes contain an optional time value
181 * (with error bounds), and flag to indicate if this
182 * location information was specific to this collection
183 * item, or applied to a group of items.
184 * <p>
185 * A true value for `specific' says that the item had
186 * its own time info rather than inheriting a general value.
187 * <p>
188 * If the time value is zero it indicates that none is known.
189 * (If the time value is zero, so should its error bound be.)
190 * <p>
191 * All classes derived from this should be immutable
192 * and serialisable and support equals() and hashCode().
193 * <p>
194 * TODO: should do validation on deserialisation.
195 */
196 public static abstract class Base implements java.io.Serializable, MemoryTools.Internable
197 {
198 /**Build new base from properties.
199 * Looks for the type field, which must be present,
200 * and the optional time fields.
201 * <p>
202 * Note that the _specific parameter is overridden
203 * where a `specific' property is present.
204 * <p>
205 * Package-visible to restrict the range of derived classes.
206 *
207 * @param _specific is true if the location info was specifically
208 * for this item, else false to indicate that
209 * this location info was inherited from some
210 * more general source
211 */
212 protected Base(final boolean _specific,
213 final String prefix,
214 final Properties p)
215 throws PGException
216 {
217 type = p.getProperty(prefix + typeKey);
218 if(type == null)
219 { throw new PGException("missing type field: " + prefix + typeKey); }
220 final Boolean pSpec = getBooleanProp(prefix, p, specificKey, false);
221 specific = ((pSpec == null) ? _specific : pSpec.booleanValue());
222 }
223 /**The type of this location info. */
224 public final String type;
225 /**Did this item have its own specific location info? */
226 public final boolean specific;
227
228 /**Hash depends on type and "specific" flag. */
229 @Override
230 public int hashCode()
231 { return(type.hashCode() ^ (specific ? 0 : 5)); }
232
233 /**Hash depends on equality of type and "specific" flags.
234 * A derived class should probably make use of this recursively.
235 */
236 @Override
237 public boolean equals(final Object obj)
238 {
239 if(obj == this) { return(true); }
240 if(!(obj instanceof Base)) { return(false); }
241 final Base other = (Base) obj;
242 return((specific == other.specific) &&
243 (type.equals(other.type)));
244 }
245
246 /**Generate terse, human readable portion of location info.
247 * This should be overridden by derived classes, but
248 * with this used (via super.toString()) to generate the
249 * prefix for the result.
250 * <p>
251 * The string generated by this routine contains no
252 * white space to make the result handlable as one token
253 * and is shell- and HTML- friendly, with portions separated
254 * by colons, and deriving classes must abide by this
255 * too.
256 * <p>
257 * This generates a string consisting of the type name
258 * followed by a colon then `s' for a specific location or
259 * `g' for a generic one.
260 */
261 @Override
262 public String toString()
263 { return(type + ':' + (specific ? 's' : 'g')); }
264
265
266 /**Factory method to build a class from a property set; never returns null.
267 * The Properties map passed is only read (not updated),
268 * and consists of a number of keys prefixed with
269 * the given prefix (either the empty string, or
270 * a fully-qualified name prefix, ending with ".").
271 * <p>
272 * Whatever type of map is being built, there must
273 * always be the key given by the prefix + typeKey,
274 * which must contain the type of location info, and
275 * that type must be the name of the class in this
276 * Location outer class, derived from Base, that holds
277 * the location info.
278 * <p>
279 * For robustness, if we cannot use the location info
280 * for some reason, we should probably pretend that the
281 * item has none, though flag the problem to stderr.
282 *
283 * @exception PGException in case of inability to
284 * build a location value from the specified
285 * properties
286 */
287 public static Base buildFromProperties(final boolean specific,
288 final String prefix,
289 final Properties p)
290 throws PGException
291 {
292 // Find the type.
293 final String type = p.getProperty(prefix + typeKey);
294 if(type == null)
295 { throw new PGException("missing type field: " + prefix + typeKey); }
296
297 // Try to find the class indicated by the type...
298 // Do this by hand rather than by pure reflection.
299 Class<? extends Base> c;
300 if("Base".equals(type)) { c = Base.class; }
301 else if("None".equals(type)) { c = None.class; }
302 else if("Time".equals(type)) { c = Time.class; }
303 else if("Estd".equals(type)) { c = Estd.class; }
304 else
305 { throw new PGException("unknown location type (class not found): " + type); }
306
307 // Try to find the constructor we will need...
308 final Class<?> argTypes[] =
309 { boolean.class, String.class, Properties.class };
310 java.lang.reflect.Constructor<? extends Base> cons;
311 try { cons = c.getConstructor(argTypes); }
312 catch(final NoSuchMethodException e)
313 { throw new PGException("unsuitable location type (no such method): " + type); }
314 if(cons == null)
315 { throw new PGException("unsuitable location type (no constructor): " + type); }
316
317 // Now try and make an instance...
318 // We try to instance-control the values we create to help conserve memory.
319 final Object args[] =
320 { specific ? Boolean.TRUE : Boolean.FALSE, prefix, p };
321 final Object o;
322 try { o = cons.newInstance(args); }
323 catch(final ThreadDeath t) { throw t; } // Die on command.
324 catch(final Throwable t)
325 {
326 t.printStackTrace();
327 throw new PGException("uninstantiable location type (unexpected exception): " + type + ": " + t.getMessage());
328 }
329 if(!(o instanceof Base))
330 { throw new PGException("unknown location type (bad class): " + type); }
331
332 return(MemoryTools.intern((Base) o)); // All done!
333 }
334 /**Build from properties with empty prefix. */
335 public static Base buildFromProperties(final boolean specific, final Properties p)
336 throws PGException
337 { return(buildFromProperties(specific, "", p)); }
338 /**Build directly from a file, assuming empty prefix. */
339 public static Base buildFromFile(final boolean specific, final File f)
340 throws IOException, PGException
341 {
342 final InputStream is = new BufferedInputStream(new FileInputStream(f));
343 // if(is == null) { throw new IOException("could not open location properties file: " + f); }
344 try {
345 final Properties p = new Properties();
346 p.load(is);
347 return(buildFromProperties(specific, p));
348 }
349 finally { is.close(); }
350 }
351 /**Attempts to build location from file.
352 * Returns Location.NONE type in case of difficulty.
353 */
354 public static Base buildFromFileOrNONE(final boolean specific, final File f)
355 {
356 if(!f.exists()) { return(Location.NONE); }
357 try {
358 final Base result = buildFromFile(specific, f);
359 //System.err.println(" " + f + ": OK");
360 return(result);
361 }
362 catch(final PGException e)
363 {
364 System.err.println(" " + f + ": pretending no location info: " + e);
365 return(Location.NONE);
366 }
367 catch(final IOException e)
368 {
369 System.err.println(" " + f + ": IO problem: pretending no location info: " + e);
370 return(Location.NONE);
371 }
372 }
373
374 /**Name of key giving location type in Properties map. */
375 public static final String typeKey = "type";
376
377 /**Name of key giving specificity in Properties map. */
378 public static final String specificKey = "specific";
379
380
381 /**Routine to get a double property.
382 * Throws a PGException if there is a problem.
383 * <p>
384 * Can be asked to veto negative values, eg for error margins.
385 */
386 public static final Double getDoubleProp(final String prefix, final Properties p,
387 final String prop, final boolean rejectNegative)
388 throws PGException
389 {
390 final String v = p.getProperty(prefix + prop);
391 if(v == null) { throw new PGException("missing property " + prop); }
392 try {
393 final Double result = new Double(v);
394 if(!rejectNegative) { return(result); }
395 if(result.doubleValue() < 0)
396 { throw new PGException("illegal negative value for property: " + prop); }
397 return(result);
398 }
399 catch(final NumberFormatException e)
400 { throw new PGException("unparsable (double) property: " + prop); }
401 }
402 /**Get pair of double properties; requested one and its error interval.
403 * If the specified property is <i>X</i>, the main property is
404 * <i>X</i>Err.
405 * <p>
406 * If mandatory and either property is missing, a PGException is
407 * thrown.
408 * <p>
409 * If optional and the first property is missing, null is returned;
410 * but if the first property is present and the second is absent
411 * then a PGException is thrown.
412 */
413 public static final DoubleValueAndBounds getDoubleProps(final String prefix, final Properties p,
414 final String prop,
415 final boolean mandatory)
416 throws PGException
417 {
418 if(!mandatory && (p.getProperty(prefix + prop) == null)) { return(null); }
419 return(new DoubleValueAndBounds(
420 getDoubleProp(prefix, p, prop, false),
421 getDoubleProp(prefix, p, prop + "Err", true)));
422 }
423
424
425 /**Routine to get a boolean property.
426 * Throws a PGException if there is a problem.
427 */
428 public static final Boolean getBooleanProp(final String prefix, final Properties p,
429 final String prop, final boolean mandatory)
430 throws PGException
431 {
432 final String v = p.getProperty(prefix + prop);
433 if(v == null)
434 {
435 if(!mandatory) { return(null); }
436 throw new PGException("missing property " + prop);
437 }
438 final Boolean result = Boolean.valueOf(v);
439 return(result);
440 }
441
442
443 /**Routine to get a double property.
444 * Throws a PGException if there is a problem.
445 * <p>
446 * Can be asked to veto negative values, eg for error margins.
447 */
448 public static final Long getLongProp(final String prefix, final Properties p,
449 final String prop, final boolean rejectNegative)
450 throws PGException
451 {
452 final String v = p.getProperty(prefix + prop);
453 if(v == null) { throw new PGException("missing property " + prop); }
454 try {
455 final Long result = new Long(v);
456 if(!rejectNegative) { return(result); }
457 if(result.longValue() < 0)
458 { throw new PGException("illegal negative value for property: " + prop); }
459 return(result);
460 }
461 catch(final NumberFormatException e)
462 { throw new PGException("unparsable (long) property: " + prop); }
463 }
464
465 /**Get pair of long properties; requested one and its error interval.
466 * If the specified property is <i>X</i>, the main property is
467 * <i>X</i>Err.
468 * <p>
469 * If mandatory and either property is missing, a PGException is
470 * thrown.
471 * <p>
472 * If optional and the first property is missing, null is returned;
473 * but if the first property is present and the second absent a
474 * PGException is thrown.
475 */
476 public static final LongValueAndBounds getLongProps(final String prefix, final Properties p,
477 final String prop,
478 final boolean mandatory)
479 throws PGException
480 {
481 if(!mandatory && (p.getProperty(prefix + prop) == null)) { return(null); }
482 return(new LongValueAndBounds(
483 getLongProp(prefix, p, prop, false),
484 getLongProp(prefix, p, prop + "Err", true)));
485 }
486
487
488 /**Unique Serialisation class ID generated by http://random.hd.org/. */
489 private static final long serialVersionUID = -7784138110080680826L;
490 }
491
492 /**Singleton: represents an absence of location information. */
493 private static final class None extends Base
494 {
495 /**Special way to construct distinguished NONE value. */
496 private None(final Properties p)
497 throws PGException
498 { super(false, "", p); }
499
500 /**Make distinguished NONE value; only call this once. */
501 static None makeNone()
502 {
503 try {
504 final Properties p = new Properties();
505 p.put(typeKey, "None"); // Sneakily add a property...
506 return(new None(p));
507 }
508 catch(final PGException e)
509 { throw new RuntimeException(e.getMessage()); }
510 }
511
512 /**Ensure that there is at most one instance of NONE. */
513 private Object readResolve()
514 { return(NONE); }
515
516 /**Unique Serialisation class ID generated by http://random.hd.org/. */
517 private static final long serialVersionUID = 1900944139123394635L;
518 }
519
520 /**Single distinguished value meaning `no location info'. */
521 public static final Base NONE = None.makeNone();
522
523 /**Just captures time; designed to be derived from.
524 * TODO: needs to validate on deserialisation.
525 */
526 public static class Time extends Location.Base
527 {
528 public Time(final boolean _specific,
529 final String prefix,
530 final Properties p)
531 throws PGException
532 {
533 // Get the base class initialised.
534 super(_specific, prefix, p);
535
536 // Get optional Time prop.
537 // TODO: Needs to accept YYYY[/MM[/DD [hh:[mm:[ss[.ddd]]]]]]
538 // time format (UTC) plus nn[Y|M|D|h|m|s|ms] error, as
539 // well as raw ms values for both.
540 LongValueAndBounds t = null;
541 try { t = getLongProps(prefix, p, "Time", false); }
542 catch(final PGException e)
543 {
544 System.err.println("[WARNING: unable to parse Time field in location data; ignoring...]");
545 }
546 Time = t;
547 }
548
549 /**What time does the item record? */
550 private final LongValueAndBounds Time;
551 /**Get the time value; null means unknown. */
552 public final LongValueAndBounds getTime() { return(Time); }
553
554 /**Depends on fields from the base class and the time info, if any. */
555 @Override
556 public int hashCode()
557 {
558 return((super.hashCode() * 19) +
559 ((Time != null) ? Time.hashCode() : 1));
560 }
561
562 /**Depends on fields from the base class and the time info, if any. */
563 @Override
564 public boolean equals(final Object obj)
565 {
566 if(!(obj instanceof Time)) { return(false); }
567 final Time other = (Time) obj;
568
569 // Base class fields must match.
570 if(!super.equals(obj)) { return(false); }
571
572 // Time fields, if any, must match.
573 if(Time == null) { return(other.Time == null); }
574 else { return(Time.equals(other.Time)); }
575 }
576
577 /**Get value as a String. */
578 @Override
579 public String toString()
580 {
581 if(Time == null) { return(super.toString()); }
582 final StringBuilder sb = new StringBuilder();
583 sb.append(super.toString());
584 sb.append(':');
585 sb.append("TIME"); // Not implemented...
586 return(sb.toString());
587 }
588
589 /**Unique Serialisation class ID generated by http://random.hd.org/. */
590 private static final long serialVersionUID = 3902949361757128661L;
591 }
592
593 /**Earth-standard coordinates.
594 * Eastings and Northings, with optional altitude
595 * above mean sea level (in metres).
596 * <p>
597 * This class is immutable.
598 * <p>
599 * Time is handled by a base class if any.
600 * <p>
601 * TODO: needs to validate on deserialisation.
602 */
603 public static final class Estd extends Location.Time
604 {
605 /**Build Earth-standard coordinates in WGS84 space.
606 */
607 public Estd(final boolean _specific,
608 final String prefix,
609 final Properties p)
610 throws PGException
611 {
612 // Get the base class initialised.
613 super(_specific, prefix, p);
614
615 // Get mandatory E/N props.
616 E = getDoubleProps(prefix, p, "E", true);
617 N = getDoubleProps(prefix, p, "N", true);
618 // Get optional Alt prop.
619 Alt = getDoubleProps(prefix, p, "Alt", false);
620 }
621
622 /**Get maximum value for E (positive); minimum is negation of this. */
623 public static final int MAX_E = 180;
624 /**Get maximum value for N (positive); minimum is negation of this. */
625 public static final int MAX_N = 90;
626
627 /**How many degrees East are we? */
628 private final DoubleValueAndBounds E;
629 /**Get the Easting value; never null. */
630 public final DoubleValueAndBounds getE() { return(E); }
631
632 /**How many degrees North are we? */
633 private final DoubleValueAndBounds N;
634 /**Get the Northing value; never null. */
635 public final DoubleValueAndBounds getN() { return(N); }
636
637 /**How many metres above mean sea level are we? */
638 private final DoubleValueAndBounds Alt;
639 /**Get the altitude in metres; null indicates not known. */
640 public final DoubleValueAndBounds getAlt() { return(Alt); }
641
642 /**True if this Location is "bigger" than that of its argument.
643 * True if:
644 * <ul>
645 * <li>This strictly contains the area of the argument,
646 * <li>Or, this has a larger error area on the Mercator projection,
647 * ie NErr * EErr.
648 * </ul>
649 */
650 public boolean isLargerThan(final Location.Estd other)
651 {
652 // A completely contained area is smaller.
653 if(strictlyContainsArea(other)) { return(true); }
654 return(E.error.doubleValue() * N.error.doubleValue() >
655 other.E.error.doubleValue() * other.N.error.doubleValue());
656 }
657
658 /**True if this Location and bounding box contains the bounding box of the argument Location.
659 * Note that this is reflexive, ie a Location contains its own bounding box.
660 */
661 public boolean containsArea(final Location.Estd other)
662 {
663 if(equals(other)) { return(true); }
664
665 final double thisN = getN().value.doubleValue();
666 final double thisNErr = getN().error.doubleValue();
667 final double otherN = other.getN().value.doubleValue();
668 final double otherNErr = other.getN().error.doubleValue();
669
670 // If not in bounds for the North, not contained.
671 // Structured to fail if any of the numbers is NaN.
672 if(!((otherN - otherNErr >= thisN - thisNErr) && (otherN + otherNErr <= thisN + thisNErr)))
673 { return(false); }
674
675 final double thisE = getE().value.doubleValue();
676 final double thisEErr = getE().error.doubleValue();
677 final double otherE = other.getE().value.doubleValue();
678 final double otherEErr = other.getE().error.doubleValue();
679
680 // If not in bounds for the East, not contained.
681 // Structured to fail if any of the numbers is NaN.
682 if(!((otherE - otherEErr >= thisE - thisEErr) && (otherE + otherEErr <= thisE + thisEErr)))
683 { return(false); }
684
685 return(true); // Seems OK.
686 }
687
688 /**True if this Location and bounding box strictly contains the bounding box of the argument Location.
689 * Note that this is not reflexive, ie a Location not not strictly contain its own bounding box,
690 * since the contained item must be smaller,
691 * or equivalently contained but not equal.
692 */
693 public boolean strictlyContainsArea(final Location.Estd other)
694 {
695 if(equals(other)) { return(false); }
696 return(containsArea(other));
697 }
698
699 /**True if this Location and bounding box contains the centre of the argument Location.
700 * Note that this is reflexive, ie a Location contains its own centre.
701 */
702 public boolean containsCentre(final Location.Estd other)
703 {
704 final double thisN = getN().value.doubleValue();
705 final double thisNErr = getN().error.doubleValue();
706 final double otherN = other.getN().value.doubleValue();
707
708 // If not in bounds for the North, not contained.
709 // Structured to fail if any of the numbers is NaN.
710 if(!((otherN >= thisN - thisNErr) && (otherN <= thisN + thisNErr)))
711 { return(false); }
712
713 final double thisE = getE().value.doubleValue();
714 final double thisEErr = getE().error.doubleValue();
715 final double otherE = other.getE().value.doubleValue();
716
717 // If not in bounds for the East, not contained.
718 // Structured to fail if any of the numbers is NaN.
719 if(!((otherE >= thisE - thisEErr) && (otherE <= thisE + thisEErr)))
720 { return(false); }
721
722 return (true); // Seems OK.
723 }
724
725
726 /**Depends on base hash plus E and N values. */
727 @Override
728 public int hashCode()
729 {
730 return((super.hashCode() * 69069) ^
731 (N.hashCode() * 251) ^
732 (E.hashCode()));
733 }
734
735 /**Depends on all the elements of this class and base classes.
736 */
737 @Override
738 public boolean equals(final Object obj)
739 {
740 if(obj == this) { return(true); }
741 if(!(obj instanceof Estd)) { return(false); }
742 final Estd other = (Estd) obj;
743
744 // Base class fields must match.
745 if(!super.equals(obj)) { return(false); }
746
747 // Mandatory fields must match.
748 if(!N.equals(other.N)) { return(false); }
749 if(!E.equals(other.E)) { return(false); }
750
751 // Alt fields, if any, must match.
752 if(Alt == null) { return(other.Alt == null); }
753 else { return(Alt.equals(other.Alt)); }
754 }
755
756 /**Get value as a String. */
757 @Override
758 public String toString()
759 {
760 final StringBuilder sb = new StringBuilder();
761 sb.append(super.toString());
762 sb.append(':');
763 if(E.value.doubleValue() < 0)
764 { sb.append(-E.value.doubleValue()); sb.append('W'); }
765 else
766 { sb.append( E.value.doubleValue()); sb.append('E'); }
767 sb.append(',');
768 if(N.value.doubleValue() < 0)
769 { sb.append(-N.value.doubleValue()); sb.append('S'); }
770 else
771 { sb.append( N.value.doubleValue()); sb.append('N'); }
772 if(Alt != null)
773 {
774 sb.append(',');
775 sb.append(Alt.value.doubleValue());
776 sb.append('m');
777 }
778 // Put in error bounds if needed.
779 if((E.error.doubleValue() != 0) ||
780 (N.error.doubleValue() != 0) ||
781 ((Alt != null) && (Alt.error.doubleValue() != 0)))
782 {
783 sb.append("{+/-");
784 sb.append(E.error.doubleValue());
785 sb.append(',');
786 sb.append(N.error.doubleValue());
787 if(Alt != null) { sb.append(','); sb.append(Alt.error.doubleValue()); }
788 sb.append('}');
789 }
790 return(sb.toString());
791 }
792
793 /**"Whole Earth" value; not null. */
794 public static final Location.Estd WHOLE_EARTH;
795 /**Initialise "Whole Earth" value. */
796 static
797 {
798 final Properties p = new Properties();
799 p.setProperty("type", "Estd");
800 p.setProperty("E", "0");
801 p.setProperty("EErr", String.valueOf(MAX_E));
802 p.setProperty("N", "0");
803 p.setProperty("NErr", String.valueOf(MAX_N));
804 try { WHOLE_EARTH = new Estd(false, "", p); }
805 catch(final PGException e) { throw new Error("INTERNAL ERROR"); }
806 }
807
808
809 /**Unique Serialisation class ID generated by http://random.hd.org/. */
810 private static final long serialVersionUID = 3108417206234772844L;
811 }
812 }