Google Maps API v2在MapFragment上绘制部分圆圈

时间:2013-12-04 18:15:38

标签: android google-maps-android-api-2

我需要绘制这样的东西,这些东西将被涂上并且透明度很低

还需要点击(onTouch事件等)

enter image description here

我知道在API v1中你必须使用Overlay并使用canvas和一些数学来扩展它。 在Google Map API v2中最简单的方法是什么?

PS:Radius是可变的。

(供进一步参考) 编辑1:

我实现了CanvasTileProvider子类并覆盖了它的onDraw()方法:

@Override
void onDraw(Canvas canvas, TileProjection projection) {
    // TODO Auto-generated method stub

    LatLng tempLocation = moveByDistance(mSegmentLocation, mSegmentRadius, mSegmentAngle);

    DoublePoint segmentLocationPoint = new DoublePoint(0, 0);
    DoublePoint tempLocationPoint = new DoublePoint(0, 0);

    projection.latLngToPoint(mSegmentLocation, segmentLocationPoint);
    projection.latLngToPoint(tempLocationPoint, tempLocationPoint);

    float radiusInPoints = FloatMath.sqrt((float) (Math.pow(
            (segmentLocationPoint.x - tempLocationPoint.x), 2) + Math.pow(
            (segmentLocationPoint.y - tempLocationPoint.y), 2)));

    RectF segmentArea = new RectF();
    segmentArea.set((float)segmentLocationPoint.x - radiusInPoints, (float)segmentLocationPoint.y - radiusInPoints, 
            (float)segmentLocationPoint.x + radiusInPoints, (float)segmentLocationPoint.y + radiusInPoints);

    canvas.drawArc(segmentArea, getAdjustedAngle(mSegmentAngle), 
            getAdjustedAngle(mSegmentAngle + 60), true, getOuterCirclePaint());


}

另外,我在MapActivity中添加了这个:

private void loadSegmentTiles() {

     TileProvider tileProvider; 
     TileOverlay tileOverlay = mMap.addTileOverlay(
         new TileOverlayOptions().tileProvider(new SegmentTileProvider(new LatLng(45.00000,15.000000), 250, 30)));

}

现在我想知道为什么我的弧不在地图上?

2 个答案:

答案 0 :(得分:20)

为了绘制圆弧段,如果段主要是静态的,我会注册一个TileProvider。 (平铺通常只加载一次然后缓存。)为了检查点击事件,您可以注册onMapClickListener并遍历您的段以检查单击的LatLng是否在您的某个段内。 (详见下文。)

这是一个TileProvider示例,您可以将其子类化并仅实现onDraw方法。
一个重要的注意事项:子类必须是线程安全的! onDraw方法将由多个线程同时调用。因此,请避免在onDraw内部更改任何全局变量!

/* imports should be obvious */ 
public abstract class CanvasTileProvider implements TileProvider {
private static int TILE_SIZE = 256;

private BitMapThreadLocal tlBitmap;

@SuppressWarnings("unused")
private static final String TAG = CanvasTileProvider.class.getSimpleName();

public CanvasTileProvider() {
    super();
    tlBitmap = new BitMapThreadLocal();
}

@Override
// Warning: Must be threadsafe. To still avoid creation of lot of bitmaps,
// I use a subclass of ThreadLocal !!!
public Tile getTile(int x, int y, int zoom) {
    TileProjection projection = new TileProjection(TILE_SIZE,
            x, y, zoom);

    byte[] data;
    Bitmap image = getNewBitmap();
    Canvas canvas = new Canvas(image);
    onDraw(canvas, projection);
    data = bitmapToByteArray(image);
    Tile tile = new Tile(TILE_SIZE, TILE_SIZE, data);
    return tile;
}

/** Must be implemented by a concrete TileProvider */
abstract void onDraw(Canvas canvas, TileProjection projection);

/**
 * Get an empty bitmap, which may however be reused from a previous call in
 * the same thread.
 * 
 * @return
 */
private Bitmap getNewBitmap() {
    Bitmap bitmap = tlBitmap.get();
    // Clear the previous bitmap
    bitmap.eraseColor(Color.TRANSPARENT);
    return bitmap;
}

private static byte[] bitmapToByteArray(Bitmap bm) {
    ByteArrayOutputStream bos = new ByteArrayOutputStream();
    bm.compress(Bitmap.CompressFormat.PNG, 100, bos);
    byte[] data = bos.toByteArray();
    return data;
}

class BitMapThreadLocal extends ThreadLocal<Bitmap> {
    @Override
    protected Bitmap initialValue() {
        Bitmap image = Bitmap.createBitmap(TILE_SIZE, TILE_SIZE,
                Config.ARGB_8888);
        return image;
    }
}
}

使用传递给onDraw方法的投影首先获取图块的边界。如果边界内没有任何段,则返回。否则将您的seqment绘制到画布中。方法projection.latLngToPoint可帮助您将LatLng转换为画布的像素。

