Java, convert lat/lon to UTM [closed]
Asked Answered
B

5

29

Does anyone know of a way, in Java, to convert an earth surface position from lat, lon to UTM (say in WGS84)? I'm currently looking at Geotools but unfortunately the solution is not obvious.

Barfuss answered 6/10, 2008 at 20:50 Comment(5)
the Alberta 10 TM answer is probably overkill for what you need - this link from developer works probably has all the information you need.Exserviceman
Take a look at OpenMap specifically the [com.bbn.openmap.proj.coords ](openmap.bbn.com/doc/api/com/bbn/openmap/proj/coords/…) package in the API.Product
Steve Dutch at University of Wisconson has a pretty good write-up on the algorithm. Also includes an Excel document to help verify your numbers.Scraggy
I suggest JCoord. It allows you to convert between various cartographic coordinate schemes using a very simple API. Iy you're feeling saucy, have a look at the source code; its pages and pages of dense trigonometry. Splendid stuff. There's also a javascript version called JSCoord.Lycian
Yeah GeoTools documentation assumes that the end-user will already be very familiar with the API, and GIS in general. It's very difficult to understand. I am currently having a hard time getting plot points on a map using it. #29567731Postbox
B
19

I was able to use Geotools 2.4 to get something that works, based on some example code.

double utmZoneCenterLongitude = ...  // Center lon of zone, example: zone 10 = -123
int zoneNumber = ...                 // zone number, example: 10
double latitude, longitude = ...     // lat, lon in degrees

MathTransformFactory mtFactory = ReferencingFactoryFinder.getMathTransformFactory(null);
ReferencingFactoryContainer factories = new ReferencingFactoryContainer(null);

GeographicCRS geoCRS = org.geotools.referencing.crs.DefaultGeographicCRS.WGS84;
CartesianCS cartCS = org.geotools.referencing.cs.DefaultCartesianCS.GENERIC_2D;

ParameterValueGroup parameters = mtFactory.getDefaultParameters("Transverse_Mercator");
parameters.parameter("central_meridian").setValue(utmZoneCenterLongitude);
parameters.parameter("latitude_of_origin").setValue(0.0);
parameters.parameter("scale_factor").setValue(0.9996);
parameters.parameter("false_easting").setValue(500000.0);
parameters.parameter("false_northing").setValue(0.0);

Map properties = Collections.singletonMap("name", "WGS 84 / UTM Zone " + zoneNumber);
ProjectedCRS projCRS = factories.createProjectedCRS(properties, geoCRS, null, parameters, cartCS);

MathTransform transform = CRS.findMathTransform(geoCRS, projCRS);

double[] dest = new double[2];
transform.transform(new double[] {longitude, latitude}, 0, dest, 0, 1);

int easting = (int)Math.round(dest[0]);
int northing = (int)Math.round(dest[1]);
Barfuss answered 7/10, 2008 at 1:43 Comment(1)
can you please look into this?#61996154Felske
A
48

No Library, No Nothing. Copy This!

Using These Two Classes , You can Convert Degree(latitude/longitude) to UTM and Vice Versa!

