qwt_raster_data.cpp 11.5 KB
Newer Older
pixhawk's avatar
pixhawk committed
1 2 3 4 5 6 7 8 9 10
/* -*- mode: C++ ; c-file-style: "stroustrup" -*- *****************************
 * Qwt Widget Library
 * Copyright (C) 1997   Josef Wilgen
 * Copyright (C) 2002   Uwe Rathmann
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the Qwt License, Version 1.0
 *****************************************************************************/

#include "qwt_raster_data.h"
Bryant's avatar
Bryant committed
11 12
#include "qwt_point_3d.h"
#include <qnumeric.h>
pixhawk's avatar
pixhawk committed
13 14 15 16

class QwtRasterData::ContourPlane
{
public:
Bryant's avatar
Bryant committed
17 18 19
    inline ContourPlane( double z ):
        d_z( z )
    {
pixhawk's avatar
pixhawk committed
20 21
    }

Bryant's avatar
Bryant committed
22 23
    inline bool intersect( const QwtPoint3D vertex[3],
        QPointF line[2], bool ignoreOnPlane ) const;
pixhawk's avatar
pixhawk committed
24

Bryant's avatar
Bryant committed
25
    inline double z() const { return d_z; }
pixhawk's avatar
pixhawk committed
26 27

private:
Bryant's avatar
Bryant committed
28 29 30
    inline int compare( double z ) const;
    inline QPointF intersection(
        const QwtPoint3D& p1, const QwtPoint3D &p2 ) const;
pixhawk's avatar
pixhawk committed
31 32 33 34 35

    double d_z;
};

inline bool QwtRasterData::ContourPlane::intersect(
Bryant's avatar
Bryant committed
36 37
    const QwtPoint3D vertex[3], QPointF line[2],
    bool ignoreOnPlane ) const
pixhawk's avatar
pixhawk committed
38 39 40 41
{
    bool found = true;

    // Are the vertices below (-1), on (0) or above (1) the plan ?
Bryant's avatar
Bryant committed
42 43 44
    const int eq1 = compare( vertex[0].z() );
    const int eq2 = compare( vertex[1].z() );
    const int eq3 = compare( vertex[2].z() );
pixhawk's avatar
pixhawk committed
45 46 47 48 49 50 51 52 53 54 55 56 57 58

    /*
        (a) All the vertices lie below the contour level.
        (b) Two vertices lie below and one on the contour level.
        (c) Two vertices lie below and one above the contour level.
        (d) One vertex lies below and two on the contour level.
        (e) One vertex lies below, one on and one above the contour level.
        (f) One vertex lies below and two above the contour level.
        (g) Three vertices lie on the contour level.
        (h) Two vertices lie on and one above the contour level.
        (i) One vertex lies on and two above the contour level.
        (j) All the vertices lie above the contour level.
     */

Bryant's avatar
Bryant committed
59 60
    static const int tab[3][3][3] =
    {
pixhawk's avatar
pixhawk committed
61 62 63 64 65 66 67
        // jump table to avoid nested case statements
        { { 0, 0, 8 }, { 0, 2, 5 }, { 7, 6, 9 } },
        { { 0, 3, 4 }, { 1, 10, 1 }, { 4, 3, 0 } },
        { { 9, 6, 7 }, { 5, 2, 0 }, { 8, 0, 0 } }
    };

    const int edgeType = tab[eq1+1][eq2+1][eq3+1];
Bryant's avatar
Bryant committed
68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128
    switch ( edgeType )
    {
        case 1:
            // d(0,0,-1), h(0,0,1)
            line[0] = vertex[0].toPoint();
            line[1] = vertex[1].toPoint();
            break;
        case 2:
            // d(-1,0,0), h(1,0,0)
            line[0] = vertex[1].toPoint();
            line[1] = vertex[2].toPoint();
            break;
        case 3:
            // d(0,-1,0), h(0,1,0)
            line[0] = vertex[2].toPoint();
            line[1] = vertex[0].toPoint();
            break;
        case 4:
            // e(0,-1,1), e(0,1,-1)
            line[0] = vertex[0].toPoint();
            line[1] = intersection( vertex[1], vertex[2] );
            break;
        case 5:
            // e(-1,0,1), e(1,0,-1)
            line[0] = vertex[1].toPoint();
            line[1] = intersection( vertex[2], vertex[0] );
            break;
        case 6:
            // e(-1,1,0), e(1,0,-1)
            line[0] = vertex[2].toPoint();
            line[1] = intersection( vertex[0], vertex[1] );
            break;
        case 7:
            // c(-1,1,-1), f(1,1,-1)
            line[0] = intersection( vertex[0], vertex[1] );
            line[1] = intersection( vertex[1], vertex[2] );
            break;
        case 8:
            // c(-1,-1,1), f(1,1,-1)
            line[0] = intersection( vertex[1], vertex[2] );
            line[1] = intersection( vertex[2], vertex[0] );
            break;
        case 9:
            // f(-1,1,1), c(1,-1,-1)
            line[0] = intersection( vertex[2], vertex[0] );
            line[1] = intersection( vertex[0], vertex[1] );
            break;
        case 10:
            // g(0,0,0)
            // The CONREC algorithm has no satisfying solution for
            // what to do, when all vertices are on the plane.

            if ( ignoreOnPlane )
                found = false;
            else
            {
                line[0] = vertex[2].toPoint();
                line[1] = vertex[0].toPoint();
            }
            break;
        default:
129
            found = false;
pixhawk's avatar
pixhawk committed
130 131 132 133 134
    }

    return found;
}

