/* * $Id: _biggles.c,v 1.18 2003/01/19 02:30:04 mrnolta Exp $ * * Copyright (C) 2001 Mike Nolta * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License * as published by the Free Software Foundation; either version 2 * of the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public * License along with this program; if not, write to the * Free Software Foundation, Inc., 59 Temple Place - Suite 330, * Boston, MA 02111-1307, USA. * */ #include #include #ifdef NO_NUMERIC_INCDIR #include #else #include #endif #ifndef M_PI #define M_PI 3.14159265358979323846 #endif #define PyArray_1D(v,i)\ (*((double *)((v)->data+(i)*(v)->strides[0]))) #define PyArray_1D_ptr(v,i)\ ((double *)((v)->data+(i)*(v)->strides[0])) #define PyArray_2D(m,i,j)\ (*((double *)((m)->data+(i)*(m)->strides[0]+(j)*(m)->strides[1]))) /* * I wish min/max(z) worked. */ #define BGL_MIN(a,b) (((a) < (b)) ? (a) : (b)) #define BGL_MAX(a,b) (((a) > (b)) ? (a) : (b)) static PyObject * biggles_range( PyObject *self, PyObject *args ) { PyObject *input; PyArrayObject *x; int i, n; double *dp, min, max; if ( !PyArg_ParseTuple(args, "O", &input) ) return NULL; x = (PyArrayObject *) PyArray_ContiguousFromObject( input, PyArray_DOUBLE, 0, 0 ); if ( x == NULL ) return NULL; n = PyArray_Size( input ); dp = (double *) x->data; min = max = dp[0]; for ( i = 1; i < n; i++ ) { min = BGL_MIN( min, dp[i] ); max = BGL_MAX( max, dp[i] ); } Py_DECREF(x); return Py_BuildValue( "dd", min, max ); } /****************************************************************************** * contour.py * * The problem is, given a 2D matrix, draw isocontours. This is * basically an exercise in interpolation, considering the matrix * to be the values of a function sampled on a regular grid. * * So it boils down to how best to interpolate a 2D function over * a rectangular area given only the values at the corners. * * The simplest solution would be a plane, but 4 points aren't * necessarily coplanar. And the next simplest smooth function, * a quadratic, is underdetermined. The "solution" seems to be * to add a fifth point, the average of the four corner points. * * * z[i,j+1] = p1 o-----------o p2 = z[i+1,j+1] * | + + | * | + + | * | o <---+--- p4 = average(p0,p1,p2,p3) * | + + | * | + + | * z[i,j] = p0 o-----------o p3 = z[i+1,j] * * * This breaks the square up into four simplicies (triangles), * each of which defines a plane. The algorithm is then simply * figure out whether the simplex intersects the desired plane * and ifso add a line segment to the render list. * * Downside is that the output is a bunch of tiny unconnected line * segments, which bloats postscript files and slows down rendering. * Have to connect the dots at some point. * */ #define MAX_SEGS 4 static int _find_zero( double p[3], double q[3], double zero[2] ) { double a; if ( p[2] == 0. ) { zero[0] = p[0]; zero[1] = p[1]; return 1; } else if ( p[2]*q[2] < 0. ) { a = p[2]/(p[2] - q[2]); zero[0] = p[0] + a*(q[0] - p[0]); zero[1] = p[1] + a*(q[1] - p[1]); return 1; } return 0; } static int _pixel_interpolate( PyArrayObject *x, PyArrayObject *y, PyArrayObject *z, double z0, int i, int j, double segs[MAX_SEGS][4] ) { int k, l, ii, jj, kk; double p[5][3], zeros[3][2]; int nz, ns; for ( l = 0; l < 3; l++ ) p[4][l] = 0.; for ( k = 0; k < 4; k++ ) { ii = i + (k/2 % 2); jj = j + ((k+1)/2 % 2); p[k][0] = PyArray_1D(x,ii); p[k][1] = PyArray_1D(y,jj); p[k][2] = PyArray_2D(z,ii,jj) - z0; for ( l = 0; l < 3; l++ ) p[4][l] += 0.25 * p[k][l]; } ns = 0; for ( k = 0; k < 4; k++ ) { kk = (k + 1) % 4; nz = 0; nz += _find_zero( p[4], p[k], zeros[nz] ); nz += _find_zero( p[k], p[kk], zeros[nz] ); nz += _find_zero( p[kk], p[4], zeros[nz] ); if ( nz == 2 ) { segs[ns][0] = zeros[0][0]; segs[ns][1] = zeros[0][1]; segs[ns][2] = zeros[1][0]; segs[ns][3] = zeros[1][1]; ns++; } } return ns; } static PyObject * biggles_contour_segments( PyObject *self, PyObject *args ) { PyObject *ox, *oy, *oz, *list, *ref; PyArrayObject *x, *y, *z; double z0; double segs[MAX_SEGS][4]; int i, j, k, ns; list = NULL; if ( !PyArg_ParseTuple(args, "OOOd", &ox, &oy, &oz, &z0) ) return NULL; x = (PyArrayObject *) PyArray_ContiguousFromObject( ox, PyArray_DOUBLE, 1, 1 ); y = (PyArrayObject *) PyArray_ContiguousFromObject( oy, PyArray_DOUBLE, 1, 1 ); z = (PyArrayObject *) PyArray_ContiguousFromObject( oz, PyArray_DOUBLE, 2, 2 ); if ( x == NULL || y == NULL || z == NULL ) goto quit; if ( z->dimensions[0] != x->dimensions[0] || z->dimensions[1] != y->dimensions[0] ) { PyErr_SetString( PyExc_ValueError, "array dimensions are not compatible" ); goto quit; } list = PyList_New( 0 ); if ( list == NULL ) goto quit; for ( i = 0; i < z->dimensions[0]-1; i++ ) for ( j = 0; j < z->dimensions[1]-1; j++ ) { ns = _pixel_interpolate( x, y, z, z0, i, j, segs ); for ( k = 0; k < ns; k++ ) { ref = Py_BuildValue( "((dd)(dd))", segs[k][0], segs[k][1], segs[k][2], segs[k][3] ); PyList_Append( list, ref ); Py_DECREF(ref); /* ??? */ } } quit: Py_XDECREF(x); Py_XDECREF(y); Py_XDECREF(z); return list; } /****************************************************************************** * hammer.py */ static void _lb2xyz( double l, double b, double *x, double *y, double *z ) { *x = cos(b)*cos(l); *y = cos(b)*sin(l); *z = sin(b); } static void _xyz2lb( double x, double y, double z, double *l, double *b) { *l = atan2( y, x ); *b = asin( z/sqrt(x*x + y*y + z*z) ); } static void _x_rotate( double l, double b, double theta, double *ll, double *bb ) { double x, y, z, y2, z2; _lb2xyz( l, b, &x, &y, &z ); y2 = cos(theta)*y - sin(theta)*z; z2 = cos(theta)*z + sin(theta)*y; _xyz2lb( x, y2, z2, ll, bb ); } static void _y_rotate( double l, double b, double theta, double *ll, double *bb ) { double x, y, z, x2, z2; _lb2xyz( l, b, &x, &y, &z ); x2 = cos(theta)*x - sin(theta)*z; z2 = cos(theta)*z + sin(theta)*x; _xyz2lb( x2, y, z2, ll, bb ); } static void _z_rotate( double l, double b, double theta, double *ll, double *bb ) { *ll = atan2( sin(l+theta), cos(l+theta) ); *bb = b; } static void _lb_input( double l, double b, double l0, double b0, double rot, double *ll, double *bb ) { double p, q, r, s; _z_rotate( l, b, -l0, &p, &q ); _y_rotate( p, q, -b0, &r, &s ); _x_rotate( r, s, rot, ll, bb ); } static void _lb2uv( double l, double b, double *u, double *v ) { double q; q = sqrt( 1. + cos(b)*cos(0.5*l) ); *u = cos(b)*sin(0.5*l)/q; *v = 0.5*sin(b)/q; } static PyObject * biggles_hammer_call( PyObject *self, PyObject *args ) { double l, b, l0, b0, rot; double ll, bb, u, v; if ( !PyArg_ParseTuple(args, "ddddd", &l, &b, &l0, &b0, &rot) ) return NULL; _lb_input( l, b, l0, b0, rot, &ll, &bb ); _lb2uv( ll, bb, &u, &v ); return Py_BuildValue( "dd", u, v ); } static PyObject * biggles_hammer_call_vec( PyObject *self, PyObject *args ) { PyObject *ol, *ob, *ret; PyArrayObject *l, *b, *u, *v; double l0, b0, rot; double ll, bb; int i, n; ret = NULL; if ( !PyArg_ParseTuple(args, "OOddd", &ol, &ob, &l0, &b0, &rot) ) return NULL; l = (PyArrayObject *) PyArray_ContiguousFromObject( ol, PyArray_DOUBLE, 1, 1 ); b = (PyArrayObject *) PyArray_ContiguousFromObject( ob, PyArray_DOUBLE, 1, 1 ); if ( l == NULL || b == NULL ) goto quit0; n = BGL_MIN( l->dimensions[0], b->dimensions[0] ); u = (PyArrayObject *) PyArray_FromDims( 1, &n, PyArray_DOUBLE ); v = (PyArrayObject *) PyArray_FromDims( 1, &n, PyArray_DOUBLE ); if ( u == NULL || v == NULL ) goto quit1; for ( i = 0; i < n; i++ ) { _lb_input( PyArray_1D(l,i), PyArray_1D(b,i), l0, b0, rot, &ll, &bb ); _lb2uv( ll, bb, PyArray_1D_ptr(u,i), PyArray_1D_ptr(v,i) ); } ret = Py_BuildValue( "OO", u, v ); quit1: Py_XDECREF(u); Py_XDECREF(v); quit0: Py_XDECREF(l); Py_XDECREF(b); return ret; } static void _lb_geodesic( int div, double l0, double b0, double l1, double b1, double l[], double b[] ) { double lr0, br0, lr1, br1; double db; int i; _z_rotate( l1, b1, -l0, &lr0, &br0 ); _y_rotate( lr0, br0, -b0+M_PI/2, &lr1, &br1 ); l[0] = l0; b[0] = b0; db = (br1 - M_PI/2)/div; for ( i = 1; i < div; i++ ) { _y_rotate( lr1, M_PI/2 + i*db, -M_PI/2+b0, &lr0, &br0 ); _z_rotate( lr0, br0, l0, l+i, b+i ); } l[div] = l1; b[div] = b1; } static PyObject * biggles_hammer_geodesic_fill( PyObject *self, PyObject *args ) { PyObject *ol, *ob, *ref; PyArrayObject *l, *b, *l2, *b2; int i, n, div, dims[1]; ref = NULL; if ( !PyArg_ParseTuple(args, "OOi", &ol, &ob, &div) ) return NULL; l = (PyArrayObject *) PyArray_ContiguousFromObject( ol, PyArray_DOUBLE, 1, 1 ); b = (PyArrayObject *) PyArray_ContiguousFromObject( ob, PyArray_DOUBLE, 1, 1 ); if ( l == NULL || b == NULL ) { Py_XDECREF(l); Py_XDECREF(b); return NULL; } n = l->dimensions[0]; dims[0] = (n-1)*div + 1; l2 = (PyArrayObject *) PyArray_FromDims( 1, dims, PyArray_DOUBLE ); b2 = (PyArrayObject *) PyArray_FromDims( 1, dims, PyArray_DOUBLE ); if ( l2 == NULL || b2 == NULL ) goto quit; for ( i = 0; i < n-1; i++ ) _lb_geodesic( div, PyArray_1D(l,i), PyArray_1D(b,i), PyArray_1D(l,i+1), PyArray_1D(b,i+1), ((double *)l2->data) + i*div, ((double *)b2->data) + i*div ); ref = Py_BuildValue( "OO", l2, b2 ); quit: Py_DECREF(l); Py_DECREF(b); Py_XDECREF(l2); Py_XDECREF(b2); return ref; } static PyObject * biggles_hammer_connect( PyObject *self, PyObject *args ) { double l1, b1, l2, b2; double l0, b0, rot; double ll1, bb1, ll2, bb2; int connect; if ( !PyArg_ParseTuple(args, "ddddddd", &l1, &b1, &l2, &b2, &l0, &b0, &rot) ) return NULL; _lb_input( l1, b1, l0, b0, rot, &ll1, &bb1 ); _lb_input( l2, b2, l0, b0, rot, &ll2, &bb2 ); connect = sin(ll1)*sin(ll2) < 0.; return Py_BuildValue( "i", connect ); } /****************************************************************************** * module init */ static PyMethodDef BigglesMethods[] = { /* general */ { "range", biggles_range, METH_VARARGS }, /* contour.py */ { "contour_segments", biggles_contour_segments, METH_VARARGS }, /* hammer.py */ { "hammer_call", biggles_hammer_call, METH_VARARGS }, { "hammer_call_vec", biggles_hammer_call_vec, METH_VARARGS }, { "hammer_connect", biggles_hammer_connect, METH_VARARGS }, { "hammer_geodesic_fill", biggles_hammer_geodesic_fill, METH_VARARGS }, { NULL, NULL } }; void init_biggles( void ) { Py_InitModule( "_biggles", BigglesMethods ); import_array(); }