-
Notifications
You must be signed in to change notification settings - Fork 1.1k
Migrate Units to JSR 363
- Contact: César Martínez Izquierdo, jodygarnett
- Tracker: https://osgeo-org.atlassian.net/browse/GEOT-5867
- TLDR: Migrate to JSR-363 imports for Java 10.3 compatibility
- Branch: https://github.com/dispiste/geotools/tree/test_units_jsr363
- Pull Request: https://github.com/geotools/geotools/pull/1834
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:
-
JSR-363 - Final Release September 13th, 2016
- JSR363Specification_Final_Eval.pdf – August 16, 2016
-
Java-9-Compatibility - GeoTools Wiki
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
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.
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.
GeoTools 20
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
- CMI: (done) Update maven dependency to JSR-363 artifacts
- JG: (done) Change imports
- CMI: (done) Change API references
- CMI: (done) Bulk fix imports and erros based on API change
- JG: (done) Add to upgrade instructions for downstream projects upgrading page.
- JG: (done) Test GWC downstream
- CMI: (done) Test/Fix GeoServer downstream
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>
-
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)
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;
}
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())
);
}
}
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