private class Deg2UTM
{
    double Easting;
    double Northing;
    int Zone;
    char Letter;
    private  Deg2UTM(double Lat,double Lon)
    {
        Zone= (int) Math.floor(Lon/6+31);
        if (Lat<-72) 
            Letter='C';
        else if (Lat<-64) 
            Letter='D';
        else if (Lat<-56)
            Letter='E';
        else if (Lat<-48)
            Letter='F';
        else if (Lat<-40)
            Letter='G';
        else if (Lat<-32)
            Letter='H';
        else if (Lat<-24)
            Letter='J';
        else if (Lat<-16)
            Letter='K';
        else if (Lat<-8) 
            Letter='L';
        else if (Lat<0)
            Letter='M';
        else if (Lat<8)  
            Letter='N';
        else if (Lat<16) 
            Letter='P';
        else if (Lat<24) 
            Letter='Q';
        else if (Lat<32) 
            Letter='R';
        else if (Lat<40) 
            Letter='S';
        else if (Lat<48) 
            Letter='T';
        else if (Lat<56) 
            Letter='U';
        else if (Lat<64) 
            Letter='V';
        else if (Lat<72) 
            Letter='W';
        else
            Letter='X';
        Easting=0.5*Math.log((1+Math.cos(Lat*Math.PI/180)*Math.sin(Lon*Math.PI/180-(6*Zone-183)*Math.PI/180))/(1-Math.cos(Lat*Math.PI/180)*Math.sin(Lon*Math.PI/180-(6*Zone-183)*Math.PI/180)))*0.9996*6399593.62/Math.pow((1+Math.pow(0.0820944379, 2)*Math.pow(Math.cos(Lat*Math.PI/180), 2)), 0.5)*(1+ Math.pow(0.0820944379,2)/2*Math.pow((0.5*Math.log((1+Math.cos(Lat*Math.PI/180)*Math.sin(Lon*Math.PI/180-(6*Zone-183)*Math.PI/180))/(1-Math.cos(Lat*Math.PI/180)*Math.sin(Lon*Math.PI/180-(6*Zone-183)*Math.PI/180)))),2)*Math.pow(Math.cos(Lat*Math.PI/180),2)/3)+500000;
        Easting=Math.round(Easting*100)*0.01;
        Northing = (Math.atan(Math.tan(Lat*Math.PI/180)/Math.cos((Lon*Math.PI/180-(6*Zone -183)*Math.PI/180)))-Lat*Math.PI/180)*0.9996*6399593.625/Math.sqrt(1+0.006739496742*Math.pow(Math.cos(Lat*Math.PI/180),2))*(1+0.006739496742/2*Math.pow(0.5*Math.log((1+Math.cos(Lat*Math.PI/180)*Math.sin((Lon*Math.PI/180-(6*Zone -183)*Math.PI/180)))/(1-Math.cos(Lat*Math.PI/180)*Math.sin((Lon*Math.PI/180-(6*Zone -183)*Math.PI/180)))),2)*Math.pow(Math.cos(Lat*Math.PI/180),2))+0.9996*6399593.625*(Lat*Math.PI/180-0.005054622556*(Lat*Math.PI/180+Math.sin(2*Lat*Math.PI/180)/2)+4.258201531e-05*(3*(Lat*Math.PI/180+Math.sin(2*Lat*Math.PI/180)/2)+Math.sin(2*Lat*Math.PI/180)*Math.pow(Math.cos(Lat*Math.PI/180),2))/4-1.674057895e-07*(5*(3*(Lat*Math.PI/180+Math.sin(2*Lat*Math.PI/180)/2)+Math.sin(2*Lat*Math.PI/180)*Math.pow(Math.cos(Lat*Math.PI/180),2))/4+Math.sin(2*Lat*Math.PI/180)*Math.pow(Math.cos(Lat*Math.PI/180),2)*Math.pow(Math.cos(Lat*Math.PI/180),2))/3);
        if (Letter<'M')
            Northing = Northing + 10000000;
        Northing=Math.round(Northing*100)*0.01;
    }
}

