Пользовательский чертеж на карте Mapbox Canvas

Я хотел бы иметь возможность вручную рисовать сложные фигуры на карте mapbox, используя sroid sroid. Я унаследовал класс отображения карты и переопределил событие ondraw, но, к сожалению, все, что я рисую, нарисовано самой картой.

В качестве примера мне нужно иметь возможность рисовать полигоны с гранями в форме алмаза среди других сложных форм. Это я не могу решить проблему в GoogleMaps с помощью пользовательского поставщика плитки и переопределения ondraw.

Вот только тот код, который у меня есть для mapbox:

    @Override
    public void onDraw(Canvas canvas) {        
        super.onDraw(canvas);

        Paint stroke = new Paint();
        stroke.setColor(Color.BLACK);
        stroke.setStyle(Paint.Style.STROKE);
        stroke.setStrokeWidth(5);
        stroke.setAntiAlias(true); 

        canvas.drawLine(0f,0f,1440f,2464f,stroke);
    }

введите описание изображения здесь

Ответы

Ответ 1

Вы можете сделать то, что хотите, двумя способами:

1), поскольку вы предлагаете: "наследовать класс MapView и переопределить событие onDraw()". Но MapView extends FrameLayout, который является ViewGroup, поэтому вы должны переопределить dispatchDraw() вместо onDraw().

Этот подход требует пользовательского представления, которое расширяет MapView и реализует:

  • рисунок MapView;

  • настройка стилей линий ( "бриллианты вместо простой строки" );

  • путь привязки к Lat/Lon координатам MapView.

Для рисования над MapView Вы должны переопределить dispatchDraw(), например, следующим образом:

@Override
public void dispatchDraw(Canvas canvas) {
    super.dispatchDraw(canvas);
    canvas.save();
    drawDiamondsPath(canvas);
    canvas.restore();
}

Для настройки стилей линий вы можете использовать метод setPathEffect() класса Paint. Для этого вы должны создать путь для "алмазной штампа" (в пикселях), который будет повторяться каждый "прогресс" (в пикселях тоже):

        mPathDiamondStamp = new Path();
        mPathDiamondStamp.moveTo(-DIAMOND_WIDTH / 2, 0);
        mPathDiamondStamp.lineTo(0, DIAMOND_HEIGHT / 2);
        mPathDiamondStamp.lineTo(DIAMOND_WIDTH / 2, 0);
        mPathDiamondStamp.lineTo(0, -DIAMOND_HEIGHT / 2);
        mPathDiamondStamp.close();

        mPathDiamondStamp.moveTo(-DIAMOND_WIDTH / 2 + DIAMOND_BORDER_WIDTH, 0);
        mPathDiamondStamp.lineTo(0, -DIAMOND_HEIGHT / 2 + DIAMOND_BORDER_WIDTH / 2);
        mPathDiamondStamp.lineTo(DIAMOND_WIDTH / 2 - DIAMOND_BORDER_WIDTH, 0);
        mPathDiamondStamp.lineTo(0, DIAMOND_HEIGHT / 2 - DIAMOND_BORDER_WIDTH / 2);
        mPathDiamondStamp.close();

        mPathDiamondStamp.setFillType(Path.FillType.EVEN_ODD);

        mDiamondPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
        mDiamondPaint.setColor(Color.BLUE);
        mDiamondPaint.setStrokeWidth(2);
        mDiamondPaint.setStyle(Paint.Style.FILL_AND_STROKE);
        mDiamondPaint.setStyle(Paint.Style.STROKE);
        mDiamondPaint.setPathEffect(new PathDashPathEffect(mPathDiamondStamp, DIAMOND_ADVANCE, DIAMOND_PHASE, PathDashPathEffect.Style.ROTATE));

(в этом случае 2 Path - первый (по часовой стрелке) для внешней границы и второй (против часовой стрелки) для внутренней границы для "алмазного" прозрачного "отверстия" ).

Для привязки пути на экране к Lat/Lon координатам MapView У вас должен быть MapboxMap объект MapView - для этого должны быть переопределены getMapAsync() и onMapReady():

@Override
public void getMapAsync(OnMapReadyCallback callback) {
    mMapReadyCallback = callback;
    super.getMapAsync(this);
}

@Override
public void onMapReady(MapboxMap mapboxMap) {
    mMapboxMap = mapboxMap;
    if (mMapReadyCallback != null) {
        mMapReadyCallback.onMapReady(mapboxMap);
    }
}