/** Converts between LatLng coordinates and the pixels inside a tile. */
public class TileProjection {

private int x;
private int y;
private int zoom;
private int TILE_SIZE;

private DoublePoint pixelOrigin_;
private double pixelsPerLonDegree_;
private double pixelsPerLonRadian_;

TileProjection(int tileSize, int x, int y, int zoom) {
    this.TILE_SIZE = tileSize;
    this.x = x;
    this.y = y;
    this.zoom = zoom;
    pixelOrigin_ = new DoublePoint(TILE_SIZE / 2, TILE_SIZE / 2);
    pixelsPerLonDegree_ = TILE_SIZE / 360d;
    pixelsPerLonRadian_ = TILE_SIZE / (2 * Math.PI);
}

/** Get the dimensions of the Tile in LatLng coordinates */
public LatLngBounds getTileBounds() {
    DoublePoint tileSW = new DoublePoint(x * TILE_SIZE, (y + 1) * TILE_SIZE);
    DoublePoint worldSW = pixelToWorldCoordinates(tileSW);
    LatLng SW = worldCoordToLatLng(worldSW);
    DoublePoint tileNE = new DoublePoint((x + 1) * TILE_SIZE, y * TILE_SIZE);
    DoublePoint worldNE = pixelToWorldCoordinates(tileNE);
    LatLng NE = worldCoordToLatLng(worldNE);
    return new LatLngBounds(SW, NE);
}

/**
 * Calculate the pixel coordinates inside a tile, relative to the left upper
 * corner (origin) of the tile.
 */
public void latLngToPoint(LatLng latLng, DoublePoint result) {
    latLngToWorldCoordinates(latLng, result);
    worldToPixelCoordinates(result, result);
    result.x -= x * TILE_SIZE;
    result.y -= y * TILE_SIZE;
}


private DoublePoint pixelToWorldCoordinates(DoublePoint pixelCoord) {
    int numTiles = 1 << zoom;
    DoublePoint worldCoordinate = new DoublePoint(pixelCoord.x / numTiles,
            pixelCoord.y / numTiles);
    return worldCoordinate;
}

/**
 * Transform the world coordinates into pixel-coordinates relative to the
 * whole tile-area. (i.e. the coordinate system that spans all tiles.)
 * 
 * 
 * Takes the resulting point as parameter, to avoid creation of new objects.
 */
private void worldToPixelCoordinates(DoublePoint worldCoord, DoublePoint result) {
    int numTiles = 1 << zoom;
    result.x = worldCoord.x * numTiles;
    result.y = worldCoord.y * numTiles;
}

private LatLng worldCoordToLatLng(DoublePoint worldCoordinate) {
    DoublePoint origin = pixelOrigin_;
    double lng = (worldCoordinate.x - origin.x) / pixelsPerLonDegree_;
    double latRadians = (worldCoordinate.y - origin.y)
            / -pixelsPerLonRadian_;
    double lat = Math.toDegrees(2 * Math.atan(Math.exp(latRadians))
            - Math.PI / 2);
    return new LatLng(lat, lng);
}

/**
 * Get the coordinates in a system describing the whole globe in a
 * coordinate range from 0 to TILE_SIZE (type double).
 * 
 * Takes the resulting point as parameter, to avoid creation of new objects.
 */
private void latLngToWorldCoordinates(LatLng latLng, DoublePoint result) {
    DoublePoint origin = pixelOrigin_;

    result.x = origin.x + latLng.longitude * pixelsPerLonDegree_;

    // Truncating to 0.9999 effectively limits latitude to 89.189. This is
    // about a third of a tile past the edge of the world tile.
    double siny = bound(Math.sin(Math.toRadians(latLng.latitude)), -0.9999,
            0.9999);
    result.y = origin.y + 0.5 * Math.log((1 + siny) / (1 - siny))
            * -pixelsPerLonRadian_;
};

/** Return value reduced to min and max if outside one of these bounds. */
private double bound(double value, double min, double max) {
    value = Math.max(value, min);
    value = Math.min(value, max);
    return value;
}

/** A Point in an x/y coordinate system with coordinates of type double */
public static class DoublePoint {
    double x;
    double y;

    public DoublePoint(double x, double y) {
        this.x = x;
        this.y = y;
    }
}

}

最后,您需要检查一下,LatLng-Coordinate上的点击是否在您的细分中。 因此,我将通过LatLng坐标列表来近似该段,在您的情况下,简单的三角形可能就足够了。对于LatLng坐标的每个列表,即对于每个段,您可以调用以下内容:

private static boolean isPointInsidePolygon(List<LatLng> vertices, LatLng point) {
    /**
     * Test is based on a horizontal ray, starting from point to the right.
     * If the ray is crossed by an even number of polygon-sides, the point
     * is inside the polygon, otherwise it is outside.
     */
    int i, j;
    boolean inside = false;
    int size = vertices.size();
    for (i = 0, j = size - 1; i < size; j = i++) {
        LatLng vi = vertices.get(i);
        LatLng vj = vertices.get(j);
        if ((vi.latitude > point.latitude) != (vj.latitude > point.latitude)) {
            /* The polygonside crosses the horizontal level of the ray. */
            if (point.longitude <= vi.longitude
                    && point.longitude <= vj.longitude) {
                /*
                 * Start and end of the side is right to the point. Side
                 * crosses the ray.
                 */
                inside = !inside;
            } else if (point.longitude >= vi.longitude
                    && point.longitude >= vj.longitude) {
                /*
                 * Start and end of the side is left of the point. No
                 * crossing of the ray.
                 */
            } else {
                double crossingLongitude = (vj.longitude - vi.longitude)
                        * (point.latitude - vi.latitude)
                        / (vj.latitude - vi.latitude) + vi.longitude;
                if (point.longitude < crossingLongitude) {
                    inside = !inside;
                }
            }
        }
    }
    return inside;
}

正如您所看到的,我有一个非常类似的任务要解决:-)

答案 1 :(得分:1)

创建一个View,覆盖其onDraw方法以在其画布上使用drawArc,并将其添加到MapFragment。您可以在drawArc中指定半径。在View上设置onClickListener(或onTouch,真正可以用于普通视图的任何侦听器)。