#include "EXTERN.h" #include "perl.h" #include "XSUB.h" #include #include #include #include #include #include #include #if __GNUC__ >= 3 # define expect(expr,value) __builtin_expect ((expr), (value)) # define INLINE static inline #else # define expect(expr,value) (expr) # define INLINE static #endif #define PI 3.14159265358979323846 #define DEG2RAD(DEG) ((DEG)*((PI)/(180.0))) #define RAD2DEG(DEG) ((DEG)*(180.0/PI)) #define INT_NOT_R int enum DISTANCE_UNIT_ENUM { UNIT_METERS, UNIT_KM, UNIT_YARDS, UNIT_FEET, UNIT_MILES }; typedef struct { /* Coords */ long double latitude; long double longitude; /* Distance Units */ enum DISTANCE_UNIT_ENUM unit_conv; /* Earth Radius */ long double radius; } GCX; typedef struct { long double lat; long double lon; long double final_bearing; } DESTINATION; typedef struct { long double lat; long double lon; } POINT; INLINE void geocalc_init( GCX *gcx, HV * options ) { Zero( gcx, 1, GCX ); SV** sv_lat = hv_fetch(options, "lat", strlen("lat"), 0); if (! sv_lat) { croak("lat must be specified"); } SV** sv_lon = hv_fetch(options, "lon", strlen("lon"), 0); if (! sv_lon) { croak("lon must be specified"); } gcx->latitude = (long double) SvNV( *sv_lat ); gcx->longitude = (long double) SvNV( *sv_lon ); SV ** sv = hv_fetch(options, "units", 5, 0); if( sv == (SV**)NULL ) { gcx->unit_conv = UNIT_METERS; // Default to m } else { if( strEQ( SvPV_nolen( *sv ), "m" ) ) { gcx->unit_conv = UNIT_METERS; } else if( strEQ( SvPV_nolen( *sv ), "k-m" ) ) { gcx->unit_conv = UNIT_KM; } else if( strEQ( SvPV_nolen( *sv ), "yd" ) ) { gcx->unit_conv = UNIT_YARDS; } else if( strEQ( SvPV_nolen( *sv ), "ft" ) ) { gcx->unit_conv = UNIT_FEET; } else if( strEQ( SvPV_nolen( *sv ), "mi" ) ) { gcx->unit_conv = UNIT_MILES; } else if( strEQ( SvPV_nolen( *sv ), "" ) ) { gcx->unit_conv = UNIT_METERS; } else { warn("Unrecognised unit (defaulting to m)"); gcx->unit_conv = UNIT_METERS; } } /* sv = hv_fetch(options, "radius", 6, 0); if( sv == (SV**)NULL ) { gcx->radius = 6371; // Default to KM } else { gcx->radius = 6371; // Default to KM } */ gcx->radius = 6371; // Earth Radius } INLINE long double convert_km( long double input, enum DISTANCE_UNIT_ENUM to_unit ) { double output = 0; switch( to_unit ) { case UNIT_METERS : output = input * 1000; break; case UNIT_KM : output = input; break; case UNIT_YARDS : output = input * 1093.6133; break; case UNIT_FEET : output = input * 3280.8399; break; case UNIT_MILES : output = input * 0.621371192; break; default : output = input; break; // kilometers } return output; } INLINE long double convert_to_m( long double input, enum DISTANCE_UNIT_ENUM from_unit ) { double output = 0; switch( from_unit ) { case UNIT_METERS : output = input; break; case UNIT_KM : output = input * 1000; break; case UNIT_YARDS : output = input * 0.9144; break; case UNIT_FEET : output = input * 0.3048; break; case UNIT_MILES : output = input * 1609.344; break; default : output = input * 1000; break; // killometers } return output; } INLINE long double cint( double x ) { double f; if ( modf( x, &f ) >= 0.5 ) return ( x >= 0 ) ? ceil( x ) : floor( x ); else return ( x < 0 ) ? ceil( x ) : floor( x ); } INLINE long double _precision_ld( long double r, int places ) { long double off = pow( 10, -places ); return cint( r * off ) / off; } INLINE long double _ib_precision( long double brng, int precision, int mul ) { return _precision_ld( (long double)fmod( mul * ( RAD2DEG( brng ) ) + 360, 360 ), precision ); } INLINE long double _fb_precision( long double brng, int precision ) { return _precision_ld( (long double)fmod( ( RAD2DEG( brng ) ) + 180, 360 ), precision ); } INLINE void _destination_point( GCX *self, double bearing, double s, int precision, DESTINATION *dest ) { s = convert_to_m( s, self->unit_conv ); long double r_major = 6378137; // Equatorial Radius, WGS84 long double r_minor = 6356752.314245179; // defined as constant long double f = 1/298.257223563; // 1/f = ( $r_major - $r_minor ) / $r_major long double alpha1 = DEG2RAD( bearing ); long double sinAlpha1 = sin( alpha1 ); long double cosAlpha1 = cos( alpha1 ); long double tanU1 = ( 1 - f ) * tan( DEG2RAD( self->latitude ) ); long double cosU1 = 1 / sqrt( ( 1 + ( tanU1 * tanU1 ) ) ); long double sinU1 = tanU1 * cosU1; long double sigma1 = atan2( tanU1, cosAlpha1 ); long double sinAlpha = cosU1 * sinAlpha1; long double cosSqAlpha = 1 - ( sinAlpha * sinAlpha ); long double uSq = cosSqAlpha * ( ( r_major * r_major ) - ( r_minor * r_minor ) ) / ( r_minor * r_minor ); long double A = 1 + uSq/16384*(4096+uSq*(-768+uSq*(320-175*uSq))); long double B = uSq/1024 * (256+uSq*(-128+uSq*(74-47*uSq))); long double sigma = s / ( r_minor * A ); long double sigmaP = PI * 2; long double cos2SigmaM = cos(2*sigma1 + sigma); long double sinSigma = sin(sigma); long double cosSigma = cos(sigma); while ( abs(sigma - sigmaP) > 1 * pow( 10, -12 ) ) { cos2SigmaM = cos(2*sigma1 + sigma); sinSigma = sin(sigma); cosSigma = cos(sigma); long double deltaSigma = B*sinSigma*(cos2SigmaM+B/4*(cosSigma*(-1+2*cos2SigmaM*cos2SigmaM)- B/6*cos2SigmaM*(-3+4*sinSigma*sinSigma)*(-3+4*cos2SigmaM*cos2SigmaM))); sigmaP = sigma; sigma = s / (r_minor*A) + deltaSigma; } long double tmp = sinU1*sinSigma - cosU1*cosSigma*cosAlpha1; long double lat2 = atan2( sinU1*cosSigma + cosU1*sinSigma*cosAlpha1, (1-f)*sqrt(sinAlpha*sinAlpha + tmp*tmp) ); long double lambda = atan2(sinSigma*sinAlpha1, cosU1*cosSigma - sinU1*sinSigma*cosAlpha1); long double C = f/16*cosSqAlpha*(4+f*(4-3*cosSqAlpha)); long double L = lambda - (1-C) * f * sinAlpha * (sigma + C*sinSigma*(cos2SigmaM+C*cosSigma*(-1+2*cos2SigmaM*cos2SigmaM))); long double lon2 = fmod( DEG2RAD( self->longitude ) + L + ( PI * 3 ), PI * 2 ) - PI; long double revAz = atan2( sinAlpha, -tmp ); // final bearing, if required dest->lat = _precision_ld( RAD2DEG( lat2 ), precision ); dest->lon = _precision_ld( RAD2DEG( lon2 ), precision ); dest->final_bearing = _precision_ld( RAD2DEG( revAz ), precision ); } MODULE = Geo::Calc::XS PACKAGE = Geo::Calc::XS PROTOTYPES: DISABLE void new ( char *klass, ... ) PREINIT: int add_count = items - 1; PPCODE: { SV *pv = NEWSV ( 0, sizeof( GCX ) ); HV *options = newHV(); int i = 0; SvPOK_only( pv ); if( add_count % 2 != 0 ) croak( "Please check your parameters while initiating the module\n" ); for( i = 0; i < add_count; i = i + 2 ) { char *key = SvPV_nolen(ST(i + 1)); if (! (0 == strcmp(key, "lat") || 0 == strcmp(key, "lon") || 0 == strcmp(key, "units") || 0 == strcmp(key, "radius"))) { croak("Unexpected key '%s'", key); } hv_store_ent( options, ST( i + 1 ), newSVsv(ST( i + 2)), 0); } geocalc_init( (GCX *)SvPVX( pv ), options ); SvREFCNT_dec((SV *) options); XPUSHs( sv_2mortal( sv_bless( newRV_noinc( pv ), gv_stashpv( klass, 1 )) ) ); } long double get_lat ( GCX *self ) CODE: RETVAL = self->latitude; OUTPUT: RETVAL long double get_lon ( GCX *self ) CODE: RETVAL = self->longitude; OUTPUT: RETVAL char * get_units ( GCX *self ) CODE: switch ( self->unit_conv) { case UNIT_METERS: RETVAL = "m"; break; case UNIT_KM: RETVAL = "k-m"; break; case UNIT_YARDS: RETVAL = "yd"; break; case UNIT_FEET: RETVAL = "ft"; break; default: RETVAL = "mi"; } OUTPUT: RETVAL long double get_radius ( GCX *self ) CODE: RETVAL = self->radius; OUTPUT: RETVAL long double distance_to( GCX *self, POINT *to_latlon, INT_NOT_R precision = -6 ) CODE: { long double lat1 = DEG2RAD( self->latitude ); long double lon1 = DEG2RAD( self->longitude ); long double lat2 = DEG2RAD( to_latlon->lat ); long double lon2 = DEG2RAD( to_latlon->lon ); long double t = pow( sin( ( lat2 - lat1 ) / 2 ), 2 ) + ( pow( cos( lat1 ), 2 ) * pow( sin( ( lon2 - lon1 )/2 ), 2 ) ); long double d = convert_km( self->radius * ( 2 * atan2( sqrt(t), sqrt(1-t) ) ), self->unit_conv ); RETVAL = _precision_ld( d, precision ); } OUTPUT: RETVAL HV * destination_point( GCX *self, double bearing, double s, INT_NOT_R precision = -6 ) CODE: { DESTINATION dest; _destination_point( self, bearing, s, precision, &dest ); HV *retval = newHV(); hv_store( retval, "lat", 3, newSVnv( dest.lat ), 0 ); hv_store( retval, "lon", 3, newSVnv( dest.lon ), 0 ); hv_store( retval, "final_bearing", 13, newSVnv( dest.final_bearing ), 0 ); RETVAL = retval; sv_2mortal((SV *) RETVAL); } OUTPUT: RETVAL HV * boundry_box( GCX *self, ... ) CODE: { /* Signature is: * $self, $width, $height, $precision * The latter three arguments are optional */ double width, height; int precision; DESTINATION dest; if (items < 2 || items > 4) { croak("Unexpected number of parameters"); } else if (! ST(1) || ! SvOK(ST(1)) || SvROK(ST(1))) { croak("width is invalid"); } width = SvNV(ST(1)); if (items >= 3 && ST(2) && SvOK(ST(2)) && SvROK(ST(2))) { croak("height is not expected to be a reference"); } else if (items >= 3 && ST(2) && SvOK(ST(2))) { height = SvNV(ST(2)); } else { width = width * 2; height = width; } if (items >= 4 && ST(3) && SvOK(ST(3)) && SvROK(ST(3))) { croak("precision is not expected to be a reference"); } else if (items >= 4 && ST(3) && SvOK(ST(3))) { precision = SvNV(ST(3)); } else { precision = -6; } HV *retval = newHV(); _destination_point( self, 180, height / 2, precision, &dest ); hv_store( retval, "lat_min", 7, newSVnv( dest.lat ), 0 ); _destination_point( self, 270, width / 2, precision, &dest ); hv_store( retval, "lon_min", 7, newSVnv( dest.lon ), 0 ); _destination_point( self, 0, height / 2, precision, &dest ); hv_store( retval, "lat_max", 7, newSVnv( dest.lat ), 0 ); _destination_point( self, 90, width / 2, precision, &dest ); hv_store( retval, "lon_max", 7, newSVnv( dest.lon ), 0 ); RETVAL = retval; sv_2mortal((SV *) RETVAL); } OUTPUT: RETVAL HV * midpoint_to( GCX *self, POINT *to_latlon, INT_NOT_R precision = -6 ) CODE: { long double lat1 = DEG2RAD( self->latitude ); long double lon1 = DEG2RAD( self->longitude ); long double lat2 = DEG2RAD( to_latlon->lat ); long double dlon = DEG2RAD( to_latlon->lon - self->longitude ); long double bx = cos( lat2 ) * cos( dlon ); long double by = cos( lat2 ) * sin( dlon ); long double lat3 = atan2( sin( lat1 ) + sin ( lat2 ), sqrt( ( pow( ( cos( lat1 ) + bx ), 2 ) ) + pow( by, 2 ) ) ); long double lon3 = fmod( lon1 + atan2( by, cos( lat1 ) + bx ) + ( PI * 3 ), PI * 2 ) - PI; HV *retval = newHV(); hv_store( retval, "lat", 3, newSVnv( _precision_ld( RAD2DEG( lat3 ), precision ) ), 0 ); hv_store( retval, "lon", 3, newSVnv( _precision_ld( RAD2DEG( lon3 ), precision ) ), 0 ); RETVAL = retval; sv_2mortal((SV *) RETVAL); } OUTPUT: RETVAL HV * intersection( GCX *self, double brng1, POINT *to_latlon, double brng2, INT_NOT_R precision = -6 ) CODE: { long double lat1 = DEG2RAD( self->latitude ); long double lon1 = DEG2RAD( self->longitude ); long double lat2 = DEG2RAD( to_latlon->lat ); long double lon2 = DEG2RAD( to_latlon->lon ); long double brng13 = DEG2RAD( brng1 ); long double brng23 = DEG2RAD( brng2 ); long double dlat = lat2 - lat1; long double dlon = lon2 - lon1; long double dist12 = 2 * asin( sqrt( pow( sin( dlat/2 ), 2 ) + cos( lat1 ) * cos( lat2 ) * pow( sin( dlon/2 ), 2 ) ) ); if( dist12 == 0 ) { XSRETURN_UNDEF; } // initial/final bearings between points long double brnga, brngb; if( sin( dist12 ) * cos( lat1 ) > 0 ) { brnga = acos( ( sin( lat2 ) - sin( lat1 ) * cos( dist12 ) ) / ( sin( dist12 ) * cos( lat1 ) ) ); } else { brnga = 0; } if( sin( dist12 ) * cos( lat2 ) > 0 ) { brngb = acos( ( sin( lat1 ) - sin( lat2 ) * cos( dist12 ) ) / ( sin( dist12 ) * cos( lat2 ) ) ); } else { brngb = 0; } long double brng12, brng21; if( sin( dlon ) > 0 ) { brng12 = brnga; brng21 = PI*2 - brngb; } else { brng12 = PI*2 - brnga; brng21 = brngb; } long double alpha1 = fmod( brng13 - brng12 + ( PI * 3 ), PI * 2 ) - PI; long double alpha2 = fmod( brng21 - brng23 + ( PI * 3 ), PI * 2 ) - PI; if( ( sin( alpha1 ) == 0 ) && ( sin( alpha2 ) == 0 ) ) { // infinite intersections XSRETURN_UNDEF; } if( sin( alpha1 ) * sin( alpha2 ) < 0 ) { // ambiguous intersection XSRETURN_UNDEF; } long double alpha3 = acos( -cos( alpha1 ) * cos( alpha2 ) + sin( alpha1 ) * sin( alpha2 ) * cos( dist12 ) ); long double dist13 = atan2( sin( dist12 ) * sin( alpha1 ) * sin( alpha2 ), cos( alpha2 ) + cos( alpha1 ) * cos( alpha3 ) ); long double lat3 = asin( sin( lat1 ) * cos( dist13 ) + cos( lat1 ) * sin( dist13 ) * cos( brng13 ) ); long double dlon13 = atan2( sin( brng13 ) * sin( dist13 ) * cos( lat1 ), cos( dist13 ) - sin( lat1 ) * sin( lat3 ) ); long double lon3 = fmod( lon1 + dlon13 + ( PI * 3 ), PI * 2 ) - PI; HV *retval = newHV(); hv_store( retval, "lat", 3, newSVnv( _precision_ld( RAD2DEG( lat3 ), precision ) ), 0 ); hv_store( retval, "lon", 3, newSVnv( _precision_ld( RAD2DEG( lon3 ), precision ) ), 0 ); RETVAL = retval; sv_2mortal((SV *) RETVAL); } OUTPUT: RETVAL HV * distance_at( GCX *self, INT_NOT_R precision = -6 ) CODE: { long double lat = DEG2RAD( self->latitude ); // Set up "Constants" double m1 = 111132.92; // latitude calculation term 1 double m2 = -559.82; // latitude calculation term 2 double m3 = 1.175; // latitude calculation term 3 double m4 = -0.0023; // latitude calculation term 4 double p1 = 111412.84; // longitude calculation term 1 double p2 = -93.5; // longitude calculation term 2 double p3 = 0.118; // longitude calculation term 3. HV *retval = newHV(); hv_store( retval, "m_lat", 5, newSVnv( _precision_ld( m1 + (m2 * cos(2 * lat)) + (m3 * cos(4 * lat)) + ( m4 * cos(6 * lat) ), precision ) ), 0 ); hv_store( retval, "m_lon", 5, newSVnv( _precision_ld( ( p1 * cos(lat)) + (p2 * cos(3 * lat)) + (p3 * cos(5 * lat) ), precision ) ), 0 ); RETVAL = retval; sv_2mortal((SV *) RETVAL); } OUTPUT: RETVAL long double bearing_to( GCX *self, POINT *to_latlon, INT_NOT_R precision = -6 ) CODE: { long double lat1 = DEG2RAD( self->latitude ); long double lat2 = DEG2RAD( to_latlon->lat ); long double dlon = DEG2RAD( self->longitude - to_latlon->lon ); long double brng = atan2( sin( dlon ) * cos( lat2 ), ( cos( lat1 ) * sin( lat2 ) ) - ( sin( lat1 ) * cos( lat2 ) * cos( dlon ) ) ); RETVAL = _ib_precision( brng, precision, -1 ); } OUTPUT: RETVAL long double final_bearing_to( GCX *self, POINT *to_latlon, INT_NOT_R precision = -6 ) CODE: { long double lat1 = DEG2RAD( to_latlon->lat ); long double lat2 = DEG2RAD( self->latitude ); long double dlon = -DEG2RAD( to_latlon->lon - self->longitude ); long double brng = atan2( sin( dlon ) * cos( lat2 ), ( cos( lat1 ) * sin( lat2 ) ) - ( sin( lat1 ) * cos( lat2 ) * cos( dlon ) ) ); RETVAL = _fb_precision( brng, precision ); } OUTPUT: RETVAL long double rhumb_distance_to( GCX *self, POINT *to_latlon, INT_NOT_R precision = -6 ) CODE: { long double lat1 = DEG2RAD( self->latitude ); long double lat2 = DEG2RAD( to_latlon->lat ); long double dlat = DEG2RAD( to_latlon->lat - self->latitude ); long double dlon = DEG2RAD( to_latlon->lon - self->longitude ); if( dlon < 0 ) dlon = -dlon; long double dphi = log( tan( lat2/2 + PI/4 ) / tan( lat1/2 + PI/4 ) ); long double q; if ( dphi != 0 ) { q = dlat/dphi; } else { q = cos(lat1); // E-W line gives dPhi=0 } if( dlon > PI ) dlon = PI*2 - dlon; long double dist = sqrt( pow( dlat, 2 ) + pow( q, 2 ) * pow( dlon, 2 ) ) * self->radius; RETVAL = _precision_ld( convert_km( dist, self->unit_conv ), precision ); } OUTPUT: RETVAL long double rhumb_bearing_to( GCX *self, POINT *to_latlon, INT_NOT_R precision = -6 ) CODE: { long double lat1 = DEG2RAD( self->latitude ); long double lat2 = DEG2RAD( to_latlon->lat ); long double dlon = DEG2RAD( to_latlon->lon - self->longitude ); long double dphi = log( tan( lat2/2 + PI/4 ) / tan( lat1/2 + PI/4 ) ); long double abs_dphi = dphi; if( abs_dphi < 0 ) { abs_dphi = -abs_dphi; } if( abs_dphi > PI ) { dlon = ( dlon > 0 ) ? -( PI*2 - dlon ) : ( PI*2 + dlon ); } RETVAL = _ib_precision( atan2( dlon, dphi ), precision, 1 ); } OUTPUT: RETVAL HV * rhumb_destination_point( GCX *self, double brng, double s, INT_NOT_R precision = -6 ) CODE: { long double d = ( convert_to_m( s, self->unit_conv ) / 1000 ) / self->radius; long double lat1 = DEG2RAD( self->latitude ); long double lon1 = DEG2RAD( self->longitude ); brng = DEG2RAD( brng ); long double lat2 = lat1 + ( d * cos( brng ) ); long double dlat = lat2 - lat1; long double dphi = log( tan( lat2/2 + PI/4 ) / tan( lat1/2 + PI/4 ) ); long double q = ( dphi != 0 ) ? dlat/dphi : cos(lat1); //# E-W line gives dPhi=0 long double dlon = d * sin( brng ) / q; // check for some daft bugger going past the pole long double lat2_abs = lat2; if( lat2_abs < 0 ) lat2_abs = -lat2_abs; if( lat2_abs > PI/2 ) lat2 = ( lat2 ) > 0 ? PI-lat2 : -(PI-lat2); long double lon2 = fmod( lon1 + dlon + ( PI * 3 ), PI * 2 ) - PI; HV *retval = newHV(); hv_store( retval, "lat", 3, newSVnv( _precision_ld( RAD2DEG( lat2 ), precision ) ), 0 ); hv_store( retval, "lon", 3, newSVnv( _precision_ld( RAD2DEG( lon2 ), precision ) ), 0 ); RETVAL = retval; sv_2mortal((SV *) RETVAL); } OUTPUT: RETVAL