Чем вы можете использовать его в преобразовании "lat/lon-to-screen":

        mBorderPath = new Path();
        LatLng firstBorderPoint = mBorderPoints.get(0);
        PointF firstScreenPoint = mMapboxMap.getProjection().toScreenLocation(firstBorderPoint);
        mBorderPath.moveTo(firstScreenPoint.x, firstScreenPoint.y);

        for (int ixPoint = 1; ixPoint < mBorderPoints.size(); ixPoint++) {
            PointF currentScreenPoint = mMapboxMap.getProjection().toScreenLocation(mBorderPoints.get(ixPoint));
            mBorderPath.lineTo(currentScreenPoint.x, currentScreenPoint.y);
        }

Полный исходный код:

Пользовательский DrawMapView.java

public class DrawMapView extends MapView implements OnMapReadyCallback{

    private float DIAMOND_WIDTH = 42;
    private float DIAMOND_HEIGHT = 18;
    private float DIAMOND_ADVANCE = 1.5f * DIAMOND_WIDTH;       // spacing between each stamp of shape
    private float DIAMOND_PHASE = DIAMOND_WIDTH / 2;            // amount to offset before the first shape is stamped
    private float DIAMOND_BORDER_WIDTH = 6;                     // width of diamond border

    private Path mBorderPath;
    private Path mPathDiamondStamp;
    private Paint mDiamondPaint;
    private OnMapReadyCallback mMapReadyCallback;
    private MapboxMap mMapboxMap = null;
    private List<LatLng> mBorderPoints;

    public DrawMapView(@NonNull Context context) {
        super(context);
        init();
    }

    public DrawMapView(@NonNull Context context, @Nullable AttributeSet attrs) {
        super(context, attrs);
        init();
    }

    public DrawMapView(@NonNull Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        init();
    }

    public DrawMapView(@NonNull Context context, @Nullable MapboxMapOptions options) {
        super(context, options);
        init();
    }

    public void setBorderPoints(List<LatLng> borderPoints) {
        mBorderPoints = borderPoints;
    }

    @Override
    public void getMapAsync(OnMapReadyCallback callback) {
        mMapReadyCallback = callback;
        super.getMapAsync(this);
    }

    @Override
    public void onMapReady(MapboxMap mapboxMap) {
        mMapboxMap = mapboxMap;
        if (mMapReadyCallback != null) {
            mMapReadyCallback.onMapReady(mapboxMap);
        }
    }

    @Override
    public void dispatchDraw(Canvas canvas) {
        super.dispatchDraw(canvas);
        canvas.save();
        drawDiamondsPath(canvas);
        canvas.restore();
    }

    private void drawDiamondsPath(Canvas canvas) {
        if (mBorderPoints == null || mBorderPoints.size() == 0) {
            return;
        }

        mBorderPath = new Path();

        LatLng firstBorderPoint = mBorderPoints.get(0);
        PointF firstScreenPoint = mMapboxMap.getProjection().toScreenLocation(firstBorderPoint);
        mBorderPath.moveTo(firstScreenPoint.x, firstScreenPoint.y);

        for (int ixPoint = 1; ixPoint < mBorderPoints.size(); ixPoint++) {
            PointF currentScreenPoint = mMapboxMap.getProjection().toScreenLocation(mBorderPoints.get(ixPoint));
            mBorderPath.lineTo(currentScreenPoint.x, currentScreenPoint.y);
        }

        mPathDiamondStamp = new Path();
        mPathDiamondStamp.moveTo(-DIAMOND_WIDTH / 2, 0);
        mPathDiamondStamp.lineTo(0, DIAMOND_HEIGHT / 2);
        mPathDiamondStamp.lineTo(DIAMOND_WIDTH / 2, 0);
        mPathDiamondStamp.lineTo(0, -DIAMOND_HEIGHT / 2);
        mPathDiamondStamp.close();

        mPathDiamondStamp.moveTo(-DIAMOND_WIDTH / 2 + DIAMOND_BORDER_WIDTH, 0);
        mPathDiamondStamp.lineTo(0, -DIAMOND_HEIGHT / 2 + DIAMOND_BORDER_WIDTH / 2);
        mPathDiamondStamp.lineTo(DIAMOND_WIDTH / 2 - DIAMOND_BORDER_WIDTH, 0);
        mPathDiamondStamp.lineTo(0, DIAMOND_HEIGHT / 2 - DIAMOND_BORDER_WIDTH / 2);
        mPathDiamondStamp.close();

        mPathDiamondStamp.setFillType(Path.FillType.EVEN_ODD);

        mDiamondPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
        mDiamondPaint.setColor(Color.BLUE);
        mDiamondPaint.setStrokeWidth(2);
        mDiamondPaint.setStyle(Paint.Style.FILL_AND_STROKE);
        mDiamondPaint.setStyle(Paint.Style.STROKE);
        mDiamondPaint.setPathEffect(new PathDashPathEffect(mPathDiamondStamp, DIAMOND_ADVANCE, DIAMOND_PHASE, PathDashPathEffect.Style.ROTATE));

        canvas.drawPath(mBorderPath, mDiamondPaint);
    }