private class UTM2Deg
{
    double latitude;
    double longitude;
    private  UTM2Deg(String UTM)
    {
        String[] parts=UTM.split(" ");
        int Zone=Integer.parseInt(parts[0]);
        char Letter=parts[1].toUpperCase(Locale.ENGLISH).charAt(0);
        double Easting=Double.parseDouble(parts[2]);
        double Northing=Double.parseDouble(parts[3]);           
        double Hem;
        if (Letter>'M')
            Hem='N';
        else
            Hem='S';            
        double north;
        if (Hem == 'S')
            north = Northing - 10000000;
        else
            north = Northing;
        latitude = (north/6366197.724/0.9996+(1+0.006739496742*Math.pow(Math.cos(north/6366197.724/0.9996),2)-0.006739496742*Math.sin(north/6366197.724/0.9996)*Math.cos(north/6366197.724/0.9996)*(Math.atan(Math.cos(Math.atan(( Math.exp((Easting - 500000) / (0.9996*6399593.625/Math.sqrt((1+0.006739496742*Math.pow(Math.cos(north/6366197.724/0.9996),2))))*(1-0.006739496742*Math.pow((Easting - 500000) / (0.9996*6399593.625/Math.sqrt((1+0.006739496742*Math.pow(Math.cos(north/6366197.724/0.9996),2)))),2)/2*Math.pow(Math.cos(north/6366197.724/0.9996),2)/3))-Math.exp(-(Easting-500000)/(0.9996*6399593.625/Math.sqrt((1+0.006739496742*Math.pow(Math.cos(north/6366197.724/0.9996),2))))*( 1 -  0.006739496742*Math.pow((Easting - 500000) / (0.9996*6399593.625/Math.sqrt((1+0.006739496742*Math.pow(Math.cos(north/6366197.724/0.9996),2)))),2)/2*Math.pow(Math.cos(north/6366197.724/0.9996),2)/3)))/2/Math.cos((north-0.9996*6399593.625*(north/6366197.724/0.9996-0.006739496742*3/4*(north/6366197.724/0.9996+Math.sin(2*north/6366197.724/0.9996)/2)+Math.pow(0.006739496742*3/4,2)*5/3*(3*(north/6366197.724/0.9996+Math.sin(2*north/6366197.724/0.9996 )/2)+Math.sin(2*north/6366197.724/0.9996)*Math.pow(Math.cos(north/6366197.724/0.9996),2))/4-Math.pow(0.006739496742*3/4,3)*35/27*(5*(3*(north/6366197.724/0.9996+Math.sin(2*north/6366197.724/0.9996)/2)+Math.sin(2*north/6366197.724/0.9996)*Math.pow(Math.cos(north/6366197.724/0.9996),2))/4+Math.sin(2*north/6366197.724/0.9996)*Math.pow(Math.cos(north/6366197.724/0.9996),2)*Math.pow(Math.cos(north/6366197.724/0.9996),2))/3))/(0.9996*6399593.625/Math.sqrt((1+0.006739496742*Math.pow(Math.cos(north/6366197.724/0.9996),2))))*(1-0.006739496742*Math.pow((Easting-500000)/(0.9996*6399593.625/Math.sqrt((1+0.006739496742*Math.pow(Math.cos(north/6366197.724/0.9996),2)))),2)/2*Math.pow(Math.cos(north/6366197.724/0.9996),2))+north/6366197.724/0.9996)))*Math.tan((north-0.9996*6399593.625*(north/6366197.724/0.9996 - 0.006739496742*3/4*(north/6366197.724/0.9996+Math.sin(2*north/6366197.724/0.9996)/2)+Math.pow(0.006739496742*3/4,2)*5/3*(3*(north/6366197.724/0.9996+Math.sin(2*north/6366197.724/0.9996)/2)+Math.sin(2*north/6366197.724/0.9996 )*Math.pow(Math.cos(north/6366197.724/0.9996),2))/4-Math.pow(0.006739496742*3/4,3)*35/27*(5*(3*(north/6366197.724/0.9996+Math.sin(2*north/6366197.724/0.9996)/2)+Math.sin(2*north/6366197.724/0.9996)*Math.pow(Math.cos(north/6366197.724/0.9996),2))/4+Math.sin(2*north/6366197.724/0.9996)*Math.pow(Math.cos(north/6366197.724/0.9996),2)*Math.pow(Math.cos(north/6366197.724/0.9996),2))/3))/(0.9996*6399593.625/Math.sqrt((1+0.006739496742*Math.pow(Math.cos(north/6366197.724/0.9996),2))))*(1-0.006739496742*Math.pow((Easting-500000)/(0.9996*6399593.625/Math.sqrt((1+0.006739496742*Math.pow(Math.cos(north/6366197.724/0.9996),2)))),2)/2*Math.pow(Math.cos(north/6366197.724/0.9996),2))+north/6366197.724/0.9996))-north/6366197.724/0.9996)*3/2)*(Math.atan(Math.cos(Math.atan((Math.exp((Easting-500000)/(0.9996*6399593.625/Math.sqrt((1+0.006739496742*Math.pow(Math.cos(north/6366197.724/0.9996),2))))*(1-0.006739496742*Math.pow((Easting-500000)/(0.9996*6399593.625/Math.sqrt((1+0.006739496742*Math.pow(Math.cos(north/6366197.724/0.9996),2)))),2)/2*Math.pow(Math.cos(north/6366197.724/0.9996),2)/3))-Math.exp(-(Easting-500000)/(0.9996*6399593.625/Math.sqrt((1+0.006739496742*Math.pow(Math.cos(north/6366197.724/0.9996),2))))*(1-0.006739496742*Math.pow((Easting-500000)/(0.9996*6399593.625/Math.sqrt((1+0.006739496742*Math.pow(Math.cos(north/6366197.724/0.9996),2)))),2)/2*Math.pow(Math.cos(north/6366197.724/0.9996),2)/3)))/2/Math.cos((north-0.9996*6399593.625*(north/6366197.724/0.9996-0.006739496742*3/4*(north/6366197.724/0.9996+Math.sin(2*north/6366197.724/0.9996)/2)+Math.pow(0.006739496742*3/4,2)*5/3*(3*(north/6366197.724/0.9996+Math.sin(2*north/6366197.724/0.9996)/2)+Math.sin(2*north/6366197.724/0.9996)*Math.pow(Math.cos(north/6366197.724/0.9996),2))/4-Math.pow(0.006739496742*3/4,3)*35/27*(5*(3*(north/6366197.724/0.9996+Math.sin(2*north/6366197.724/0.9996)/2)+Math.sin(2*north/6366197.724/0.9996)*Math.pow(Math.cos(north/6366197.724/0.9996),2))/4+Math.sin(2*north/6366197.724/0.9996)*Math.pow(Math.cos(north/6366197.724/0.9996),2)*Math.pow(Math.cos(north/6366197.724/0.9996),2))/3))/(0.9996*6399593.625/Math.sqrt((1+0.006739496742*Math.pow(Math.cos(north/6366197.724/0.9996),2))))*(1-0.006739496742*Math.pow((Easting-500000)/(0.9996*6399593.625/Math.sqrt((1+0.006739496742*Math.pow(Math.cos(north/6366197.724/0.9996),2)))),2)/2*Math.pow(Math.cos(north/6366197.724/0.9996),2))+north/6366197.724/0.9996)))*Math.tan((north-0.9996*6399593.625*(north/6366197.724/0.9996-0.006739496742*3/4*(north/6366197.724/0.9996+Math.sin(2*north/6366197.724/0.9996)/2)+Math.pow(0.006739496742*3/4,2)*5/3*(3*(north/6366197.724/0.9996+Math.sin(2*north/6366197.724/0.9996)/2)+Math.sin(2*north/6366197.724/0.9996)*Math.pow(Math.cos(north/6366197.724/0.9996),2))/4-Math.pow(0.006739496742*3/4,3)*35/27*(5*(3*(north/6366197.724/0.9996+Math.sin(2*north/6366197.724/0.9996)/2)+Math.sin(2*north/6366197.724/0.9996)*Math.pow(Math.cos(north/6366197.724/0.9996),2))/4+Math.sin(2*north/6366197.724/0.9996)*Math.pow(Math.cos(north/6366197.724/0.9996),2)*Math.pow(Math.cos(north/6366197.724/0.9996),2))/3))/(0.9996*6399593.625/Math.sqrt((1+0.006739496742*Math.pow(Math.cos(north/6366197.724/0.9996),2))))*(1-0.006739496742*Math.pow((Easting-500000)/(0.9996*6399593.625/Math.sqrt((1+0.006739496742*Math.pow(Math.cos(north/6366197.724/0.9996),2)))),2)/2*Math.pow(Math.cos(north/6366197.724/0.9996),2))+north/6366197.724/0.9996))-north/6366197.724/0.9996))*180/Math.PI;
        latitude=Math.round(latitude*10000000);
        latitude=latitude/10000000;
        longitude =Math.atan((Math.exp((Easting-500000)/(0.9996*6399593.625/Math.sqrt((1+0.006739496742*Math.pow(Math.cos(north/6366197.724/0.9996),2))))*(1-0.006739496742*Math.pow((Easting-500000)/(0.9996*6399593.625/Math.sqrt((1+0.006739496742*Math.pow(Math.cos(north/6366197.724/0.9996),2)))),2)/2*Math.pow(Math.cos(north/6366197.724/0.9996),2)/3))-Math.exp(-(Easting-500000)/(0.9996*6399593.625/Math.sqrt((1+0.006739496742*Math.pow(Math.cos(north/6366197.724/0.9996),2))))*(1-0.006739496742*Math.pow((Easting-500000)/(0.9996*6399593.625/Math.sqrt((1+0.006739496742*Math.pow(Math.cos(north/6366197.724/0.9996),2)))),2)/2*Math.pow(Math.cos(north/6366197.724/0.9996),2)/3)))/2/Math.cos((north-0.9996*6399593.625*( north/6366197.724/0.9996-0.006739496742*3/4*(north/6366197.724/0.9996+Math.sin(2*north/6366197.724/0.9996)/2)+Math.pow(0.006739496742*3/4,2)*5/3*(3*(north/6366197.724/0.9996+Math.sin(2*north/6366197.724/0.9996)/2)+Math.sin(2* north/6366197.724/0.9996)*Math.pow(Math.cos(north/6366197.724/0.9996),2))/4-Math.pow(0.006739496742*3/4,3)*35/27*(5*(3*(north/6366197.724/0.9996+Math.sin(2*north/6366197.724/0.9996)/2)+Math.sin(2*north/6366197.724/0.9996)*Math.pow(Math.cos(north/6366197.724/0.9996),2))/4+Math.sin(2*north/6366197.724/0.9996)*Math.pow(Math.cos(north/6366197.724/0.9996),2)*Math.pow(Math.cos(north/6366197.724/0.9996),2))/3)) / (0.9996*6399593.625/Math.sqrt((1+0.006739496742*Math.pow(Math.cos(north/6366197.724/0.9996),2))))*(1-0.006739496742*Math.pow((Easting-500000)/(0.9996*6399593.625/Math.sqrt((1+0.006739496742*Math.pow(Math.cos(north/6366197.724/0.9996),2)))),2)/2*Math.pow(Math.cos(north/6366197.724/0.9996),2))+north/6366197.724/0.9996))*180/Math.PI+Zone*6-183;
        longitude=Math.round(longitude*10000000);
        longitude=longitude/10000000;       
    }   
}
Aerugo answered 29/1, 2015 at 21:3 Comment(12)
Maybe a short comment for better understanding would be great.Hight
Use These Classes Like This : UTM2Deg pos =new UTM2Deg("35 R 312915.84 4451481.33") And lat=pos.latitude.Aerugo
Does this factor in that the earth is a spheroid and not a sphere?Barfuss
The UTM WGS-84 projection does, so if the code correctly converts into that projection, then it does that (not verified.) Looking at the math, it seems to use some factors to squash the spheroid, so there's some chance it's right (although I see 0.9996, which is off by an order of magnitude from the 0.996 ratio between pole/equator, so it could also be wrong, not for lack of trying, but for being wrong :-) As with any GIS math package, you have to verify it thoroughly before you trust it, or your ship will hit a rock.Hominid
I know that this thread is so old by now, but I wanted to thank you for providing this code. IT IS AWESOMELY-SIMPLE AND SURPRISINGLY PRECISE!Kahlil
@JonWatte 0.9996 is the central scaling factor of the UTM projection. It is not meant to deal with the ellipsoidal shape of the earth, that is taken care of elsewhere in the formulae.Favata
with no licence and an encourangement to "copy this", I cleaned up the code a bit, made it a bit more object oriented and in line with some java idioms. The result is uploaded to githubRinglet
@Aerugo thanks for the code, it seems to work just fine. Could you give more details about its source and its license?Axil
WARNING: I think I discovered a bug in this code. I believe if (Letter<'M') Northing = Northing + 10000000; should be if (Letter<='M') Northing = Northing + 10000000; Otherwise you can get negative UTM Northings.Ape
not working correctly when checking for Hemis letterMessalina
While this code is amazing and many thanks to the author, it is off by a couple of centimeters comparing to known libraries. My needs are to be millimeter precise and I need a super fast conversion (I am converting hundred times a second and existing libraries are slow). If the author could extract constants from the code, so perhaps eath's polar raius or eath's equatorial radius can be set this will become even more amazing code! please break the code to editable constants!Numeration
What if I want to deliberately convert to the coordinates of a specific zone? Point clouds are often in UTM format, but if you create a Point cloud that crosses UTM zone borders you want to remain in the same UTM Zone, even though that will introduce some error, as otherwise, the Points don't have a consistent coordinate system. Also, it would be lovely to have this explained a little better, or at least have the constants as named constants.Leadbelly
B
19

