GEOTRANS in .NET and Java

GEOTRANS

GeoTrans (Geographic Translator) is a software that allows you to convert geographic coordinates among a variety of coordinate systems, map projections, and datums. It is made available by U.S. National Geospatial Intelligence Agency at no cost and free of copyright restrictions.

It is written in C (recent versions are in C++) and I've seen it used in software inside various surveying instruments. I worked on a supporting software for these devices and needed to do conversions of measured coordinates etc. So naturally, I wanted to use the tried and tested GeoTrans in my software as well. Only I needed to use it in .NET and Java…

.NET

In .NET (C# in my case) you have basically three options how to use GeoTrans:

  1. Compile C sources to DLL and use P/Invoke to call it from your managed code.
  2. Create C++/CLI wrapper for the C code.
  3. Rewrite the thing in managed language 🙁

I chose number 2. The good thing is you can directly step into the original C code when debugging your .NET managed app in Visual Studio. On the other hand, you are required to write a wrapper where you translate between managed and unmanaged types. As I was only interested in conversions between geographic coordinates and UTM and MGRS grids, my wrapper was quite small and not much work. Also, your .NET application will need MS VC redistributable DLLs (msvcr120.dll etc.) to run.

NOTE: I prefer to use older GeoTrans version 2.4 which is in C with plain and simple API. More recent versions use C++. That would be probably ok for C++/CLI wrapper but not that nice when using P/Invoke.

Here's the C++/CLI code of my GeoTrans wrapper:

#include "geotrans\utm.h"
#include "geotrans\ellipse.h"
#include "geotrans\mgrs.h"

using namespace System;
using namespace System::Runtime::InteropServices;

namespace GeotransWrapper {

    public ref class GeotransConverter
    {
    public:
        GeotransConverter()
        {
            double a, f;
            ellipsoidSet =
                (Ellipsoid_Parameters(WGS84_Index, &a, &f) == ELLIPSE_NO_ERROR) &&
                (Set_UTM_Parameters(a, f, 0) == UTM_NO_ERROR);
        }

        property bool IsReady
        {
            bool get()
            {
                return ellipsoidSet;
            }
        }

        bool GetWGS84Parameters([Out]double% a, [Out]double% f)
        {
            double anat, fnat;
            if (Ellipsoid_Parameters(WGS84_Index, &anat, &fnat) == ELLIPSE_NO_ERROR)
            {
                a = anat;
                f = fnat;
                return true;
            }
            return false;
        }

        bool ConvertGeoToUTM(double latitude, double longitude,
            [Out]bool% isNorthernHemisphere, [Out]int% zone, [Out]double% northing, [Out]double% easting)
        {
            long zoneIdx;
            char hemi;
            double east;
            double north;

            if (Convert_Geodetic_To_UTM(latitude, longitude, &zoneIdx, &hemi, &east, &north) == UTM_NO_ERROR)
            {
                northing = north;
                easting = east;
                zone = (int)zoneIdx;
                isNorthernHemisphere = (hemi == 'N');
                return true;
            }
            return false;
        }

        bool ConvertGeoToMGRS(double latitude, double longitude, [Out]String^% mgrsString)
        {
            char mgrs[16];

            if (Convert_Geodetic_To_MGRS(latitude, longitude, 5, mgrs) == MGRS_NO_ERROR)
            {
                mgrsString = gcnew String(mgrs);
                return true;
            }
            return false;
        }

        bool ConvertUTMToGeo(bool isNorthernHemisphere, int zone, double northing, double easting,
            [Out]double% latitude, [Out]double% longitude)
        {
            char hemi = isNorthernHemisphere ? 'N' : 'S';
            double lat, lon;

            if (Convert_UTM_To_Geodetic(zone, hemi, easting, northing, &lat, &lon) == UTM_NO_ERROR)
            {
                latitude = lat;
                longitude = lon;
                return true;
            }

            return false;
        }

        bool ConvertMGRSToGeo(String^ mgrsString, [Out]double% latitude, [Out]double% longitude)
        {
            double lat, lon;
            IntPtr ptrToNativeString = Marshal::StringToHGlobalAnsi(mgrsString);

            if (Convert_MGRS_To_Geodetic((const char*)ptrToNativeString.ToPointer(), &lat, &lon) == MGRS_NO_ERROR)
            {
                latitude = lat;
                longitude = lon;
                Marshal::FreeHGlobal(ptrToNativeString);
                return true;
            }

            Marshal::FreeHGlobal(ptrToNativeString);
            return false;
        }

    private:
        static const long WGS84_Index = 1;
        bool ellipsoidSet;
    };
}

And here's how the project looks like in Visual Studio:

GeonTrans C++/CLI project

Java

In Java, you have just two options. Either use JNI to access the compiled GeoTrans C sources or do a rewrite in Java. Since there is already a working JNI interface included in GeoTrans distribution this would seem like a no-brainer. However, I wanted GeoTrans in Java for Android app. JNI can of course be used there as well but I'd prefer not to mess with Android NDK if possible.

Fortunately, Internet search revealed that some nice guy pulled off that rewrite to Java. Only conversions between geographic, UTM/UPS, and MGRS coordinates are ported but that's all I needed. The port is called jeotrans (now also on GitHub).

One thought on “GEOTRANS in .NET and Java

  1. Trying to follow your C++/CLI example but not having much success as Geotrans 2.4 doesn’t have .cpp files as show in your Visual Studio project example. The newer Geotrans 3.x is C++ based and does have .cpp files but doesn’t have functions similar to the ones you have in your GeotransWrapper code. Would you be willing to provide your GeotransWrapper project?

Leave a Reply

Your email address will not be published. Required fields are marked *