Bryant's avatar
Bryant committed
135
inline int QwtRasterData::ContourPlane::compare( double z ) const
pixhawk's avatar
pixhawk committed
136
{
Bryant's avatar
Bryant committed
137
    if ( z > d_z )
pixhawk's avatar
pixhawk committed
138 139
        return 1;

Bryant's avatar
Bryant committed
140
    if ( z < d_z )
pixhawk's avatar
pixhawk committed
141 142 143 144 145
        return -1;

    return 0;
}

Bryant's avatar
Bryant committed
146 147
inline QPointF QwtRasterData::ContourPlane::intersection(
    const QwtPoint3D& p1, const QwtPoint3D &p2 ) const
pixhawk's avatar
pixhawk committed
148 149 150 151
{
    const double h1 = p1.z() - d_z;
    const double h2 = p2.z() - d_z;

Bryant's avatar
Bryant committed
152 153
    const double x = ( h2 * p1.x() - h1 * p2.x() ) / ( h2 - h1 );
    const double y = ( h2 * p1.y() - h1 * p2.y() ) / ( h2 - h1 );
pixhawk's avatar
pixhawk committed
154

Bryant's avatar
Bryant committed
155
    return QPointF( x, y );
pixhawk's avatar
pixhawk committed
156 157
}

Bryant's avatar
Bryant committed
158
//! Constructor
pixhawk's avatar
pixhawk committed
159 160 161 162
QwtRasterData::QwtRasterData()
{
}

Bryant's avatar
Bryant committed
163
//! Destructor
pixhawk's avatar
pixhawk committed
164 165 166 167
QwtRasterData::~QwtRasterData()
{
}

Bryant's avatar
Bryant committed
168 169
/*!
   Set the bounding interval for the x, y or z coordinates.
pixhawk's avatar
pixhawk committed
170

Bryant's avatar
Bryant committed
171 172 173 174 175 176
   \param axis Axis
   \param interval Bounding interval

   \sa interval()
*/
void QwtRasterData::setInterval( Qt::Axis axis, const QwtInterval &interval )
pixhawk's avatar
pixhawk committed
177
{
Bryant's avatar
Bryant committed
178
    d_intervals[axis] = interval;
pixhawk's avatar
pixhawk committed
179 180 181 182 183
}

/*!
  \brief Initialize a raster

Bryant's avatar
Bryant committed
184
  Before the composition of an image QwtPlotSpectrogram calls initRaster(),
pixhawk's avatar
pixhawk committed
185
  announcing the area and its resolution that will be requested.
186

pixhawk's avatar
pixhawk committed
187
  The default implementation does nothing, but for data sets that
Bryant's avatar
Bryant committed
188
  are stored in files, it might be good idea to reimplement initRaster(),
pixhawk's avatar
pixhawk committed
189
  where the data is resampled and loaded into memory.
190

Bryant's avatar
Bryant committed
191
  \param area Area of the raster
pixhawk's avatar
pixhawk committed
192 193 194 195
  \param raster Number of horizontal and vertical pixels

  \sa initRaster(), value()
*/
Bryant's avatar
Bryant committed
196
void QwtRasterData::initRaster( const QRectF &area, const QSize &raster )
pixhawk's avatar
pixhawk committed
197
{
Bryant's avatar
Bryant committed
198 199
    Q_UNUSED( area );
    Q_UNUSED( raster );
pixhawk's avatar
pixhawk committed
200 201 202 203 204 205
}

