void SE_PositioningAlgorithms::MultipleHighwaysShields(SE_ApplyContext* applyCtx, SE_RenderStyle* rstyle, double mm2su, RS_FeatureReader* featureReader, SE_SymbolManager* symbolManager) { if (featureReader == NULL) return; SE_Renderer* se_renderer = applyCtx->renderer; LineBuffer* geometry = applyCtx->geometry; // this placement algorithm only applies to line styles if (rstyle->type != SE_RenderStyle_Line) return; SE_RenderLineStyle* rlStyle = (SE_RenderLineStyle*)rstyle; // ... and the units control must be absolute if (rlStyle->unitsControl != SE_UnitsControl_Absolute) return; // highway info format: countryCode|type1|num1|type2|num2|type3|num3|... // example: US|2|101|3|1 StringOfTokens highwayInfo(featureReader->GetString(L"Url"), L"|"); int shieldCount = (highwayInfo.getTokenCount() - 1) / 2; if (shieldCount < 1) return; double startOffset = rlStyle->startOffset; double increment = rlStyle->repeat; // the endOffset is used in this context as the increment between multiple shields in one group // double incrementS = 10.0 * mm2su; double incrementS = rlStyle->endOffset; // calc the overall length of this geometry double totalLen = 0.0; for (int i=0; i<geometry->cntr_count(); ++i) { int pt = geometry->contour_start_point(i); int last = geometry->contour_end_point(i); while (pt < last) { // transform the point to screen space double cx1, cy1, cx2, cy2; se_renderer->WorldToScreenPoint(geometry->x_coord(pt), geometry->y_coord(pt), cx1, cy1); pt++; se_renderer->WorldToScreenPoint(geometry->x_coord(pt), geometry->y_coord(pt), cx2, cy2); // calc length double dx = cx2 - cx1; double dy = cy2 - cy1; totalLen += sqrt(dx*dx + dy*dy); } } if (startOffset >= 0.0) { // calc optimal start offset (with rlStyle->startOffset taken as a minimum) // to co-locate shield groups placed on two-line highways where the two // parallel lines are processed from opposit ends. // this avoids a problem with perceived irregular placement when overposting // removes just some of the duplicate shields double shieldGroupLen = (shieldCount - 1) * incrementS; // length in excess of the required length to place one group with startOffset on each side double availLen = totalLen - (shieldGroupLen + 2.0 * startOffset); if (availLen < 0.0) { // there is no room to 'properly' place even one group, nothing to do but cry about it return; } int numAdditionalGroups = (int) (availLen / (shieldGroupLen + increment)); double additionalOffset = (availLen - numAdditionalGroups * (shieldGroupLen + increment)) / 2; startOffset += additionalOffset; } else { // negative startOffset value disables the optimization // use absolute value as the offset startOffset = -startOffset; } SE_RenderPrimitiveList* symbolVectors = new SE_RenderPrimitiveList[shieldCount]; std::wstring countryCode = highwayInfo.getFirstToken(); int shieldIndex; for (shieldIndex=0; shieldIndex<shieldCount; ++shieldIndex) { std::wstring shieldType = highwayInfo.getNextToken(); std::wstring highwayNum = highwayInfo.getNextToken(); // first the shield graphic SE_RenderRaster* rr = new SE_RenderRaster(); std::wstring imgName = HIGWAY_SHIELD_SYMBOLS_PREFIX + countryCode + L"_" + shieldType + L".png"; symbolManager->GetImageData(HIGWAY_SHIELD_SYMBOLS_RESOURCE.c_str(), imgName.c_str(), rr->imageData); if (rr->imageData.size == 0) { // could not find the image or resource // we could fall back and try to pick up the image from a disk file like this: // std::wstring imgPathName = HIGWAY_SHIELD_SYMBOLS_LOCATION + imgName; // rr->pngPtr = symbolManager->GetImageData(L"", imgPathName.c_str(), rr->pngSize); // but let's not do that unless really necessary // cannot just leave this shield empty, that causes exceptions later, so bail out // TODO: find a better way to handle this condition return; } rr->position[0] = 0.0; rr->position[1] = 0.0; rr->extent[0] = 20.0; rr->extent[1] = 20.0; rr->angleRad = 0.0; if (highwayNum.length() == 1) rr->extent[0] = ((shieldType == L"3")? 25.0 : 20.0); else if (highwayNum.length() == 2) rr->extent[0] = 25.0; else rr->extent[0] = 30.0; double w = 0.5 * rr->extent[0]; double h = 0.5 * rr->extent[1]; rr->bounds[0].x = -w; rr->bounds[0].y = -h; rr->bounds[1].x = w; rr->bounds[1].y = -h; rr->bounds[2].x = w; rr->bounds[2].y = h; rr->bounds[3].x = -w; rr->bounds[3].y = h; // the shield graphic is ready symbolVectors[shieldIndex].push_back(rr); // now symbol for the highway number SE_RenderText* rt = new SE_RenderText(); rt->content = highwayNum; rt->position[0] = 0.0; rt->position[1] = 0.0; rt->tdef.font().name() = L"Arial"; rt->tdef.font().height() = 10.0*0.001 / mm2su; // convert mm to meters rt->tdef.rotation() = 0.0; rt->tdef.halign() = RS_HAlignment_Center; rt->tdef.valign() = RS_VAlignment_Half; if (shieldType == L"1") { rt->tdef.textcolor() = RS_Color(255, 255, 255, 255); } else { rt->tdef.textcolor() = RS_Color(0, 0, 0, 255); } rt->tdef.textbg() = RS_TextBackground_None; rt->tdef.font().style() = RS_FontStyle_Bold; // the number graphic is ready symbolVectors[shieldIndex].push_back(rt); } SE_Matrix symxf; shieldIndex = 0; // account for any viewport rotation double angleRad = se_renderer->GetWorldToScreenRotation(); // init position along the whole geometry to the start offset double drawpos = startOffset; for (int j=0; j<geometry->cntr_count(); ++j) { // current polyline int pt = geometry->contour_start_point(j); int last = geometry->contour_end_point(j); while (pt < last) { symxf.setIdentity(); symxf.rotate(angleRad); // current line segment // transform the point to screen space double cx1, cy1, cx2, cy2; se_renderer->WorldToScreenPoint(geometry->x_coord(pt), geometry->y_coord(pt), cx1, cy1); pt++; se_renderer->WorldToScreenPoint(geometry->x_coord(pt), geometry->y_coord(pt), cx2, cy2); // calc length double dx = cx2 - cx1; double dy = cy2 - cy1; double len = sqrt(dx*dx + dy*dy); // check if completely skipping current segment since it is smaller than the increment if (drawpos < len) { double invlen = 1.0 / len; double dx_fact = dx * invlen; double dy_fact = dy * invlen; double tx = cx1 + dx_fact * drawpos; double ty = cy1 + dy_fact * drawpos; symxf.translate(tx, ty); double dx_incr = dx_fact * increment; // x/y incr between groups of shields double dy_incr = dy_fact * increment; double dx_incS = dx_fact * incrementS; // x/y incr between shields in a group double dy_incS = dy_fact * incrementS; // follow the segment and place the shield symbols alternated via shieldIndex while (drawpos < len) { if (rlStyle->drawLast) { rlStyle->symbol = symbolVectors[shieldIndex]; memcpy(rlStyle->bounds, symbolVectors[shieldIndex].front()->bounds, sizeof(rlStyle->bounds)); SE_RenderStyle* clonedStyle = se_renderer->CloneRenderStyle(rlStyle); SE_LabelInfo info(symxf.x2, symxf.y2, RS_Units_Device, angleRad, clonedStyle); RS_OverpostType overpostType = rlStyle->checkExclusionRegion? RS_OverpostType_AllFit : RS_OverpostType_All; se_renderer->ProcessSELabelGroup(&info, 1, overpostType, rlStyle->addToExclusionRegion, geometry); } else { se_renderer->DrawSymbol(symbolVectors[shieldIndex], symxf, angleRad); // TODO: if this is ever needed ... // if (rlStyle->addToExclusionRegion) // se_renderer->AddExclusionRegion(style, symxf, 0.0); } // move on to the next shield, if beyond the last one go back to the first shieldIndex++; if (shieldIndex < shieldCount) { symxf.translate(dx_incS, dy_incS); drawpos += incrementS; } else { // finished with one group // go back to the first symbol and advance using the full increment shieldIndex = 0; symxf.translate(dx_incr, dy_incr); drawpos += increment; } } } drawpos -= len; } } // cleanup time // the rlStyle->symbol has been assigned various values from the 'symbolVectors', // 'un-assign' it, so that we can clean them up rlStyle->symbol.clear(); for (shieldIndex=0; shieldIndex<shieldCount; ++shieldIndex) { for (SE_RenderPrimitiveList::iterator iter = symbolVectors[shieldIndex].begin(); iter != symbolVectors[shieldIndex].end(); ++iter) { // necessary since destructor of SE_RenderPrimitive is not virtual switch ((*iter)->type) { case SE_RenderPrimitive_Polyline: delete (SE_RenderPolyline*)(*iter); break; case SE_RenderPrimitive_Polygon: delete (SE_RenderPolygon*)(*iter); break; case SE_RenderPrimitive_Raster: delete (SE_RenderRaster*)(*iter); break; case SE_RenderPrimitive_Text: delete (SE_RenderText*)(*iter); break; default: throw; // means there is a bug } } } delete [] symbolVectors; }