Skip to content

Migrate Units to JSR 363

Jody Garnett edited this page Apr 23, 2018 · 26 revisions

Description

Previous proposal Replace JSR-275 Units Library looked at alternatives, one of which was to wait for JSR-363 to be approved! That day is now - the proposal was accepted and Units will be included in Java 10.3.

It appears that to work with Java 10.3 we will need to migrate to this Units API.

JSR-363 provides the following dependencies:

  • javax.measure.unit-api - API and Interfaces
  • tec.units - reference implementation, primarily used for mobile
  • tec.uom - intended for server and desktop use

This units library is being refined over time, JSR-385 is being prepared with some non-breaking changes (Focused on the SI System redefinition, modularity and support for Java SE 8/9 and above.)

References:

Measurable

One aspect of JSR-275 we used that does not have a direct parallel is the interface Measurable<Q extends Quantity> and its subclasses DecimalMeasure, VectorMeasure as used by the gt-coverage-api. Current idea is to reimplement these ideas for our Units utility class.

Update: This functionality was dropped after discussion on geotools-devel

Unit Equality

One common regression found during implementation was a difference in how Unit equality was handled, JSR-275 used floats to compare any conversion factors, allowing a wider range of values degree definitions to be recognized (2*PI/360, 0.0174532925199433, etc...). We experimented with hard coding these factors and using a epsilon check.

We eventually matched JSR275 functionality by comparing both units definitions to the base unit.

  • Converting the value 1.0 from unit "a" to the base unit
  • Then converting the result from the base unit to unit "b"
  • Confirming the result was equal to 1.0f

Using this technique we were able to automatically "match" unit definitions and pass the same unit tests as JSR275.

To help code making the transition additional utility methods have beed added to the GeoTools Units class.

Unit Formatting

Formatting and naming of units is fairly complicated, units have:

  • A symbol, example "m"
  • A name, example "metre"
  • One or more alias, example "meter"

We found that the unit.toString() implementation relied on SimpleUnitFormat.getInstance().format() - which contains the correct logic choose between symbol, alias and name.

To help code making the transition additional utility methods have beed added to the GeoTools Units class.

Assigned to Release

GeoTools 20

Status

Choose one of:

  • Under Discussion
  • In Progress
  • Completed
  • Rejected,
  • Deferred

Voting:

  • Andrea Aime: +1
  • Ben Caradoc-Davies: +1
  • Christian Mueller: +1
  • Ian Turton: +1
  • Justin Deoliveira:
  • Jody Garnett: +1
  • Simone Giannecchini: +0

Tasks

  1. CMI: (done) Update maven dependency to JSR-363 artifacts
  2. JG: (done) Change imports
  3. CMI: (done) Change API references
  4. CMI: (done) Bulk fix imports and erros based on API change
  5. JG: (done) Add to upgrade instructions for downstream projects upgrading page.
  6. JG: (done) Test GWC downstream
  7. CMI: (done) Test/Fix GeoServer downstream

API Change

Dependency Change

Before (single dependency for API and implementation):

  <dependency>
    <groupId>net.java.dev.jsr-275</groupId>
    <artifactId>jsr-275</artifactId>
    <version>1.0-beta-2</version>
  </dependency>

After (using transitive dependencies to pull in api and implementation):

<dependency>
   <groupId>systems.uom</groupId>
   <artifactId>systems-common-java8</artifactId>
   <version>0.7.2</version>
</dependency>

Search and Replace

  • import javax.measure.unit.Unit;

    replace: import javax.measure.Unit;

  • ConversionException

    replace: IncommensurableException

    This is a checked exception, the former ConversionException extended IllegalArgumentException. In areas of our code where this is found we will wrapper with an IllegalArgumentException to prevent GeoTools API change.

  • converter == UnitConverter.IDENTITY

    replace: converter.isIdentity

  • import javax.measure.unit.NonSI;

    replace: import si.uom.NonSI;

  • import javax.measure.unit.SI;

    replace: import si.uom.SI;

  • SI.METER

    replace: SI.METRE

  • import javax.measure.converter.UnitConverter;

    replace: import javax.measure.UnitConverter;

  • import javax.measure.unit.UnitFormat;

    replace: import javax.measure.format.UnitFormat;

  • Unit.ONE

    replace: AbstractUnit.ONE

  • Dimensionless.UNIT

    replace: AbstractUnit.ONE

  • Unit

    Replace: Unit

  • unit = Unit.valueOf(unitString);