/*!
  \brief Discard a raster

  After the composition of an image QwtPlotSpectrogram calls discardRaster().
206

pixhawk's avatar
pixhawk committed
207 208 209 210 211 212 213 214 215 216
  The default implementation does nothing, but if data has been loaded
  in initRaster(), it could deleted now.

  \sa initRaster(), value()
*/
void QwtRasterData::discardRaster()
{
}

/*!
Bryant's avatar
Bryant committed
217
   \brief Pixel hint
pixhawk's avatar
pixhawk committed
218

Bryant's avatar
Bryant committed
219 220 221 222 223 224 225 226
   pixelHint() returns the geometry of a pixel, that can be used 
   to calculate the resolution and alignment of the plot item, that is
   representing the data. 
   
   Width and height of the hint need to be the horizontal  
   and vertical distances between 2 neighbored points. 
   The center of the hint has to be the position of any point 
   ( it doesn't matter which one ).
pixhawk's avatar
pixhawk committed
227

Bryant's avatar
Bryant committed
228
   An empty hint indicates, that there are values for any detail level.
229

Bryant's avatar
Bryant committed
230 231 232
   Limiting the resolution of the image might significantly improve
   the performance and heavily reduce the amount of memory when rendering
   a QImage from the raster data. 
pixhawk's avatar
pixhawk committed
233

Bryant's avatar
Bryant committed
234 235
   The default implementation returns an empty rectangle recommending
   to render in target device ( f.e. screen ) resolution.
pixhawk's avatar
pixhawk committed
236

Bryant's avatar
Bryant committed
237 238 239 240
   \param area In most implementations the resolution of the data doesn't
               depend on the requested area.

   \return Bounding rectangle of a pixel 
pixhawk's avatar
pixhawk committed
241
*/
Bryant's avatar
Bryant committed
242
QRectF QwtRasterData::pixelHint( const QRectF &area ) const
pixhawk's avatar
pixhawk committed
243
{
Bryant's avatar
Bryant committed
244 245
    Q_UNUSED( area );
    return QRectF(); 
pixhawk's avatar
pixhawk committed
246 247 248 249
}

/*!
   Calculate contour lines
250

Bryant's avatar
Bryant committed
251 252 253 254 255 256 257
   \param rect Bounding rectangle for the contour lines
   \param raster Number of data pixels of the raster data
   \param levels List of limits, where to insert contour lines
   \param flags Flags to customize the contouring algorithm

   \return Calculated contour lines

pixhawk's avatar
pixhawk committed
258 259
   An adaption of CONREC, a simple contouring algorithm.
   http://local.wasp.uwa.edu.au/~pbourke/papers/conrec/
260
*/
pixhawk's avatar
pixhawk committed
261
QwtRasterData::ContourLines QwtRasterData::contourLines(
Bryant's avatar
Bryant committed
262 263
    const QRectF &rect, const QSize &raster,
    const QList<double> &levels, ConrecFlags flags ) const
