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;
}