Как отобразить масштабную линейку и элементы управления масштабированием на карте?

Как добавить масштабную линейку на карту, когда значение увеличения и уменьшения масштаба должно измениться (масштабная линейка) и отобразить элементы управления масштабированием в правом нижнем углу, но я хочу заменить его прямо над ним.

Как сделать это возможным?

Спасибо


person pallavi    schedule 25.07.2013    source источник


Ответы (5)


В API нет способа установить масштабную линейку. В этом случае вы должны реализовать его самостоятельно. Это простой пример того, как вы можете это сделать. Я адаптировал код из ответа Как добавить масштаб карты в MapView на Android? для работы с GoogleMap.

class ScaleBar extends ImageView {
    float mXOffset = 10;
    float mYOffset = 10;
    float mLineWidth = 3;
    int mTextSize = 25;

    boolean mIsImperial = false;
    boolean mIsNautical = false;

    boolean mIsLatitudeBar = true;
    boolean mIsLongitudeBar = true;

    private GoogleMap mMap;

    float mXdpi;
    float mYdpi;

    public ScaleBar(Context context, GoogleMap map) {
        super(context);

        mMap = map;

        mXdpi = context.getResources().getDisplayMetrics().xdpi;
        mYdpi = context.getResources().getDisplayMetrics().ydpi;
    }

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

        drawScaleBarPicture(canvas);

        canvas.restore();
    }

    private void drawScaleBarPicture(Canvas canvas) {
        // We want the scale bar to be as long as the closest round-number miles/kilometers
        // to 1-inch at the latitude at the current center of the screen.

        Projection projection = mMap.getProjection();

        if (projection == null) {
            return;
        }

        final Paint barPaint = new Paint();
        barPaint.setColor(Color.BLACK);
        barPaint.setAntiAlias(true);
        barPaint.setStrokeWidth(mLineWidth);

        final Paint textPaint = new Paint();
        textPaint.setColor(Color.BLACK);
        textPaint.setAntiAlias(true);
        textPaint.setTextSize(mTextSize);

        drawXMetric(canvas, textPaint, barPaint);

        drawYMetric(canvas, textPaint, barPaint);
    }

    private void drawXMetric(Canvas canvas, Paint textPaint, Paint barPaint) {
        Projection projection = mMap.getProjection();

        if (projection != null) {
            LatLng p1 = projection.fromScreenLocation(new Point((int) ((getWidth() / 2) - (mXdpi / 2)), getHeight() / 2));
            LatLng p2 = projection.fromScreenLocation(new Point((int) ((getWidth() / 2) + (mXdpi / 2)), getHeight() / 2));

            Location locationP1 = new Location("ScaleBar location p1");
            Location locationP2 = new Location("ScaleBar location p2");

            locationP1.setLatitude(p1.latitude);
            locationP2.setLatitude(p2.latitude);
            locationP1.setLongitude(p1.longitude);
            locationP2.setLongitude(p2.longitude);

            float xMetersPerInch = locationP1.distanceTo(locationP2);

            if (mIsLatitudeBar) {
                String xMsg = scaleBarLengthText(xMetersPerInch, mIsImperial, mIsNautical);
                Rect xTextRect = new Rect();
                textPaint.getTextBounds(xMsg, 0, xMsg.length(), xTextRect);

                int textSpacing = (int) (xTextRect.height() / 5.0);

                canvas.drawRect(mXOffset, mYOffset, mXOffset + mXdpi, mYOffset + mLineWidth, barPaint);
                canvas.drawRect(mXOffset + mXdpi, mYOffset, mXOffset + mXdpi + mLineWidth, mYOffset +
                        xTextRect.height() + mLineWidth + textSpacing, barPaint);

                if (!mIsLongitudeBar) {
                    canvas.drawRect(mXOffset, mYOffset, mXOffset + mLineWidth, mYOffset +
                            xTextRect.height() + mLineWidth + textSpacing, barPaint);
                }
                canvas.drawText(xMsg, (mXOffset + mXdpi / 2 - xTextRect.width() / 2),
                        (mYOffset + xTextRect.height() + mLineWidth + textSpacing), textPaint);
            }
        }
    }

    private void drawYMetric(Canvas canvas, Paint textPaint, Paint barPaint) {
        Projection projection = mMap.getProjection();

        if (projection != null) {
            Location locationP1 = new Location("ScaleBar location p1");
            Location locationP2 = new Location("ScaleBar location p2");

            LatLng p1 = projection.fromScreenLocation(new Point(getWidth() / 2,
                    (int) ((getHeight() / 2) - (mYdpi / 2))));
            LatLng p2 = projection.fromScreenLocation(new Point(getWidth() / 2,
                    (int) ((getHeight() / 2) + (mYdpi / 2))));

            locationP1.setLatitude(p1.latitude);
            locationP2.setLatitude(p2.latitude);
            locationP1.setLongitude(p1.longitude);
            locationP2.setLongitude(p2.longitude);

            float yMetersPerInch = locationP1.distanceTo(locationP2);

            if (mIsLongitudeBar) {
                String yMsg = scaleBarLengthText(yMetersPerInch, mIsImperial, mIsNautical);
                Rect yTextRect = new Rect();
                textPaint.getTextBounds(yMsg, 0, yMsg.length(), yTextRect);

                int textSpacing = (int) (yTextRect.height() / 5.0);

                canvas.drawRect(mXOffset, mYOffset, mXOffset + mLineWidth, mYOffset + mYdpi, barPaint);
                canvas.drawRect(mXOffset, mYOffset + mYdpi, mXOffset + yTextRect.height() +
                        mLineWidth + textSpacing, mYOffset + mYdpi + mLineWidth, barPaint);

                if (!mIsLatitudeBar) {
                    canvas.drawRect(mXOffset, mYOffset, mXOffset + yTextRect.height() +
                            mLineWidth + textSpacing, mYOffset + mLineWidth, barPaint);
                }

                float x = mXOffset + yTextRect.height() + mLineWidth + textSpacing;
                float y = mYOffset + mYdpi / 2 + yTextRect.width() / 2;

                canvas.rotate(-90, x, y);
                canvas.drawText(yMsg, x, y + textSpacing, textPaint);
            }
        }
    }

    private String scaleBarLengthText(float meters, boolean imperial, boolean nautical) {
        if (this.mIsImperial) {
            if (meters >= 1609.344) {
                return (meters / 1609.344) + "mi";
            } else if (meters >= 1609.344/10) {
                return ((meters / 160.9344) / 10.0) + "mi";
            } else {
                return (meters * 3.2808399) + "ft";
            }
        } else if (this.mIsNautical) {
            if (meters >= 1852) {
                return ((meters / 1852)) + "nm";
            } else if (meters >= 1852/10) {
                return (((meters / 185.2)) / 10.0) + "nm";
            } else {
                return ((meters * 3.2808399)) + "ft";
            }
        } else {
            if (meters >= 1000) {
                return ((meters / 1000)) + "km";
            } else if (meters > 100) {
                return ((meters / 100.0) / 10.0) + "km";
            } else {
                return meters + "m";
            }
        }
    }
}

