void SE_Bounds::Transform(const SE_Matrix& xform) { double* last = hull + 2*size; double* cur = hull; min[0] = min[1] = +DBL_MAX; max[0] = max[1] = -DBL_MAX; while (cur < last) { xform.transform(cur[0], cur[1]); AddToBounds(cur[0], cur[1], min, max); cur += 2; } }
/* NOTE: This method is intentionally unchecked, the caller must ensure that the * destination SE_Bounds is large enough, that the source is valid, etc. */ void SE_Bounds::Transform(const SE_Matrix& xform, SE_Bounds* src) { double* last = src->hull + 2*src->size; double* cur = src->hull; double* dst = hull; min[0] = min[1] = +DBL_MAX; max[0] = max[1] = -DBL_MAX; size = src->size; pivot = src->pivot; while (cur < last) { xform.transform(cur[0], cur[1], dst[0], dst[1]); AddToBounds(dst[0], dst[1], min, max); cur += 2; dst += 2; } }
void SE_Renderer::DrawSymbol(SE_RenderPrimitiveList& symbol, const SE_Matrix& xform, double angleRad, bool excludeRegion) { RS_Bounds extents(DBL_MAX, DBL_MAX, DBL_MAX, -DBL_MAX, -DBL_MAX, -DBL_MAX); unsigned int nprims = symbol.size(); for (unsigned int i=0; i<nprims; ++i) { SE_RenderPrimitive* primitive = symbol[i]; if (primitive->type == SE_RenderPrimitive_Polygon || primitive->type == SE_RenderPrimitive_Polyline) { SE_RenderPolyline* rp = (SE_RenderPolyline*)primitive; LineBuffer* lb = rp->geometry->xf_buffer(); // update the extents with this primitive RS_Bounds lbnds; lb->ComputeBounds(lbnds); extents.add_bounds(lbnds); if (m_bSelectionMode) { if (primitive->type == SE_RenderPrimitive_Polygon) DrawScreenPolygon(lb, &xform, m_selFillColor); m_selLineStroke.cap = rp->lineStroke.cap; m_selLineStroke.join = rp->lineStroke.join; m_selLineStroke.miterLimit = rp->lineStroke.miterLimit; DrawScreenPolyline(lb, &xform, m_selLineStroke); } else { if (primitive->type == SE_RenderPrimitive_Polygon) DrawScreenPolygon(lb, &xform, ((SE_RenderPolygon*)primitive)->fill); DrawScreenPolyline(lb, &xform, rp->lineStroke); } } else if (primitive->type == SE_RenderPrimitive_Text) { SE_RenderText* tp = (SE_RenderText*)primitive; // update the extents with this primitive's bounds for (int j=0; j<4; ++j) extents.add_point(primitive->bounds[j]); // get position and angle to use double x, y; xform.transform(tp->position[0], tp->position[1], x, y); RS_TextDef tdef = tp->tdef; tdef.rotation() += angleRad * M_180PI; if (m_bSelectionMode) { tdef.textcolor() = m_textForeColor; tdef.ghostcolor() = m_textBackColor; // tdef.framecolor() = m_textBackColor; // tdef.opaquecolor() = m_textBackColor; } // Here we cannot use the cached RS_TextMetrics in the SE_RenderText object. // We must recalculate the text metrics with the new tdef before we can call DrawScreenText. RS_TextMetrics tm; if (this->GetRSFontEngine()->GetTextMetrics(tp->content, tdef, tm, false)) DrawScreenText(tm, tdef, x, y, NULL, 0, 0.0); } else if (primitive->type == SE_RenderPrimitive_Raster) { SE_RenderRaster* rp = (SE_RenderRaster*)primitive; if (m_bSelectionMode) { // if the raster symbol is selected, then draw the mask selection polygon only LineBuffer *lb = LineBufferPool::NewLineBuffer(m_pPool, 5); std::auto_ptr<LineBuffer> spLB(lb); lb->MoveTo(rp->bounds[3].x, rp->bounds[3].y); for (int i = 0; i < 4; ++i) { lb->LineTo(rp->bounds[i].x, rp->bounds[i].y); } DrawScreenPolygon(lb, &xform, m_selFillColor); DrawScreenPolyline(lb, &xform, m_selLineStroke); LineBufferPool::FreeLineBuffer(m_pPool, spLB.release()); } else { ImageData& imgData = rp->imageData; if (imgData.data != NULL) { // update the extents with this primitive's bounds for (int j=0; j<4; ++j) extents.add_point(primitive->bounds[j]); // get position and angle to use double x, y; xform.transform(rp->position[0], rp->position[1], x, y); double angleDeg = (rp->angleRad + angleRad) * M_180PI; DrawScreenRaster(imgData.data, imgData.size, imgData.format, imgData.width, imgData.height, x, y, rp->extent[0], rp->extent[1], angleDeg, rp->opacity); } } } } if (nprims > 0) { // always compute the last symbol extent xform.transform(extents.minx, extents.miny, m_lastSymbolExtent[0].x, m_lastSymbolExtent[0].y); xform.transform(extents.maxx, extents.miny, m_lastSymbolExtent[1].x, m_lastSymbolExtent[1].y); xform.transform(extents.maxx, extents.maxy, m_lastSymbolExtent[2].x, m_lastSymbolExtent[2].y); xform.transform(extents.minx, extents.maxy, m_lastSymbolExtent[3].x, m_lastSymbolExtent[3].y); if (excludeRegion) AddExclusionRegion(m_lastSymbolExtent, 4); } else { // symbol contains no primitives - update last symbol extent assuming // zero symbol extent, but don't add any exclusion region xform.transform(0.0, 0.0, m_lastSymbolExtent[0].x, m_lastSymbolExtent[0].y); m_lastSymbolExtent[1].x = m_lastSymbolExtent[2].x = m_lastSymbolExtent[3].x = m_lastSymbolExtent[0].x; m_lastSymbolExtent[1].y = m_lastSymbolExtent[2].y = m_lastSymbolExtent[3].y = m_lastSymbolExtent[0].y; } }
/////////////////////////////////////////////////////////////////////////////// // Called when applying a point style on a feature geometry. Point styles can // be applied to all feature geometry types. void SE_Renderer::ProcessPoint(SE_ApplyContext* ctx, SE_RenderPointStyle* style, RS_Bounds* bounds) { // the feature geometry we're applying the style on... LineBuffer* featGeom = ctx->geometry; double angleRad = 0.0; if (style->angleControl == SE_AngleControl_FromGeometry) { switch (featGeom->geom_type()) { case GeometryType_LineString: case GeometryType_MultiLineString: case GeometryType_Polygon: case GeometryType_MultiPolygon: { double x0, y0; featGeom->Centroid(LineBuffer::ctLine, &x0, &y0, &angleRad); break; } } } angleRad += style->angleRad; // also account for any viewport rotation angleRad += GetWorldToScreenRotation(); SE_Matrix xform; bool yUp = YPointsUp(); // see StylizationEngine::Stylize for a detailed explanation of these transforms SE_Matrix xformbase; xformbase.translate(style->offset[0], style->offset[1]); xformbase.rotate(yUp? angleRad : -angleRad); xformbase.premultiply(*ctx->xform); // render the points for (int i=0; i<featGeom->point_count(); ++i) { double x, y; featGeom->get_point(i, x, y); // transform to screen space - feature geometry is in [the original] mapping space WorldToScreenPoint(x, y, x, y); xform = xformbase; xform.translate(x, y); if (style->drawLast) AddLabel(featGeom, style, xform, angleRad); else DrawSymbol(style->symbol, xform, angleRad, style->addToExclusionRegion); } if (bounds) { // get the symbol bounds after applying the transforms bounds->minx = bounds->miny = +DBL_MAX; bounds->maxx = bounds->maxy = -DBL_MAX; for (int i=0; i<4; ++i) { RS_F_Point xfpt; xformbase.transform(style->bounds[i].x, style->bounds[i].y, xfpt.x, xfpt.y); bounds->add_point(xfpt); } } }
void SE_PositioningAlgorithms::EightSurrounding(SE_ApplyContext* applyCtx, SE_RenderStyle* rstyle, double mm2su) { SE_Renderer* se_renderer = applyCtx->renderer; LineBuffer* geometry = applyCtx->geometry; // eight surrounding labeling only applies to point feature geometry switch (geometry->geom_type()) { case GeometryType_Point: case GeometryType_MultiPoint: break; default: return; } // eight surrounding labeling only works with point styles if (rstyle->type != SE_RenderStyle_Point) return; // the style needs to contain at least one primitive SE_RenderPrimitiveList& prims = rstyle->symbol; if (prims.size() == 0) return; SE_RenderPointStyle* rpstyle = (SE_RenderPointStyle*)rstyle; // get actual feature point and transform to screen space // TODO: in the case of a multi-point feature we get the average of all the points; // generating candidate labels around this point doesn't make a whole lot of // sense double cx = 0.0; double cy = 0.0; geometry->Centroid(LineBuffer::ctPoint, &cx, &cy, NULL); // don't add a label if we can't compute the centroid if (_isnan(cx) || _isnan(cy)) return; se_renderer->WorldToScreenPoint(cx, cy, cx, cy); // Get the extent of the last drawn point symbol so that we know how much to offset // the label. This call assumes the symbol draws right before the label. // TODO: remove this assumption const RS_F_Point* cfpts = se_renderer->GetLastSymbolExtent(); RS_F_Point fpts[4]; if(cfpts[0].x == 0 && cfpts[0].y == 0 && cfpts[1].x == 0 && cfpts[1].y == 0 && cfpts[2].x == 0 && cfpts[2].y == 0 && cfpts[3].x == 0 && cfpts[3].y == 0) { for (int i=0; i<4; ++i) { fpts[i].x = cx; fpts[i].y = cy; } } else memcpy(fpts, cfpts, 4*sizeof(RS_F_Point)); double dx = fpts[1].x - fpts[0].x; double dy = fpts[1].y - fpts[0].y; double symbol_rot_rad = atan2(dy, dx); // factor out position and rotation SE_Matrix ixform; ixform.translate(-cx, -cy); // factor out point position ixform.rotate(-symbol_rot_rad); // factor out rotation for (int i=0; i<4; ++i) ixform.transform(fpts[i].x, fpts[i].y); bool yUp = se_renderer->YPointsUp(); if (!yUp) symbol_rot_rad = -symbol_rot_rad; // unrotated bounds RS_Bounds symbol_bounds(fpts[0].x, fpts[0].y, fpts[2].x, fpts[2].y); double symbol_width = symbol_bounds.width(); // symbol width in screen units double symbol_height = symbol_bounds.height(); // symbol height in screen units // offset the label from the symbol's edge double offset = POINT_LABEL_OFFSET_MM * mm2su; // offset in screen units // make sure we have at least one pixel's worth of offset double screenUnitsPerPixel = MILLIMETERS_PER_INCH * se_renderer->GetScreenUnitsPerMillimeterDevice() / se_renderer->GetDpi(); if (offset < screenUnitsPerPixel) offset = screenUnitsPerPixel; // compute how far label needs to be offset from center point of symbol double w2 = 0.5 * symbol_width; double h2 = 0.5 * symbol_height; double ch = 0.0; // vertical center point double cw = 0.0; // horizontal center point w2 += offset; h2 += offset; bool useBounds = symbol_bounds.IsValid(); if (useBounds) { symbol_bounds.maxx += offset; symbol_bounds.maxy += offset; symbol_bounds.minx -= offset; symbol_bounds.miny -= offset; ch = 0.5*(symbol_bounds.maxy + symbol_bounds.miny); cw = 0.5*(symbol_bounds.maxx + symbol_bounds.minx); } // get the viewport rotation double w2sAngleRad = se_renderer->GetWorldToScreenRotation(); // take into account rotation of the symbol - find increased extents // of the symbol bounds due to the rotation double op_pts[16]; if (symbol_rot_rad != 0.0) { double cs = cos(symbol_rot_rad); double sn = sin(symbol_rot_rad); // check to see if the bounds have been set double wcs, nwcs, wsn, nwsn, hsn, nhsn, hcs, nhcs, cwsn, cwcs, chsn, chcs; if (useBounds) { wcs = symbol_bounds.maxx * cs; nwcs = symbol_bounds.minx * cs; wsn = symbol_bounds.maxx * sn; nwsn = symbol_bounds.minx * sn; hsn = symbol_bounds.maxy * sn; nhsn = symbol_bounds.miny * sn; hcs = symbol_bounds.maxy * cs; nhcs = symbol_bounds.miny * cs; } else { wcs = w2 * cs; nwcs = -wcs; wsn = w2 * sn; nwsn = -wsn; hsn = h2 * sn; nhsn = -hsn; hcs = h2 * cs; nhcs = -hcs; } cwsn = cw * sn; chsn = ch * sn; cwcs = cw * cs; chcs = ch * cs; // Find the octant that the symbol is rotated into, and shift the points accordingly. // This way the overpost points are still within 22.5 degrees of an axis-aligned box // (position 0 will always be the closest to Center-Right). // NOTE: The symbol rotation includes the viewport rotation. We want to use the // relative angle between these to compute the quadrant (it's the angle of // the symbol relative to the viewport which matters). double relativeAngle = symbol_rot_rad - w2sAngleRad; double nangle = fmod(relativeAngle * M_180PI, 360.0); if (nangle < 0.0) nangle += 360.0; int i = (((int)((nangle/45.0) + 0.5)) << 1) & 0x0000000f; // i is 2 * the octant op_pts[i++] = wcs - chsn; op_pts[i++] = wsn + chcs; i &= 0x0000000f; // & 15 does (mod 16) op_pts[i++] = wcs - hsn; op_pts[i++] = wsn + hcs; i &= 0x0000000f; op_pts[i++] = cwcs - hsn; op_pts[i++] = cwsn + hcs; i &= 0x0000000f; op_pts[i++] = nwcs - hsn; op_pts[i++] = nwsn + hcs; i &= 0x0000000f; op_pts[i++] = nwcs - chsn; op_pts[i++] = nwsn + chcs; i &= 0x0000000f; op_pts[i++] = nwcs - nhsn; op_pts[i++] = nwsn + nhcs; i &= 0x0000000f; op_pts[i++] = cwcs - nhsn; op_pts[i++] = cwsn + nhcs; i &= 0x0000000f; op_pts[i++] = wcs - nhsn; op_pts[i ] = wsn + nhcs; } else { if (!useBounds) { symbol_bounds.maxx = w2; symbol_bounds.minx = -w2; symbol_bounds.maxy = h2; symbol_bounds.miny = -h2; } op_pts[0 ] = symbol_bounds.maxx; op_pts[1 ] = ch; op_pts[2 ] = symbol_bounds.maxx; op_pts[3 ] = symbol_bounds.maxy; op_pts[4 ] = cw; op_pts[5 ] = symbol_bounds.maxy; op_pts[6 ] = symbol_bounds.minx; op_pts[7 ] = symbol_bounds.maxy; op_pts[8 ] = symbol_bounds.minx; op_pts[9 ] = ch; op_pts[10] = symbol_bounds.minx; op_pts[11] = symbol_bounds.miny; op_pts[12] = cw; op_pts[13] = symbol_bounds.miny; op_pts[14] = symbol_bounds.maxx; op_pts[15] = symbol_bounds.miny; } // check if the incoming point style contains just a single text element bool foundSingleText = false; if (prims.size() == 1) { if (prims[0]->type == SE_RenderPrimitive_Text) foundSingleText = true; } // OK, who says I can't write bad code? Behold: SE_LabelInfo candidates[8]; double yScale = yUp? 1.0 : -1.0; // which way does y go in the renderer? double angleRad = rpstyle->angleRad; // also account for the viewport rotation angleRad += w2sAngleRad; if (foundSingleText) { // In this case we set the appropriate alignments for the single text element // in each candidate label. This allows us to draw the symbol directly at the // candidate points surrounding the feature point. SE_RenderStyle* st0 = se_renderer->CloneRenderStyle(rpstyle); ((SE_RenderText*)st0->symbol[0])->tdef.halign() = RS_HAlignment_Left; ((SE_RenderText*)st0->symbol[0])->tdef.valign() = RS_VAlignment_Half; UpdateStyleBounds(st0, se_renderer); candidates[0].Set(cx + op_pts[ 0], cy + op_pts[ 1]*yScale, RS_Units_Device, angleRad, st0); SE_RenderStyle* st1 = se_renderer->CloneRenderStyle(st0); ((SE_RenderText*)st1->symbol[0])->tdef.valign() = RS_VAlignment_Descent; UpdateStyleBounds(st1, se_renderer); candidates[1].Set(cx + op_pts[ 2], cy + op_pts[ 3]*yScale, RS_Units_Device, angleRad, st1); SE_RenderStyle* st2 = se_renderer->CloneRenderStyle(st1); ((SE_RenderText*)st2->symbol[0])->tdef.halign() = RS_HAlignment_Center; UpdateStyleBounds(st2, se_renderer); candidates[2].Set(cx + op_pts[ 4], cy + op_pts[ 5]*yScale, RS_Units_Device, angleRad, st2); SE_RenderStyle* st3 = se_renderer->CloneRenderStyle(st2); ((SE_RenderText*)st3->symbol[0])->tdef.halign() = RS_HAlignment_Right; UpdateStyleBounds(st3, se_renderer); candidates[3].Set(cx + op_pts[ 6], cy + op_pts[ 7]*yScale, RS_Units_Device, angleRad, st3); SE_RenderStyle* st4 = se_renderer->CloneRenderStyle(st3); ((SE_RenderText*)st4->symbol[0])->tdef.valign() = RS_VAlignment_Half; UpdateStyleBounds(st4, se_renderer); candidates[4].Set(cx + op_pts[ 8], cy + op_pts[ 9]*yScale, RS_Units_Device, angleRad, st4); SE_RenderStyle* st5 = se_renderer->CloneRenderStyle(st4); ((SE_RenderText*)st5->symbol[0])->tdef.valign() = RS_VAlignment_Ascent; UpdateStyleBounds(st5, se_renderer); candidates[5].Set(cx + op_pts[10], cy + op_pts[11]*yScale, RS_Units_Device, angleRad, st5); SE_RenderStyle* st6 = se_renderer->CloneRenderStyle(st5); ((SE_RenderText*)st6->symbol[0])->tdef.halign() = RS_HAlignment_Center; UpdateStyleBounds(st6, se_renderer); candidates[6].Set(cx + op_pts[12], cy + op_pts[13]*yScale, RS_Units_Device, angleRad, st6); SE_RenderStyle* st7 = se_renderer->CloneRenderStyle(st6); ((SE_RenderText*)st7->symbol[0])->tdef.halign() = RS_HAlignment_Left; UpdateStyleBounds(st7, se_renderer); candidates[7].Set(cx + op_pts[14], cy + op_pts[15]*yScale, RS_Units_Device, angleRad, st7); } else { // In the general case we have to account for the label symbol's extents when we // position each candidate. For example, for candidate 1 (top right) we adjust the // position so that the bottom left corner of the label symbol's extent ends up at // the top right candidate point. double labelMinX = rpstyle->bounds[0].x; double labelMinY = rpstyle->bounds[0].y; double labelMaxX = rpstyle->bounds[2].x; double labelMaxY = rpstyle->bounds[2].y; double labelCtrX = 0.5*(labelMinX + labelMaxX); double labelCtrY = 0.5*(labelMinY + labelMaxY); SE_RenderStyle* st0 = se_renderer->CloneRenderStyle(rpstyle); candidates[0].Set(cx + op_pts[ 0] - labelMinX, cy + (op_pts[ 1] - labelCtrY)*yScale, RS_Units_Device, angleRad, st0); SE_RenderStyle* st1 = se_renderer->CloneRenderStyle(st0); candidates[1].Set(cx + op_pts[ 2] - labelMinX, cy + (op_pts[ 3] - labelMinY)*yScale, RS_Units_Device, angleRad, st1); SE_RenderStyle* st2 = se_renderer->CloneRenderStyle(st1); candidates[2].Set(cx + op_pts[ 4] - labelCtrX, cy + (op_pts[ 5] - labelMinY)*yScale, RS_Units_Device, angleRad, st2); SE_RenderStyle* st3 = se_renderer->CloneRenderStyle(st2); candidates[3].Set(cx + op_pts[ 6] - labelMaxX, cy + (op_pts[ 7] - labelMinY)*yScale, RS_Units_Device, angleRad, st3); SE_RenderStyle* st4 = se_renderer->CloneRenderStyle(st3); candidates[4].Set(cx + op_pts[ 8] - labelMaxX, cy + (op_pts[ 9] - labelCtrY)*yScale, RS_Units_Device, angleRad, st4); SE_RenderStyle* st5 = se_renderer->CloneRenderStyle(st4); candidates[5].Set(cx + op_pts[10] - labelMaxX, cy + (op_pts[11] - labelMaxY)*yScale, RS_Units_Device, angleRad, st5); SE_RenderStyle* st6 = se_renderer->CloneRenderStyle(st5); candidates[6].Set(cx + op_pts[12] - labelCtrX, cy + (op_pts[13] - labelMaxY)*yScale, RS_Units_Device, angleRad, st6); SE_RenderStyle* st7 = se_renderer->CloneRenderStyle(st6); candidates[7].Set(cx + op_pts[14] - labelMinX, cy + (op_pts[15] - labelMaxY)*yScale, RS_Units_Device, angleRad, st7); } se_renderer->ProcessSELabelGroup(candidates, 8, RS_OverpostType_FirstFit, true, NULL); }