diff --git a/src/qgsclipper.cpp b/src/qgsclipper.cpp index 73f91db0f52..292ee04cbd0 100644 --- a/src/qgsclipper.cpp +++ b/src/qgsclipper.cpp @@ -223,13 +223,6 @@ bool QgsClipper::trimLine(QgsPoint& from, QgsPoint& to) } } - // Too verbose for QGISDEBUG, but handy sometimes. - /* - std::cerr << "Point 1 trimmed from " << from.x() << ", " << from.y() - << " to " << tFrom.x() << ", " << tFrom.y() << '\n' - << "Point 2 trimmed from " << to.x() << ", " << to.y() - << " to " << tTo.x() << ", " << tTo.y() << "\n\n"; - */ to = trimmedTo; from = trimmedFrom; @@ -249,29 +242,28 @@ bool QgsClipper::trimLine(QgsPoint& from, QgsPoint& to) void QgsClipper::trimPolygon(std::vector& polygon) { - // Not the most efficient way - needs to be redone. Efficiency is - // important here, so std::vector<> might not be the most - // appropriate data structure. + std::vector tmp; + trimPolygonToBoundary(polygon, tmp, Xmax); - std::vector tmp1; - std::vector tmp2; - std::vector tmp3; - std::vector tmp4; + polygon.resize(0); + trimPolygonToBoundary(tmp, polygon, Ymax); - trimPolygonToBoundary(polygon, tmp1, Xmax); - trimPolygonToBoundary(tmp1, tmp2, Ymax); - trimPolygonToBoundary(tmp2, tmp3, Xmin); - trimPolygonToBoundary(tmp3, tmp4, Ymin); - polygon = tmp4; + tmp.resize(0); + trimPolygonToBoundary(polygon, tmp, Xmin); + + polygon.resize(0); + trimPolygonToBoundary(tmp, polygon, Ymin); } // An auxilary function that is part of the polygon trimming // code. Will trim the given polygon to the given boundary and return // the trimmed polygon in the out pointer. Uses Sutherland and // Hodgman's polygon-clipping algorithm. +// Need to keep track of how the trimming alters the contents of rings void QgsClipper::trimPolygonToBoundary(const std::vector& in, - std::vector& out, boundary b) + std::vector& out, + boundary b) { int i1 = in.size()-1; // start with last point @@ -292,7 +284,8 @@ void QgsClipper::trimPolygonToBoundary(const std::vector& in, } else // end point of edge is outside boundary { - if (inside(in, i1, b)) // start point is in boundary, so need to trim back + // start point is in boundary, so need to trim back + if (inside(in, i1, b)) out.push_back( intersect(in, i1, i2, b) ); } i1 = i2; @@ -307,19 +300,19 @@ bool QgsClipper::inside(const std::vector& pa, int p, boundary b) switch (b) { case Xmax: // x < maxX is inside - if ((pa[p]).x() < maxX) + if (pa[p].x() < maxX) return true; break; case Xmin: // x > minX is inside - if ((pa[p]).x() > minX) + if (pa[p].x() > minX) return true; break; case Ymax: // y < maxY is inside - if ((pa[p]).y() < maxY) + if (pa[p].y() < maxY) return true; break; case Ymin: // y > minY is inside - if ((pa[p]).y() > minY) + if (pa[p].y() > minY) return true; break; } diff --git a/src/qgsclipper.h b/src/qgsclipper.h index 2bba1d005d9..30b83415b73 100644 --- a/src/qgsclipper.h +++ b/src/qgsclipper.h @@ -24,6 +24,7 @@ #include "qpointarray.h" #include +#include // The functions in this class are likely to be called from within a // render loop and hence need to as CPU efficient as possible. @@ -59,8 +60,6 @@ class QgsClipper // Trims the given polygon to a rectangular box. Returns the trimmed // polygon in the given QPointArray. - // static void trimPolygon(QPointArray* pa); - static void trimPolygon(std::vector& polygon); private: @@ -68,7 +67,7 @@ class QgsClipper // Trims the given polygon to the given boundary. Returns the // trimmed polygon in the out pointer. static void trimPolygonToBoundary(const std::vector& in, - std::vector& out, + std::vector& out, boundary b); // Determines if a point is inside or outside a boundary @@ -76,7 +75,8 @@ class QgsClipper // Calculates the intersection point between a line defined by a // line segment and a boundary - static QgsPoint intersect(const std::vector& pa, int i1, int i2, boundary b); + static QgsPoint intersect(const std::vector& pa, + int i1, int i2, boundary b); }; diff --git a/src/qgsvectorlayer.cpp b/src/qgsvectorlayer.cpp index b976618ed75..cf50b7a05c4 100644 --- a/src/qgsvectorlayer.cpp +++ b/src/qgsvectorlayer.cpp @@ -29,6 +29,7 @@ #include #include #include +#include #include // for htonl @@ -81,7 +82,9 @@ #include "qgslabel.h" #include "qgscoordinatetransform.h" #include "qgsattributedialog.h" +#ifdef Q_WS_X11 #include "qgsclipper.h" +#endif #include "qgssvgcache.h" #include "qgslayerprojectionselector.h" //#include "wkbheader.h" @@ -376,6 +379,146 @@ void QgsVectorLayer::drawLabels(QPainter * p, QgsRect * viewExtent, QgsMapToPixe } } +unsigned char* QgsVectorLayer::drawPolygon(unsigned char* feature, + QPainter* p, + QgsMapToPixel* mtp, + bool projectionsEnabledFlag) +{ + double *x, *y; + // get number of rings in the polygon + unsigned int numRings = *((int*)(feature + 1 + sizeof(int))); + + if ( numRings == 0 ) // sanity check for zero rings in polygon + return feature + 9; + + int total_points = 0; + QgsPoint pt, myProjectedPoint; + std::vector< std::vector* > rings; + + // set pointer to the first ring + unsigned char* ptr = feature + 1 + 2 * sizeof(int); + for (unsigned int idx = 0; idx < numRings; idx++) + { +#if defined(Q_WS_X11) + bool needToTrim = false; +#endif + + // get number of points in the ring + int nPoints = *((int*)ptr); + std::vector* ring = new std::vector(nPoints); + ptr += 4; + + for (unsigned int jdx = 0; jdx < nPoints; jdx++) + { + // add points to a point array for drawing the polygon + x = (double *) ptr; + ptr += sizeof(double); + y = (double *) ptr; + ptr += sizeof(double); + pt.set(*x, *y); + + if (projectionsEnabledFlag) + { + // reproject the point to the map coordinate system + try + { + myProjectedPoint=mCoordinateTransform->transform(pt); + } + catch (QgsCsException &e) + { + qDebug( "Transform error caught in %s line %d:\n%s", + __FILE__, __LINE__, e.what()); + } + // transform from projected coordinate system to pixel + // position on map canvas + mtp->transform(&myProjectedPoint); + } + else + { + myProjectedPoint=mtp->transform(pt); + } +#if defined(Q_WS_X11) + if (std::abs(myProjectedPoint.x()) > QgsClipper::maxX || + std::abs(myProjectedPoint.y()) > QgsClipper::maxY) + needToTrim = true; +#endif + ring->operator[](jdx) = myProjectedPoint; + } + +#if defined(Q_WS_X11) + // Work around a +/- 32768 limitation on coordinates in X11 + if (needToTrim) + QgsClipper::trimPolygon(*ring); +#endif + // Don't bother if the ring has been trimmed out of + // existence. + if (ring->size() > 0) + rings.push_back(ring); + total_points += ring->size(); + } + + int ii = 0; + QPoint outerRingPt; + + // Stores the start positon of each ring (first) and the number + // of points in each ring (second). + std::vector > ringDetails; + + // Need to copy the polygon vertices into a QPointArray for the + // QPainter::drawpolygon() call. The size is the sum of points in + // the polygon plus one extra point for each ring except for the + // first ring. + QPointArray *pa = new QPointArray(total_points + rings.size() - 1); + + for (int i = 0; i < rings.size(); ++i) + { + // Store the start index of this ring, and the number of + // points in the ring. + ringDetails.push_back(std::make_pair(ii, rings[i]->size())); + + // Transfer points to the QPointArray + std::vector::const_iterator j = rings[i]->begin(); + for (; j != rings[i]->end(); ++j) + pa->setPoint(ii++, static_cast(round(j->x())), + static_cast(round(j->y()))); + + // Store the last point of the first ring, and insert it at + // the end of all other rings. This makes all the other rings + // appear as holes in the first ring. + if (i == 0) + { + outerRingPt.setX(pa->point(ii-1).x()); + outerRingPt.setY(pa->point(ii-1).y()); + } + else + pa->setPoint(ii++, outerRingPt); + + delete rings[i]; + } + + // draw the polygon fill + QPen pen = p->pen(); // store current pen + p->setPen ( Qt::NoPen ); // no boundary + p->drawPolygon(*pa); + p->setPen ( pen ); + + // draw the polygon outline. Draw each ring as a separate + // polygon to avoid the lines associated with the outerRingPt. + QBrush brush = p->brush(); + p->setBrush ( Qt::NoBrush ); + std::vector >::const_iterator + ri = ringDetails.begin(); + for (; ri != ringDetails.end(); ++ri) + p->drawPolygon( *pa, FALSE, ri->first, ri->second); + + p->setBrush ( brush ); + + delete pa; + + return ptr; +} + + void QgsVectorLayer::draw(QPainter * p, QgsRect * viewExtent, QgsMapToPixel * theMapToPixelTransform, QPaintDevice* dst) { QSettings settings; @@ -2163,7 +2306,55 @@ void QgsVectorLayer::drawFeature(QPainter* p, QgsFeature* fet, QgsMapToPixel * t } case WKBMultiPoint: { - std::cerr << "Qgis doesn't draw multipoints yet.\n"; + QgsPoint myProjectedPoint; + + unsigned char *ptr = feature + 5; + unsigned int nPoints = *((int*)ptr); + ptr += 4; + + p->save(); + p->scale(markerScaleFactor,markerScaleFactor); + + for (int i = 0; i < nPoints; ++i) + { + ptr += 5; + double *x = (double *) ptr; + ptr += sizeof(double); + double *y = (double *) ptr; + ptr += sizeof(double); + + QgsPoint pt(*x, *y); + + if (projectionsEnabledFlag) + { + // reproject the point to the map coordinate system + try + { + myProjectedPoint=mCoordinateTransform->transform(pt); + } + catch (QgsCsException &e) + { + qDebug( "Transform error caught in %s line %d:\n%s", + __FILE__, __LINE__, e.what()); + } + // transform from projected coordinate system to pixel position + // on map canvas + theMapToPixelTransform->transform(&myProjectedPoint); + } + else + { + myProjectedPoint=theMapToPixelTransform->transform(pt); + } + + p->drawPicture((int)(static_cast(myProjectedPoint.x()) + / markerScaleFactor - marker->boundingRect().x() + - marker->boundingRect().width() / 2), + (int)(static_cast(myProjectedPoint.y()) + / markerScaleFactor - marker->boundingRect().y() + - marker->boundingRect().height() / 2), + *marker); + } + p->restore(); break; } @@ -2237,7 +2428,7 @@ void QgsVectorLayer::drawFeature(QPainter* p, QgsFeature* fet, QgsMapToPixel * t case WKBMultiLineString: { QgsPoint pt, ptFrom, ptTo; - unsigned int numLineStrings = *((int*)feature[5]); + unsigned int numLineStrings = *((int*)feature + 5); unsigned char *ptr = feature + 9; double *x, *y; @@ -2306,190 +2497,18 @@ void QgsVectorLayer::drawFeature(QPainter* p, QgsFeature* fet, QgsMapToPixel * t } case WKBPolygon: { - double *x, *y; - // get number of rings in the polygon - unsigned int numRings = *((int*)(feature + 1 + sizeof(int))); - - if ( numRings == 0 ) // sanity check for zero rings in polygon - { - break; - } - - // index of first point for each ring - unsigned int *ringStart = new unsigned int[numRings]; - // number of points in each ring - unsigned int *ringNumPoints = new unsigned int[numRings]; - - std::vector polygon; - int pdx = 0; - QgsPoint pt, outerRingPt, myProjectedPoint; - - unsigned char* ptr = feature + 1 + 2 * sizeof(int); // set pointer to the first ring - for (unsigned int idx = 0; idx < numRings; idx++) - { - // get number of points in the ring - int nPoints = *((int*)ptr); - ringStart[idx] = pdx; - ringNumPoints[idx] = nPoints; - ptr += 4; - - // better to calc size for all rings before? - polygon.resize(polygon.size() + nPoints + 1); - - for (unsigned int jdx = 0; jdx < nPoints; jdx++) - { - // add points to a point array for drawing the polygon - x = (double *) ptr; - ptr += sizeof(double); - y = (double *) ptr; - ptr += sizeof(double); - pt.set(*x, *y); - - if (projectionsEnabledFlag) - { - // reproject the point to the map coordinate system - try - { - myProjectedPoint=mCoordinateTransform->transform(pt); - } - catch (QgsCsException &e) - { - qDebug( "Transform error caught in %s line %d:\n%s", - __FILE__, __LINE__, e.what()); - } - // transform from projected coordinate system to pixel - // position on map canvas - theMapToPixelTransform->transform(&myProjectedPoint); - } - else - { - myProjectedPoint=theMapToPixelTransform->transform(pt); - } -#if defined(Q_WS_X11) - if (std::abs(myProjectedPoint.x()) > QgsClipper::maxX || - std::abs(myProjectedPoint.y()) > QgsClipper::maxY) - needToTrim = true; -#endif - polygon[pdx++] = myProjectedPoint; - } - if ( idx == 0 ) - { // remember last outer ring point - outerRingPt = myProjectedPoint; - } - else - { // return to x0,y0 (inner rings - islands) - polygon[pdx++] = outerRingPt; - } - } - - // draw the polygon fill - QPen pen = p->pen(); // store current pen - p->setPen ( Qt::NoPen ); // no boundary -#if defined(Q_WS_X11) - // Work around a +/- 32768 limitation on coordinates in X11 - if (needToTrim) - QgsClipper::trimPolygon(polygon); -#endif - - QPointArray *pa = new QPointArray(polygon.size()); - for (int i = 0; i < polygon.size(); ++i) - pa->setPoint(i, static_cast(round(polygon[i].x())), - static_cast(round(polygon[i].y()))); - - p->drawPolygon(*pa); - - /* - // draw outline - p->setPen ( pen ); - p->setBrush ( Qt::NoBrush ); - for (idx = 0; idx < *numRings; idx++) - { - p->drawPolygon( *pa, FALSE, ringStart[idx], ringNumPoints[idx]); - } - */ - delete pa; - delete [] ringStart; - delete [] ringNumPoints; - + drawPolygon(feature, p, theMapToPixelTransform, + projectionsEnabledFlag); break; - } case WKBMultiPolygon: { - QgsPoint pt, myProjectedPoint; - double *x, *y; unsigned char *ptr = feature + 5; - // get the number of polygons unsigned int numPolygons = *((int*)ptr); ptr = feature + 9; for (int kdx = 0; kdx < numPolygons; kdx++) - { - // skip the endian and feature type info and - // get number of rings in the polygon - ptr += 5; - unsigned int numRings = *((int*)ptr); - ptr += 4; - for (unsigned int idx = 0; idx < numRings; idx++) - { - // get number of points in the ring - unsigned int nPoints = *((int*)ptr); - ptr += 4; - std::vector polygon(nPoints); - - for (unsigned int jdx = 0; jdx < nPoints; jdx++) - { - // add points to a point array for drawing the polygon - x = (double *) ptr; - ptr += sizeof(double); - y = (double *) ptr; - ptr += sizeof(double); -#ifdef QGISX11DEBUG - std::cout << "Transforming " << *x << "," << *y << " to "; -#endif - pt.set(*x, *y); - if (projectionsEnabledFlag) - { - // reproject the point to the map coordinate system - try - { - myProjectedPoint=mCoordinateTransform->transform(pt); - } - catch (QgsCsException &e) - { - qDebug( "Transform error caught in %s line %d:\n%s", - __FILE__, __LINE__, e.what()); - } - // transform from projected coordinate system to - // pixel position on map canvas - theMapToPixelTransform->transform(&myProjectedPoint); - } - else - { - myProjectedPoint=theMapToPixelTransform->transform(pt); - } -#if defined(Q_WS_X11) - if (std::abs(myProjectedPoint.x()) > QgsClipper::maxX || - std::abs(myProjectedPoint.y()) > QgsClipper::maxY) - needToTrim = true; - -#endif - polygon[jdx] = myProjectedPoint; - } - // draw the ring -#if defined(Q_WS_X11) - // Work around a +/- 32768 limitation on coordinates in X11 - if (needToTrim) - QgsClipper::trimPolygon(polygon); -#endif - QPointArray *pa = new QPointArray(polygon.size()); - for (int i = 0; i < polygon.size(); ++i) - pa->setPoint(i, static_cast(round(polygon[i].x())), - static_cast(round(polygon[i].y()))); - - p->drawPolygon(*pa); - delete pa; - } - } + ptr = drawPolygon(ptr, p, theMapToPixelTransform, + projectionsEnabledFlag); break; } default: diff --git a/src/qgsvectorlayer.h b/src/qgsvectorlayer.h index 32ade6ab99f..10e3f27e6e9 100644 --- a/src/qgsvectorlayer.h +++ b/src/qgsvectorlayer.h @@ -344,6 +344,11 @@ private: // Private attributes //! Draws the layer labels using coordinate transformation void drawLabels(QPainter * p, QgsRect * viewExtent, QgsMapToPixel * cXf, QPaintDevice * dst); + // Draw the polygon as given in the WKB format. Returns a pointer to + // the byte after the end of the polygon binary data stream (WKB). + unsigned char* drawPolygon(unsigned char* WKBpolygon, QPainter* p, + QgsMapToPixel* mtp, bool projectionsEnabledFlag); + /** tailor the right-click context menu with vector layer only stuff @note called by QgsMapLayer::initContextMenu();