Затем вы можете добавить это в свой макет следующим образом:

    RelativeLayout container = (RelativeLayout) findViewById(R.id.main_view);
    RelativeLayout.LayoutParams params = new RelativeLayout.LayoutParams(800, 800);

    params.addRule(RelativeLayout.CENTER_HORIZONTAL);
    params.addRule(RelativeLayout.CENTER_VERTICAL);

    mScaleBar = new ScaleBar(this, mMap);
    mScaleBar.setLayoutParams(params);

Надеюсь, это будет полезно для кого-то.

person Arkadiusz Cieśliński    schedule 16.03.2015
comment
работает отлично. нужно просто добавить обработчик onTouch и добавить mScaleBar.invalidate() - person dariusiv; 30.06.2015
comment
@dariusiv в конструкторе класса вы можете добавить следующий код, чтобы сделать представление недействительным: mMap.setOnCameraMoveListener(new GoogleMap.OnCameraMoveListener() { @Override public void onCameraMove() { invalidate(); } }); - person ClarkXP; 07.12.2016


Просто добавьте этот код..

    googleMap.setMapType(GoogleMap.MAP_TYPE_NORMAL);
    googleMap.getUiSettings().setZoomControlsEnabled(true);
    googleMap.getUiSettings().setCompassEnabled(true);
    googleMap.getUiSettings().setMyLocationButtonEnabled(true);
    googleMap.getUiSettings().setAllGesturesEnabled(true);
    googleMap.setMyLocationEnabled(true);
person An-droid    schedule 25.07.2013

Как было сказано ранее, в API Карт Google нет упоминания о масштабной линейке. Нужно запрограммировать себя. Я взял ответ от Аркадиуша Цеслинского и улучшил его, чтобы получить масштабное представление, больше похожее на Карты Google.