Replace: SimpleUnitFormat.getInstance().parse(unitString)

API Upgrade

Before:

 import javax.measure.unit.NonSI;
 import javax.measure.unit.SI;
 ...
 public static final Unit<Angle> SEXAGESIMAL_DMS = NonSI.DEGREE_ANGLE.transform(...);
 public static final Unit<Distance> M = SI.METER;
 public static Unit<Time> MILLISECOND = SI.M(SI.SECOND);
 ...
 try {
   return Unit.valueOf(unitString);
 }
 catch( IllegalArgumentException illegal){
   return null;
 }

After:

 import si.uom.NonSI;
 import si.uom.SI;
 ...
 public static final Unit<Angle> SEXAGESIMAL_DMS = Unit.DEGREE_ANGLE.transform(...);
 public static final Unit<Distance> M = SI.METRE;
 private static final Unit<Time> MILLISECOND = MetricPrefix.MILLI(SI.SECOND);
 ...
 try {
   return SimpleFormat.getInstance().parse( unitString );
 }
 catch( javax.measure.format.ParserException illegal){
   return null;
 }

Remove gt-coverage-io references to Measure

The following unused classes had references to javax.measure.Measure and Measurable.

  • org.geotools.coverage.io.range.Axis
  • org.geotools.coverage.io.range.impl.BandIndexMeasure
  • org.geotools.coverage.io.range.impl.CodeMeasure
  • org.geotools.coverage.io.range.impl.DefaultAxis
  • org.geotools.coverage.io.range.impl.DimensionlessAxis
  • org.geotools.coverage.io.range.impl.EnumMeasure

While I was able to produce a * org.geotools.coverage.io.range.Measure abstract class to get this api to compile, there is not point since it is not in use.

public abstract class Measure<V extends Number, Q extends Quantity<Q>> implements Comparable<Measure<V, Q>> {
    abstract V getValue();
    abstract Unit<Q> getUnit();
    
    abstract Measure<V,Q> to(Unit<Q> unit);
    public Number numericValue(Unit<Q> unit ) {
        if( getUnit() == unit || getUnit().equals(unit)){
            return getValue();
        }
        else {
            UnitConverter converter = getUnit().getConverterTo(unit);
            return converter.convert(getValue());
        }
    }
    public double doubleValue(Unit<Q> unit) {
        Number number = numericValue(unit);
        return number.doubleValue();
    }
    public long longValue(Unit<Q> unit) {
        Number number = numericValue(unit);
        return number.longValue();
    }
    @Override
    public int compareTo(Measure<V, Q> obj) {
        return Double.compare(
                doubleValue(getUnit()),
                obj.doubleValue(getUnit())
                );
    }
}

Units

The Units utility class gains the following constants:

DEGREE_MINUTE_SECOND : Unit<Angle>
MONTH : Unit<Time>
PIXEL : Unit<Length>
PPM : Unit<Dimensionless>
SEXAGESIMAL_DMS : Unit<Angle>

And the following methods:

  • Units.autoCorrect(Unit): returns an equivalent unit instance based on the provided unit if appropriate
  • Units.equals(unit1, unit2): checks if the two units are equivalent
  • Units.getConverterToAny(foment,toUnit): drop in replacement, throws IllegalArgumentException rather than checked exceptions
  • Units.getDefaultFormat(): access to system-wide format, configured with common names and alias for use with GeoTools
  • Units.parseUnit(name)
  • Units.toName(unit): unit name, willing to look up label if required (allowing use of alias like "pixel", rather than "in/72")
  • Units.toSymbol(unit): unit symbol, willing to look up label if required
Clone this wiki locally