264
{
pixhawk's avatar
pixhawk committed
265
    ContourLines contourLines;
266

pixhawk's avatar
pixhawk committed
267 268 269 270 271 272 273 274 275
    if ( levels.size() == 0 || !rect.isValid() || !raster.isValid() )
        return contourLines;

    const double dx = rect.width() / raster.width();
    const double dy = rect.height() / raster.height();

    const bool ignoreOnPlane =
        flags & QwtRasterData::IgnoreAllVerticesOnLevel;

Bryant's avatar
Bryant committed
276
    const QwtInterval range = interval( Qt::ZAxis );
pixhawk's avatar
pixhawk committed
277 278 279 280
    bool ignoreOutOfRange = false;
    if ( range.isValid() )
        ignoreOutOfRange = flags & IgnoreOutOfRange;

Bryant's avatar
Bryant committed
281 282
    QwtRasterData *that = const_cast<QwtRasterData *>( this );
    that->initRaster( rect, raster );
pixhawk's avatar
pixhawk committed
283

Bryant's avatar
Bryant committed
284 285 286 287
    for ( int y = 0; y < raster.height() - 1; y++ )
    {
        enum Position
        {
pixhawk's avatar
pixhawk committed
288 289 290 291 292 293 294 295 296 297
            Center,

            TopLeft,
            TopRight,
            BottomRight,
            BottomLeft,

            NumPositions
        };

Bryant's avatar
Bryant committed
298
        QwtPoint3D xy[NumPositions];
pixhawk's avatar
pixhawk committed
299

Bryant's avatar
Bryant committed
300 301 302
        for ( int x = 0; x < raster.width() - 1; x++ )
        {
            const QPointF pos( rect.x() + x * dx, rect.y() + y * dy );
pixhawk's avatar
pixhawk committed
303

Bryant's avatar
Bryant committed
304 305 306 307
            if ( x == 0 )
            {
                xy[TopRight].setX( pos.x() );
                xy[TopRight].setY( pos.y() );
pixhawk's avatar
pixhawk committed
308
                xy[TopRight].setZ(
Bryant's avatar
Bryant committed
309
                    value( xy[TopRight].x(), xy[TopRight].y() )
pixhawk's avatar
pixhawk committed
310 311
                );

Bryant's avatar
Bryant committed
312 313
                xy[BottomRight].setX( pos.x() );
                xy[BottomRight].setY( pos.y() + dy );
pixhawk's avatar
pixhawk committed
314
                xy[BottomRight].setZ(
Bryant's avatar
Bryant committed
315
                    value( xy[BottomRight].x(), xy[BottomRight].y() )
pixhawk's avatar
pixhawk committed
316 317 318 319 320 321
                );
            }

            xy[TopLeft] = xy[TopRight];
            xy[BottomLeft] = xy[BottomRight];

Bryant's avatar
Bryant committed
322 323 324 325
            xy[TopRight].setX( pos.x() + dx );
            xy[TopRight].setY( pos.y() );
            xy[BottomRight].setX( pos.x() + dx );
            xy[BottomRight].setY( pos.y() + dy );
pixhawk's avatar
pixhawk committed
326 327

            xy[TopRight].setZ(
Bryant's avatar
Bryant committed
328
                value( xy[TopRight].x(), xy[TopRight].y() )
pixhawk's avatar
pixhawk committed
329 330
            );
            xy[BottomRight].setZ(
Bryant's avatar
Bryant committed
331
                value( xy[BottomRight].x(), xy[BottomRight].y() )
pixhawk's avatar
pixhawk committed
332 333 334 335 336 337
            );

            double zMin = xy[TopLeft].z();
            double zMax = zMin;
            double zSum = zMin;

Bryant's avatar
Bryant committed
338 339
            for ( int i = TopRight; i <= BottomLeft; i++ )
            {
pixhawk's avatar
pixhawk committed
340 341 342 343 344 345 346 347 348
                const double z = xy[i].z();

                zSum += z;
                if ( z < zMin )
                    zMin = z;
                if ( z > zMax )
                    zMax = z;
            }

Bryant's avatar
Bryant committed
349 350 351 352 353 354 355 356 357
            if ( qIsNaN( zSum ) )
            {
                // one of the points is NaN
                continue;
            }

            if ( ignoreOutOfRange )
            {
                if ( !range.contains( zMin ) || !range.contains( zMax ) )
pixhawk's avatar
pixhawk committed
358 359 360 361
                    continue;
            }

            if ( zMax < levels[0] ||
Bryant's avatar
Bryant committed
362 363
                zMin > levels[levels.size() - 1] )
            {
pixhawk's avatar
pixhawk committed
364 365 366
                continue;
            }

Bryant's avatar
Bryant committed
367 368 369 370 371 372 373
            xy[Center].setX( pos.x() + 0.5 * dx );
            xy[Center].setY( pos.y() + 0.5 * dy );
            xy[Center].setZ( 0.25 * zSum );

            const int numLevels = levels.size();
            for ( int l = 0; l < numLevels; l++ )
            {
pixhawk's avatar
pixhawk committed
374 375 376 377
                const double level = levels[l];
                if ( level < zMin || level > zMax )
                    continue;
                QPolygonF &lines = contourLines[level];
Bryant's avatar
Bryant committed
378
                const ContourPlane plane( level );
pixhawk's avatar
pixhawk committed
379

Bryant's avatar
Bryant committed
380 381
                QPointF line[2];
                QwtPoint3D vertex[3];
pixhawk's avatar
pixhawk committed
382

Bryant's avatar
Bryant committed
383 384
                for ( int m = TopLeft; m < NumPositions; m++ )
                {
pixhawk's avatar
pixhawk committed
385 386 387 388 389
                    vertex[0] = xy[m];
                    vertex[1] = xy[0];
                    vertex[2] = xy[m != BottomLeft ? m + 1 : TopLeft];

                    const bool intersects =
Bryant's avatar
Bryant committed
390 391 392
                        plane.intersect( vertex, line, ignoreOnPlane );
                    if ( intersects )
                    {
pixhawk's avatar
pixhawk committed
393 394 395 396 397 398 399 400
                        lines += line[0];
                        lines += line[1];
                    }
                }
            }
        }
    }

Bryant's avatar
Bryant committed
401
    that->discardRaster();
pixhawk's avatar
pixhawk committed
402 403 404

    return contourLines;
}