    private void init() {
        mBorderPath = new Path();
        mPathDiamondStamp = new Path();
    }
}

ActivityMain.java

public class MainActivity extends AppCompatActivity {

    private DrawMapView mapView;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        MapboxAccountManager.start(this, getString(R.string.access_token));
        setContentView(R.layout.activity_main);

        mapView = (DrawMapView) findViewById(R.id.mapView);
        mapView.onCreate(savedInstanceState);
        mapView.getMapAsync(new OnMapReadyCallback() {
            @Override
            public void onMapReady(MapboxMap mapboxMap) {

                mapView.setBorderPoints(Arrays.asList(new LatLng(-36.930129, 174.958843),
                        new LatLng(-36.877860, 174.978108),
                        new LatLng(-36.846373, 174.901841),
                        new LatLng(-36.829215, 174.814659),
                        new LatLng(-36.791326, 174.779337),
                        new LatLng(-36.767680, 174.823242)));
            }
        });
    }

    @Override
    public void onResume() {
        super.onResume();
        mapView.onResume();
    }

    @Override
    public void onPause() {
        super.onPause();
        mapView.onPause();
    }

    @Override
    public void onSaveInstanceState(Bundle outState) {
        super.onSaveInstanceState(outState);
        mapView.onSaveInstanceState(outState);
    }

    @Override
    public void onLowMemory() {
        super.onLowMemory();
        mapView.onLowMemory();
    }

    @Override
    protected void onDestroy() {
        super.onDestroy();
        mapView.onDestroy();
    }

}

activity_main.xml

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:mapbox="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context="ua.com.omelchenko.mapboxlines.MainActivity">

    <ua.com.omelchenko.mapboxlines.DrawMapView
        android:id="@+id/mapView"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        mapbox:center_latitude="-36.841362"
        mapbox:center_longitude="174.851110"
        mapbox:style_url="@string/style_mapbox_streets"
        mapbox:zoom="10"/>
</RelativeLayout>

Наконец, в результате вы должны получить что-то вроде этого:

введите описание изображения здесь

И вы должны учитывать некоторые "особые случаи", например, если все точки пути находятся за пределами текущего вида карты, на нем нет линий, даже линия должна пересекать вид карты и должна быть видимой.

2) (лучший способ) создать и опубликовать карту с вашими дополнительными строками и пользовательский стиль для них (особенно взгляните на разделы "Линейные рисунки с изображениями" ). Для этого вы можете использовать Mapbox Studio. И в этом подходе все "особые случаи" и проблемы с производительностью решаются на стороне Mabpox.

Ответ 2

Если я правильно понимаю, вы пытаетесь добавить фигуру алмаза к карте (пользователь не рисует форму)? Если это так, у вас есть несколько вариантов:

  • Используйте Polygon, просто добавьте список точек и нарисуйте фигуру (в данном случае алмаз). Это было бы самым простым, но я предполагаю, что вы уже пробовали, и это не сработает для вас.

    List<LatLng> polygon = new ArrayList<>();
    polygon.add(<LatLng Point 1>);
    polygon.add(<LatLng Point 2>);
    
    ...
    
    mapboxMap.addPolygon(new PolygonOptions()
      .addAll(polygon)
      .fillColor(Color.parseColor("#3bb2d0")));
    
  • Добавьте слой Fill, используя новый API стиля, представленный в 4.2.0 (все еще в бета-версии). Для этого сначала потребуется создать объект GeoJSON с точками, а затем добавить его на карту. Самый близкий пример, который я должен сделать, - этот пример, найденный в демонстрационном приложении.

  • Использовать onDraw, что существенно просто перевести холст к объекту GeoJSON и добавить как слой, как описано в шаге 2. Я бы рекомендовал это только в том случае, если у вас есть пользовательские фигуры во время выполнения, в этом случае координаты будут неопределенными.

Я отредактирую этот ответ, если вы ищете что-то другое.