I was able to use Geotools 2.4 to get something that works, based on some example code.

double utmZoneCenterLongitude = ...  // Center lon of zone, example: zone 10 = -123
int zoneNumber = ...                 // zone number, example: 10
double latitude, longitude = ...     // lat, lon in degrees

MathTransformFactory mtFactory = ReferencingFactoryFinder.getMathTransformFactory(null);
ReferencingFactoryContainer factories = new ReferencingFactoryContainer(null);

GeographicCRS geoCRS = org.geotools.referencing.crs.DefaultGeographicCRS.WGS84;
CartesianCS cartCS = org.geotools.referencing.cs.DefaultCartesianCS.GENERIC_2D;

ParameterValueGroup parameters = mtFactory.getDefaultParameters("Transverse_Mercator");
parameters.parameter("central_meridian").setValue(utmZoneCenterLongitude);
parameters.parameter("latitude_of_origin").setValue(0.0);
parameters.parameter("scale_factor").setValue(0.9996);
parameters.parameter("false_easting").setValue(500000.0);
parameters.parameter("false_northing").setValue(0.0);

Map properties = Collections.singletonMap("name", "WGS 84 / UTM Zone " + zoneNumber);
ProjectedCRS projCRS = factories.createProjectedCRS(properties, geoCRS, null, parameters, cartCS);