import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.graphics.Path;
import android.graphics.Point;
import android.graphics.PointF;
import android.graphics.Rect;
import android.location.Location;
import android.support.v7.widget.AppCompatImageView;
import android.util.AttributeSet;

import com.google.android.gms.maps.GoogleMap;
import com.google.android.gms.maps.Projection;
import com.google.android.gms.maps.model.LatLng;

import java.util.Locale;

import thermcam.georg.locationhistoryviewer.R;
import thermcam.georg.locationhistoryviewer.util.ResourcesUtil;

/**
 * Created by georg on 18.04.17.
 */

public class MapScaleBar extends AppCompatImageView{

    public enum ColorMode {
        COLOR_MODE_AUTO,
        COLOR_MODE_DARK,
        COLOR_MODE_WHITE
    }

    private static final int FT_IN_MILE = 5280;

    private static final int[] METERS = {1, 2, 5, 10, 20, 50, 100, 200, 500, 1000, 2000, 5000,
            10000, 20000, 50000, 100000, 200000, 500000, 1000000, 2000000};

    private static final int[] FT = {1, 2, 5, 10, 20, 50, 100, 200, 500, 1000, 2000,
            FT_IN_MILE, 2 * FT_IN_MILE, 5 * FT_IN_MILE, 10 * FT_IN_MILE, 20 * FT_IN_MILE, 50 * FT_IN_MILE,
            100 * FT_IN_MILE, 200 * FT_IN_MILE, 500 * FT_IN_MILE, 1000 * FT_IN_MILE, 2000 * FT_IN_MILE};

    float mXOffset = 10;  // in px
    float mYOffset = 10;  // in px
    private float mLineWidth = 4;  // in dp
    private float mTextSize = 15; // in dp
    private float mTextPadding = 2; // in dp

    public ColorMode mColorMode = ColorMode.COLOR_MODE_AUTO;

    private GoogleMap mMap;

    float mXdpi;
    float mYdpi;

    public MapScaleBar(Context context) {
        this(context, null);
    }

    public MapScaleBar(Context context, AttributeSet attrs) {
        this(context, attrs, 0);
    }

    public MapScaleBar(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);

        mXdpi = context.getResources().getDisplayMetrics().xdpi;
        mYdpi = context.getResources().getDisplayMetrics().ydpi;

