Skip to content

Polyline advanced display styles

monsieurtanuki edited this page Dec 19, 2019 · 2 revisions

A Polyline gets built from List<GeoPoint>.

If the list contains n GeoPoints, there are (n-1) "GeoSegment"s to display. Or n GeoSegments if the polyline is closed: the additional GeoSegment going from the last GeoPoint to the first GeoPoint.

Each GeoPoint is projected onto the screen as a pixel (Point). Each GeoSegment, as a pair of GeoPoints, can be projected onto the screen in different possible ways. For performance and crash reasons, each GeoSegment is "clipped" into a rectangle a bit bigger than the screen. The most obvious case is: if both GeoPoints are projected inside the screen rectangle , the GeoSegment will be projected as a "PixelSegment". But there are many other cases; a GeoSegment can be projected as 0 or several PixelSegments.

The standard way to display a Polyline is to use a Paint: the same Paint will be used to diplay all segments, in a single call of Canvas.drawLines.

Since PR #1440, we can:

  • use several Paints for the same Polyline - for instance you can draw a black border if you first use a thick black Paint then a thinner white Paint
  • use a different color/Paint for each GeoSegment, and even a linear gradient of colors

How does that work?

We use a new interface called PaintList, in order to be flexible regarding the Paint for the Polyline:

  • which same Paint should we use for all segments when monochromatic (Paint getPaint() is not null)?
  • which Paint should we use for each GeoSegment/PixelSegment, also handling the case of a linear gradient of colors (Paint getPaint(final int pSegmentIndex, final float pX0, final float pY0, final float pX1, final float pY1))
public interface PaintList {
    Paint getPaint();
    Paint getPaint(final int pIndex, final float pX0, final float pY0, final float pX1, final float pY1);
}

There are basically 2 implementations of PaintList:

  • class MonochromaticPaintList, which obviously manages the case of always the same Paint/color
  • class PolychromaticPaintList, which manages the other cases (a different Paint/color for each GeoSegment, possibly with a linear gradient)

How to handle the multicolor case? We need to know which color to display for which GeoSegment.

We don't display GeoSegments, but PixelSegments that are their projections. We need to know which GeoSegment we're referring to when we display a PixelSegment. We do that by adding some kind of GeoSegment counter everytime we add a PixelSegment, to be used in LineDrawer.

We also need to know which color for which GeoSegment. For that we use a new interface:

public interface ColorMapping {
    int getColorForIndex(final int pSegmentIndex);
}

From there, you can instantiate ColorMapping the way you want. For instance, if you want an italian flag dot style:

class ItalyColorList implements ColorMapping {
    @Override
    public int getColorForIndex(int pSegmentIndex) {
        switch(pSegmentIndex % 3) {
            case 0 : return Color.RED;
            case 1 : return Color.WHITE;
            case 2 : return Color.GREEN;
            default: return Color.BLACK; // not supposed to happen
        }
    }
}

Of course you can have a more refined ColorMapping that depends on more relevant data! But the general principle is that: associated the nth GeoSegment with a color.

How do we actually use the ColorMapping? Within the constructor of PolychromaticPaintList:

/**
 * @param pPaint Basis Paint
 * @param pColorMapping from where we get the color to use for each geo segment
 * @param pUseGradient should we use a gradient from this segment's color to the next segment's 
 */
public PolychromaticPaintList(final Paint pPaint, final ColorMapping pColorMapping, final boolean pUseGradient) { ... }

And we apply PaintLists using something like Polyline.getOutlinePaintLists().add(PaintList);. That's right, we can use several PaintLists for the same Polyline. That means that for instance, we can draw a black border on a white Polyline, using first a big black Paint and then a thin white Paint.

For instance, if you want a border, do something like this:

final Paint paintBorder = new Paint();
paintBorder.setStrokeWidth(20);
paintBorder.setStyle(Paint.Style.FILL_AND_STROKE);
paintBorder.setColor(Color.BLACK);
paintBorder.setStrokeCap(Paint.Cap.ROUND);
paintBorder.setAntiAlias(true);

final Paint paintInside = new Paint();
paintInside.setStrokeWidth(10);
paintInside.setStyle(Paint.Style.FILL);
paintInside.setColor(Color.WHITE);
paintInside.setStrokeCap(Paint.Cap.ROUND);
paintInside.setAntiAlias(true);

mPolyline.getOutlinePaintLists().add(new MonochromaticPaintList(paintBorder));
mPolyline.getOutlinePaintLists().add(new MonochromaticPaintList(paintInside));