MathTransform transform = CRS.findMathTransform(geoCRS, projCRS);

double[] dest = new double[2];
transform.transform(new double[] {longitude, latitude}, 0, dest, 0, 1);

int easting = (int)Math.round(dest[0]);
int northing = (int)Math.round(dest[1]);
Barfuss answered 7/10, 2008 at 1:43 Comment(1)
can you please look into this?#61996154Felske
G
5

The transformation of a coordinate can actually be done in only a few lines of code:

Coordinate coordinate = new Coordinate(x, y);
MathTransform transform = CRS.findMathTransform(CRS.decode("EPSG:4326"), CRS.decode("EPSG:3857"), false);
JTS.transform(coordinate, coordinate, transform); 

This will transform a longitude/latitude coordinate (EPSG:4326) into Web Mercator projection (EPSG:3857) coordinate.

You just need to depend on the following two GeoTools libraries in your build tool (e.g. maven):

<repositories>
    <repository>
        <id>osgeo</id>
        <name>Open Source Geospatial Foundation Repository</name>
        <url>http://download.osgeo.org/webdav/geotools/</url>
    </repository>
</repositories>

<dependencies>
    <dependency>
        <groupId>org.geotools</groupId>
        <artifactId>gt-api</artifactId>
        <version>${geotools.version}</version>
    </dependency>
    <dependency>
        <groupId>org.geotools</groupId>
        <artifactId>gt-epsg-hsql</artifactId>
        <version>${geotools.version}</version>
    </dependency>