        setLineWidth(mLineWidth);
        setTextSize(mTextSize);
        setTextPadding(mTextPadding);
    }

    public void addTarget(GoogleMap map) {
        this.mMap = map;

        map.setOnCameraMoveListener(new GoogleMap.OnCameraMoveListener() {
            @Override
            public void onCameraMove() {
                invalidate();
            }
        });
    }

    public void setLineWidth(float lineWidth) {
        this.mLineWidth = pxToDp(lineWidth);
    }

    public void setTextSize(float textSize) {
        this.mTextSize = pxToDp(textSize);
    }

    public void setTextPadding(float textPadding) {
        this.mTextPadding = pxToDp(textPadding);
    }

    public float pxToDp(float px) {
        return px * (mYdpi / 160);
    }

    @Override
    public void onDraw(Canvas canvas) {
        if (mMap == null)
            return;

        canvas.save();

        drawScaleBarPicture(canvas);

        canvas.restore();
    }

    private void drawScaleBarPicture(Canvas canvas) {
        // We want the scale bar to be as long as the closest round-number miles/kilometers
        // to 1-inch at the latitude at the current center of the screen.

        int darkC = ResourcesUtil.getColor(getResources(), R.color.textDarkPrimary, null);
        int whiteC = ResourcesUtil.getColor(getResources(), R.color.textLightPrimary, null);

        if (mColorMode == ColorMode.COLOR_MODE_WHITE ||
                mColorMode == ColorMode.COLOR_MODE_AUTO && (mMap.getMapType() == GoogleMap.MAP_TYPE_SATELLITE || mMap.getMapType() == GoogleMap.MAP_TYPE_HYBRID))
            drawXMetric(canvas, whiteC, darkC);
        else
            drawXMetric(canvas, darkC, whiteC);
    }

    private void drawXMetric(Canvas canvas, int fillColor, int outlineColor) {
        Projection projection = mMap.getProjection();

        LatLng p1 = projection.fromScreenLocation(new Point(
                (int) ((getWidth() / 2) - (mXdpi / 2)), getHeight() / 2));
        LatLng p2 = projection.fromScreenLocation(new Point((int) (
                (getWidth() / 2) + (mXdpi / 2)), getHeight() / 2));

        Location locationP1 = new Location("ScaleBar location p1");
        Location locationP2 = new Location("ScaleBar location p2");

        locationP1.setLatitude(p1.latitude);
        locationP2.setLatitude(p2.latitude);
        locationP1.setLongitude(p1.longitude);
        locationP2.setLongitude(p2.longitude);


        // PAINTS
        final Paint barPaintFill = new Paint(Paint.ANTI_ALIAS_FLAG);
        barPaintFill.setStyle(Paint.Style.FILL);
        barPaintFill.setColor(fillColor);

        final Paint barPaintStroke = new Paint(barPaintFill);
        barPaintStroke.setColor(outlineColor);
        barPaintStroke.setStyle(Paint.Style.STROKE);
        barPaintStroke.setStrokeWidth(mLineWidth/5);

        final Paint textPaintFill = new Paint(barPaintFill);
        textPaintFill.setTextSize(mTextSize);

        final Paint textPaintStroke = new Paint(barPaintStroke);
        textPaintStroke.setTextSize(mTextSize);

        // LENGTH
        float xMetersPerInch = locationP1.distanceTo(locationP2);

        int nearestM = findNextSmallestInUnit(xMetersPerInch, false);
        int nearestFT = findNextSmallestInUnit(xMetersPerInch, true);
        float lengthM = mXdpi / xMetersPerInch * nearestM;
        float lengthFT = mXdpi / xMetersPerInch * (nearestFT / 3.2808f);
        String msgM = scaleBarLengthText(nearestM, false);
        String msgFT = scaleBarLengthText(nearestFT, true);

        float longest = Math.max(lengthFT, lengthM);

        // Get text rects & cords
        Rect textRectM = new Rect();
        textPaintFill.getTextBounds(msgM, 0, msgM.length(), textRectM);
        Rect textRectFT = new Rect();
        textPaintFill.getTextBounds(msgFT, 0, msgFT.length(), textRectFT);

        PointF textCoordM = new PointF(canvas.getWidth() - mXOffset - textRectM.width() - mTextPadding,
                canvas.getHeight() - mYOffset - textRectFT.height() - mTextPadding * 2 - mLineWidth);
        PointF textCoordFT = new PointF(canvas.getWidth() - mXOffset - textRectFT.width() - mTextPadding,
                canvas.getHeight() - mYOffset - textRectFT.height() - textRectFT.top);
        // PointF textCoordFT = new PointF()

        // All movements are based in the bottom, right corner
        Path barP = new Path();
        barP.setFillType(Path.FillType.EVEN_ODD);
        barP.moveTo(canvas.getWidth() - mXOffset, canvas.getHeight() - mYOffset - textRectFT.height() - mTextPadding);

        barP.rLineTo(- lengthFT + mLineWidth, 0);
        barP.rLineTo(0, textRectFT.height() * 0.6f);
        barP.rLineTo(- mLineWidth, 0);
        barP.rLineTo(0, -textRectFT.height() * 0.6f);
        barP.rLineTo(lengthFT - longest, 0);
        barP.rLineTo(0, - mLineWidth);
        barP.rLineTo(longest - lengthM, 0);
        barP.rLineTo(0, -textRectM.height() * 0.6f);
        barP.rLineTo(mLineWidth, 0);
        barP.rLineTo(0, textRectM.height() * 0.6f);
        barP.rLineTo(lengthM - mLineWidth, 0);
        barP.close();

        canvas.drawPath(barP, barPaintFill);
        canvas.drawPath(barP, barPaintStroke);

        canvas.drawText(msgM, textCoordM.x, textCoordM.y, textPaintStroke);
        canvas.drawText(msgM, textCoordM.x, textCoordM.y, textPaintFill);

        canvas.drawText(msgFT, textCoordFT.x, textCoordFT.y, textPaintStroke);
        canvas.drawText(msgFT, textCoordFT.x, textCoordFT.y, textPaintFill);
    }

    private int findNextSmallestInUnit(float meters, boolean imperial) {
        int[] data = imperial ? FT : METERS;
        float value = meters * (imperial ? 3.2808f : 1);

        int closest = data[0];

        for (int elem: data) {
            if (elem - value > 0)
                break;
            else
                closest = elem;
        }

        return closest;
    }

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        setMeasuredDimension((int)mXdpi, (int)(mTextPadding * 4 + mTextSize * 2 + mLineWidth));
    }

    private String scaleBarLengthText(int value, boolean imperial) {
        if (imperial) {
            if (value >= FT_IN_MILE)
                return String.format(Locale.getDefault(), "%d mi", value / FT_IN_MILE);
            else
                return String.format(Locale.getDefault(), "%d ft", value);
        } else {
            if (value >= 1000)
                return String.format(Locale.getDefault(), "%d km", value / 1000);
            else
                return String.format(Locale.getDefault(), "%d m", value);
        }
    }
}

