【问题标题】:Google Maps API v2 draw part of circle on MapFragmentGoogle Maps API v2 在 MapFragment 上绘制部分圆圈
【发布时间】:2013-12-21 09:06:45
【问题描述】:

我需要画这样的东西,它会被绘制并且透明度很小

它还需要是可点击的(onTouch 事件等)

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

PS:半径是可变的。

(供进一步参考) 编辑 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)));

}

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

【问题讨论】:

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


    【解决方案1】:

    为了绘制圆段,如果段主要是静态的,我会注册一个 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 方法的投影首先获取图块的边界。如果边界内没有段,则返回。否则将您的序列绘制到画布中。 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;
    }
    

    如你所见,我有一个非常相似的任务要解决:-)

    【讨论】:

    • 哇,伙计...谢谢!我会立即实现代码并通知您。
    • 一个问题,因为我对地图 api 和 Tiles 非常陌生。在哪里实现draw方法,如何提供LatLng和radius?
    • 创建一个扩展我的 CanvasTileProvider 的子类。在那里你必须实现 onDraw(因为这在 CanvasTileProvider 中是抽象的)。
    • 不是这样的吗? TileOverlayOptions opts = new TileOverlayOptions(); opts.tileProvider(new CanvasTileProvider() { @Override void onDraw(Canvas canvas, TileProjection projection) { // TODO 自动生成的方法存根 } }); opts.zIndex(5); TileOverlay 覆盖 = mMap.addTileOverlay(opts);
    • 要添加的一个重要说明是“public Tile getTile(int x, int y, int zoom)” 具有超类的 try-catch,如果其中有任何崩溃,则不会显示任何日志!让调试变得非常有趣
    【解决方案2】:

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

    【讨论】:

    • 这会起作用,但是当地图移动时,弧不会随之移动
    • 您可以随时跟踪地图移动并根据需要翻译视图。
    • 太棒了...会尝试您的建议...它不应该移动。它是地图上的静态元素。只有它应该是可点击的......感谢您的回答,会尝试让您知道。
    • 你有代码示例吗?如何为此构建代码?问题是我将在地图上有超过 100 个弧形对象。我将使用 Web 服务为每个“Arc”对象提供 LatLng 和半径。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2012-12-30
    • 2013-02-26
    • 2013-01-05
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多