</dependencies>

This answer builds on a question/reply on gis.stackexchange.com. Posted my reply because the current answers here seem to be quite verbose.

Grano answered 15/7, 2016 at 14:51 Comment(2)
how about from UTM to lat/long using JTS?Dampier
@JerylCook it is as simple as swapping theDecoupage
L
1

For my projects I've using the library LatLongLib, from Ahmed Taha. I think that it's very easy to convert coordinates from the UTM system to the Latitude-Longitude system and vice-versa. You just need to play with the classes UTMUtils, UTMPoint and LatLonPoint.

Time ago I also considered choosing Jcoord. It was easy and straight to the point too. However, I needed to use the WGS84 ellipsoid and, at that time, only LatLongLib seemed to have that feature.

Labuan answered 20/2, 2015 at 10:8 Comment(1)
Jcoord now supports WGS84, I'm using it to convert WGS84 to UK Ordnance survey and the results are excellent.Philia
D
1

You can use this project https://github.com/Berico-Technologies/Geo-Coordinate-Conversion-Java/, adding it to your existing pom.xml using jitpack.

I've successfully been able to convert UTM coordinates (30N, this is, 30 zone and northern hemisphere) to Latitude and Longitude. See my example below:

public void setPunto(Point punto) {
    this.punto = punto;
    LatLon latlon = UTMCoord.locationFromUTMCoord(30, AVKey.NORTH, punto.getX(), punto.getY());
    this.latitud = latlon.getLatitude().degrees;
    this.longitud = latlon.getLongitude().degrees;
}

Note that Point class is of com.vividsolutions.jts.geom.Point class type.

Danczyk answered 19/11, 2015 at 14:17 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.