И можете использовать его, добавив в свой layout.xml. Например вот так:

<package.id.MapScaleBar
    android:id="@+id/scaleBar"
    android:layout_centerInParent="true"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content" />

Затем вам просто нужно добавить экземпляр Google Map в его метод addTarget, например, в функцию onMapReady(GoogleMap googleMap).

MapScaleBar scaleBar = (MapScaleBar)findViewById(R.id.scaleBar);
scaleBar.mColorMode = MapScaleBar.ColorMode.COLOR_MODE_DARK;  // normal 
scaleBar.mColorMode = MapScaleBar.ColorMode.COLOR_MODE_WHITE;  // inverted
scaleBar.mColorMode = MapScaleBar.ColorMode.COLOR_MODE_AUTO;  // detect automatically after Google Map style
scaleBar.addTarget(mMap);
person Georg Friedrich    schedule 18.04.2017

Вот мой код (используя последнюю версию пенграда).

Добавьте это в файл gradle.

dependencies {
    implementation 'com.github.pengrad:mapscaleview:1.4.2'
}

Затем вам нужно добавить компонент масштаба в файл макета с вашей картой.

<FrameLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:id="@+id/activity_main"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <fragment
        android:id="@+id/mapFragment"
        class="com.google.android.gms.maps.SupportMapFragment"
        android:layout_width="match_parent"
        android:layout_height="match_parent"/>

    <com.github.pengrad.mapscaleview.MapScaleView
        android:id="@+id/scaleView"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_gravity="bottom|end"
        android:layout_margin="4dp"/>
</FrameLayout>

Последний шаг — связать два компонента в вашей активности или фрагменте (обратите внимание, что OnCameraChangeListener сейчас устарел).

public class MyFragment extends Fragment implements OnMapReadyCallback, GoogleMap.OnCameraIdleListener, GoogleMap.OnCameraMoveListener, GoogleMap.OnCameraChangeListener {


    private GoogleMap googleMap;
    private MapScaleView mapScaleView;

....

    public View onCreateView(@NonNull LayoutInflater inflater,
                             ViewGroup container, Bundle savedInstanceState) {
        viewModel =
                ViewModelProviders.of(getActivity()).get(ViewModel.class);

        View root = inflater.inflate(R.layout.fragment_layout, container, false);
        // Get the SupportMapFragment and request notification
        // when the map is ready to be used.
        SupportMapFragment mapFragment = (SupportMapFragment) getChildFragmentManager().findFragmentById(R.id.home_map);
        mapFragment.getMapAsync(this);

        // scale view for map
        mapScaleView = (MapScaleView) root.findViewById(R.id.home_map_scale_view);

}
....

 @Override
    public void onMapReady(GoogleMap googleMap) {

        this.googleMap = googleMap;

        googleMap.setOnCameraMoveListener(this);
        googleMap.setOnCameraIdleListener(this);
        googleMap.setOnCameraChangeListener(this);
    }
...
   @Override
    public void onCameraMove() {
        CameraPosition cameraPosition = googleMap.getCameraPosition();
        if(mapScaleView != null)
            mapScaleView.update(cameraPosition.zoom, cameraPosition.target.latitude);
    }

    @Override
    public void onCameraIdle() {
        CameraPosition cameraPosition = googleMap.getCameraPosition();
        if(mapScaleView != null)
            mapScaleView.update(cameraPosition.zoom, cameraPosition.target.latitude);
    }

    @Override
    public void onCameraChange(CameraPosition cameraPosition) {
        if(mapScaleView != null)
            mapScaleView.update(cameraPosition.zoom, cameraPosition.target.latitude);
    }

}

Затем вы можете настроить viewScale, например:

mapScaleView.metersAndMiles() // default
mapScaleView.metersOnly()
mapScaleView.milesOnly()

Надеюсь, это поможет вам.

person Zhar    schedule 07.11.2019