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

Я хотел бы иметь возможность рисовать сложные фигуры вручную на карте mapbox с помощью android sdk. Я унаследовал класс отображения карты и переопределил событие 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); } 

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

Solutions Collecting From Web of "Пользовательский чертеж на карте Mapbox Canvas"

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

1), поскольку вы предлагаете: «наследовать класс onDraw() и переопределить onDraw() ». Но FrameLayout расширяет 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 объект MapboxMap – для этого 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.

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

  1. Используйте 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"))); 
  2. Добавьте слой Fill с помощью нового API стиля, представленного в 4.2.0 (все еще в бета-версии). Для этого сначала потребуется создать объект GeoJSON с точками, а затем добавить его на карту. Самый близкий пример, который я должен сделать, – это этот пример , найденный в демонстрационном приложении.

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

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