PROJ is a generic coordinate transformation software that transforms geospatial coordinates from one coordinate reference system (CRS) to another. This includes cartographic projections as well as geodetic transformations. PROJ is released under the X/MIT open source license [L1 ] [L2 ]
There's now a github page with this code and some docs and links [L3 ] Includes TEA compatible build for unixlike os's.
proj extension library usage
load ./proj.so # define proj string set S "+proj=pipeline " append S "+step +proj=axisswap +order=2,1 " append S "+step +proj=unitconvert +xy_in=deg +xy_out=rad " append S "+step +proj=merc +lat_ts=33 +ellps=clrk66" puts $S # create proj object from proj string set P [proj create $S] puts $P # lat 20.25 lon -16 set a [list 20.25 -16] puts $a # do forward transformation # lat lon (degrees) to easting northing set a [proj fwd $P $a ] puts $a # do inverse transformation # easting northing to lat lon (degrees) set a [proj inv $P $a] puts $a proj destroy $P
compile
cc -shared -fpic -g -Wall -O2 proj.c -o proj.so -ltclstub8.6 -lproj
proj.c
#include <tcl.h> #include <proj.h> typedef struct ProjState { Tcl_HashTable hash; /* List projections by name */ int uid; /* Used to generate names */ } ProjState; int ProjCmd(ClientData data, Tcl_Interp * interp, int objc, Tcl_Obj * CONST objv[]) { ProjState *statePtr = (ProjState *) data; int new, index; char name[20]; char *subCmds[] = { "create", "destroy", "fwd", "inv", "crs2crs", "norm", NULL }; enum Proj { Create, Destroy, Fwd, Inv, Crs2Crs, Norm }; if (objc < 2) { Tcl_WrongNumArgs(interp, 1, objv, "option ?arg ...?"); return TCL_ERROR; } if (Tcl_GetIndexFromObj(interp, objv[1], subCmds, "option", 0, &index) != TCL_OK) { return TCL_ERROR; } switch (index) { case Create: { if (objc != 3) { Tcl_WrongNumArgs(interp, 1, objv, "option ?arg ...?"); return TCL_ERROR; } PJ *P = proj_create(PJ_DEFAULT_CTX, Tcl_GetString(objv[2])); if (P == NULL) { Tcl_AppendResult(interp, "proj definition error: ", proj_errno_string(proj_errno(0)), NULL); return TCL_ERROR; } statePtr->uid++; sprintf(name, "proj%d", statePtr->uid); Tcl_HashEntry *entryPtr = Tcl_CreateHashEntry(&statePtr->hash, name, &new); Tcl_SetHashValue(entryPtr, (ClientData) P); Tcl_SetResult(interp, name, TCL_VOLATILE); return TCL_OK; } case Destroy: { if (objc != 3) { Tcl_WrongNumArgs(interp, 1, objv, "option ?arg ...?"); return TCL_ERROR; } Tcl_HashEntry *entryPtr = Tcl_FindHashEntry(&statePtr->hash, Tcl_GetString(objv[2])); if (entryPtr == NULL) { Tcl_AppendResult(interp, "Unknown proj: ", Tcl_GetString(objv[2]), NULL); return TCL_ERROR; } PJ *P = Tcl_GetHashValue(entryPtr); Tcl_DeleteHashEntry(entryPtr); proj_destroy(P); return TCL_OK; } case Fwd: case Inv: { if (objc != 4) { Tcl_WrongNumArgs(interp, 1, objv, "option ?arg ...?"); return TCL_ERROR; } Tcl_HashEntry *entryPtr = Tcl_FindHashEntry(&statePtr->hash, Tcl_GetString(objv[2])); if (entryPtr == NULL) { Tcl_AppendResult(interp, "Unknown proj: ", Tcl_GetString(objv[2]), NULL); return TCL_ERROR; } PJ *P = Tcl_GetHashValue(entryPtr); Tcl_Obj **d; int n; if (Tcl_ListObjGetElements(interp, objv[3], &n, &d) != TCL_OK) { return TCL_ERROR; } if (n < 2) { Tcl_AppendResult(interp,"not a point: ", NULL); return TCL_ERROR; } PJ_COORD a = proj_coord(0.0, 0.0, 0.0, 0.0); for (int i=0; i<n; i++) { if (Tcl_GetDoubleFromObj(interp, d[i], &(a.v[i])) != TCL_OK) { return TCL_ERROR; } } int dir; if (index == Fwd) { dir = PJ_FWD; } else { dir = PJ_INV; } // preferable to make unit conversions explicit in proj string // if (proj_angular_input(P, dir)) { // a.lp.lam = proj_torad(a.lp.lam); // a.lp.phi = proj_torad(a.lp.phi); // } a = proj_trans(P, dir, a); // preferable to make unit conversions explicit in proj string // if (proj_angular_output(P, dir)) { // a.lp.lam = proj_todeg(a.lp.lam); // a.lp.phi = proj_todeg(a.lp.phi); // } Tcl_Obj *listPtr = Tcl_NewListObj(0, NULL); for (int i=0; i<n; i++) { Tcl_ListObjAppendElement(interp, listPtr, Tcl_NewDoubleObj(a.v[i])); } Tcl_SetObjResult(interp, listPtr); return TCL_OK; } case Crs2Crs: { if (objc != 4) { Tcl_WrongNumArgs(interp, 1, objv, "option ?arg ...?"); return TCL_ERROR; } PJ *P = proj_create_crs_to_crs(PJ_DEFAULT_CTX, Tcl_GetString(objv[2]), Tcl_GetString(objv[3]), 0); if (P == NULL) { Tcl_AppendResult(interp, "proj definition error: ", proj_errno_string(proj_errno(0)), NULL); return TCL_ERROR; } statePtr->uid++; sprintf(name, "proj%d", statePtr->uid); Tcl_HashEntry *entryPtr = Tcl_CreateHashEntry(&statePtr->hash, name, &new); Tcl_SetHashValue(entryPtr, (ClientData) P); Tcl_SetResult(interp, name, TCL_VOLATILE); return TCL_OK; } case Norm: { if (objc != 3) { Tcl_WrongNumArgs(interp, 1, objv, "option ?arg ...?"); return TCL_ERROR; } Tcl_HashEntry *entryPtr = Tcl_FindHashEntry(&statePtr->hash, Tcl_GetString(objv[2])); if (entryPtr == NULL) { Tcl_AppendResult(interp, "Unknown proj: ", Tcl_GetString(objv[2]), NULL); return TCL_ERROR; } PJ *P = Tcl_GetHashValue(entryPtr); PJ *Q = proj_normalize_for_visualization(PJ_DEFAULT_CTX, P); statePtr->uid++; sprintf(name, "proj%d", statePtr->uid); entryPtr = Tcl_CreateHashEntry(&statePtr->hash, name, &new); Tcl_SetHashValue(entryPtr, (ClientData) Q); Tcl_SetResult(interp, name, TCL_VOLATILE); return TCL_OK; } } return TCL_OK; } void ProjCleanup(ClientData data) { ProjState *statePtr = (ProjState *) data; PJ *P; Tcl_HashSearch search; Tcl_HashEntry *entryPtr = Tcl_FirstHashEntry(&statePtr->hash, &search); while (entryPtr != NULL) { P = Tcl_GetHashValue(entryPtr); Tcl_DeleteHashEntry(entryPtr); proj_destroy(P); entryPtr = Tcl_FirstHashEntry(&statePtr->hash, &search); } ckfree((char *) statePtr); } int Proj_Init(Tcl_Interp * interp) { if (Tcl_InitStubs(interp, "8.1", 0) == NULL) { return TCL_ERROR; } if (Tcl_PkgRequire(interp, "Tcl", "8.1", 0) == NULL) { return TCL_ERROR; } ProjState *statePtr = (ProjState *) ckalloc(sizeof(ProjState)); Tcl_InitHashTable(&statePtr->hash, TCL_STRING_KEYS); statePtr->uid = 0; Tcl_CreateObjCommand(interp, "proj", ProjCmd, (ClientData) statePtr, ProjCleanup); Tcl_PkgProvide(interp, "proj", "1.0"); return TCL_OK; } // vim: cin:sw=4:tw=75
swig based binding for tcl for the proj library using the new API described here . A simple binding to the old API using critcl has been moved to proj4tcl - deprecated.
Here is the c example from proj quick start
#include <stdio.h> #include <proj.h> int main (void) { PJ_CONTEXT *C; PJ *P; PJ_COORD a, b; /* or you may set C=PJ_DEFAULT_CTX if you are sure you will */ /* use PJ objects from only one thread */ C = proj_context_create(); P = proj_create (C, "+proj=utm +zone=32 +ellps=GRS80"); if (0==P) return puts ("Oops"), 0; /* a coordinate union representing Copenhagen: 55d N, 12d E */ /* note: PROJ.4 works in radians, hence the proj_torad() calls */ a = proj_coord (proj_torad(12), proj_torad(55), 0, 0); /* transform to UTM zone 32, then back to geographical */ b = proj_trans (P, PJ_FWD, a); printf ("easting: %.3f, northing: %.3f\n", b.enu.e, b.enu.n); b = proj_trans (P, PJ_INV, b); printf ("longitude: %.9f, latitude: %.9f\n", b.lp.lam, b.lp.phi); /* Clean up */ proj_destroy (P); proj_context_destroy (C); /* may be omitted in the single threaded case */ return 0; }
and a translation to tcl
lappend auto_path . package require proj set C [proj_context_create] set p "+proj=utm +zone=32 +ellps=GRS80" set P [proj_create $C $p] set a [proj_coord [proj_torad 12] [proj_torad 55] 0 0 ] set b [proj_trans $P $::PJ_FWD $a] puts [format "easting: %.3f, northing: %.3f" \ [PJ_ENU_e_get [PJ_COORD_enu_get $b]] \ [PJ_ENU_n_get [PJ_COORD_enu_get $b]]] set c [proj_trans $P $::PJ_INV $b] puts [format "longitude: %.9f, latitude: %.9f" \ [[$c cget -lp] cget -lam] \ [[$c cget -lp] cget -phi]] proc proj_list {a} { set t [$a cget -xyzt] return [list \ [$t cget -x] \ [$t cget -y] \ [$t cget -z] \ [$t cget -t] ] } puts [format "easting: %.3f, northing: %.3f" \ {*}[proj_list $b]] puts [format "longitude: %.9f, latitude: %.9f" \ {*}[proj_list $c]] proj_destroy $P proj_context_destroy $C
file: proj.i
%module proj %{ #include <proj.h> %} %include <proj.h>
swig -tcl -pkgversion 1.0 -I/usr/include proj.i gcc -shared -fpic -DUSE_TCL_STUBS proj_wrap.c -o proj.so -ltclstub8.6 -lproj
using msys2/mingw64 static (tested with bawt )
pacboy -S gcc swig proj tcl swig -tcl -pkgversion 1.0 -I/mingw64/include proj.i gcc -shared -fpic -DUSE_TCL_STUBS proj_wrap.c -o proj.so -L/mingw64/lib -ltclstub86 -l:libproj.a
file: pkgIndex.tcl
package ifneeded proj 1.0 [list load [file join $dir proj.so]]
A number of functions are not supported using this simple wrapper namely:
PJ *proj_create_argv (PJ_CONTEXT *ctx, int argc, char **argv); int proj_trans_array (PJ *P, PJ_DIRECTION direction, size_t n, PJ_COORD *coord); size_t proj_trans_generic ( PJ *P, PJ_DIRECTION direction, double *x, size_t sx, size_t nx, double *y, size_t sy, size_t ny, double *z, size_t sz, size_t nz, double *t, size_t st, size_t nt ); double proj_roundtrip (PJ *P, PJ_DIRECTION direction, int n, PJ_COORD *coord); void proj_log_func (PJ_CONTEXT *ctx, void *app_data, PJ_LOG_FUNCTION logf); const PJ_OPERATIONS *proj_list_operations(void); const PJ_ELLPS *proj_list_ellps(void); const PJ_UNITS *proj_list_units(void); const PJ_PRIME_MERIDIANS *proj_list_prime_meridians(void); double proj_dmstor(const char *is, char **rs);