如何在Android中使用Google地图V2绘制自由手多边形?

58

我想在Google地图V2上画一个自由手绘的多边形。

在Overlay Map V1中,这个任务是可能的,但是Google在V2中删除了该类。(根据Google Map V2已删除覆盖层类)。

这里有一个好的例子来画自由风格的多边形。

在Map V2中,我们可以通过Google官方文档来编程绘制多边形,但用户该怎么做呢?我发现对于Map V2并不清楚

我从简单的Google Map开始并绘制多边形以进行程序化操作,它可以正常工作,但现在我正在寻找用户如何绘制多边形?我不想基于标记在多边形上进行绘制。

// Instantiates a new Polygon object and adds points to define a rectangle
PolygonOptions rectOptions = new PolygonOptions()
              .add(new LatLng(37.35, -122.0),
                   new LatLng(37.45, -122.0),
                   new LatLng(37.45, -122.2),
                   new LatLng(37.35, -122.2),
                   new LatLng(37.35, -122.0));

// Get back the mutable Polygon
Polygon polygon = myMap.addPolygon(rectOptions);

我对这个主题进行了大量的研究和开发,但没有找到在Map V2中实现这样一个完美方案的方法。

一些问题

  • 如何在Map V2中绘制自由样式多边形(就像我们可以在Map V1中做的那样)?
  • 是否有什么技巧或替代方法来实现此目的?如果有,如何实现?
  • 我们能否在地图上获得触摸事件并绘制多边形?
  • 在Map V2中是否可行?
  • 是否可能使用返回经纬度数组的触摸事件实现?
  • 如何根据setOnDragListener中的屏幕坐标获取经纬度?

每个新版本都比旧版本多一些功能,因此我希望在Map v2中也能实现同样的功能。

我不是要求给我一些示例代码或发布你的代码,只需要一些正确的指导和文档。

我已经提供了我在研究和开发过程中找到的所有文档和证据。


你需要在Google Map v2上绘制多边形或线吗? - AndroidHacker
是的,那正是我的确切问题。 - Chintan Khetiya
看看我的更新.. - AndroidHacker
@chintankhetiya:嗨,我试图在地图v1上绘制自由手多边形,但是我在您提供的链接中找不到任何帮助。 - AndroidDev
@chintankhetiya 你好。你在吗?? - Piyush
4个回答

87

经过整整一天的研究和测试,我找到了一个解决方案。实际上,我找到了两种解决此问题的方法,但我建议使用替代方案2,因为与替代方案1相比,它真的非常简单。

实际上,在TheLittleNarutoAndroidHacker和其他一些开发者的帮助下,我找到了替代方案1,而在Khan的帮助下,我找到了替代方案2,所以感谢大家。

替代方案1

如何在Map V2中绘制自由样式多边形(就像我们可以在Map V1中做的那样)?在Map V2中可行吗?

是的,这是可行的,但您无法直接在地图上获得OnTouch()OnDraw()。因此,我们必须想出其他方法来实现这一点。

是否有任何技巧或替代方法可以实现这一点,如果有,如何实现?

是的,Google Map V2不支持在使用class="com.google.android.gms.maps.SupportMapFragment"的地图上使用OnTouch()OnDraw(),因此我们必须计划创建一个自定义片段。

是否可以返回具有触摸事件的Lat-long数组?

是的,如果我们创建任何自定义地图片段并使用它,我们可以在地图上获得TouchDrag事件。

如何在setOnDragListener上根据屏幕坐标获取纬度和经度?

setOnDragListener将返回屏幕坐标(x,y)。现在,有一些技术可用于将(x,y)转换为LatLng,包括ProjectionPointLatLng

customMapFragment.setOnDragListener(new MapWrapperLayout.OnDragListener() {@Override
    public void onDrag(MotionEvent motionEvent) {
        Log.i("ON_DRAG", "X:" + String.valueOf(motionEvent.getX()));
        Log.i("ON_DRAG", "Y:" + String.valueOf(motionEvent.getY()));

        float x = motionEvent.getX(); // get screen x position or coordinate 
        float y = motionEvent.getY();  // get screen y position or coordinate 

        int x_co = Integer.parseInt(String.valueOf(Math.round(x))); // casting float to int 
        int y_co = Integer.parseInt(String.valueOf(Math.round(y))); // casting float to int 

        projection = mMap.getProjection(); // Will convert your x,y to LatLng
        Point x_y_points = new Point(x_co, y_co);// accept int x,y value
        LatLng latLng = mMap.getProjection().fromScreenLocation(x_y_points); // convert x,y to LatLng
        latitude = latLng.latitude; // your latitude 
        longitude = latLng.longitude; // your longitude 

        Log.i("ON_DRAG", "lat:" + latitude);
        Log.i("ON_DRAG", "long:" + longitude);

        // Handle motion event:
    }
});

它是如何工作的?

正如我之前提到的,我们需要创建一个自定义根视图,并使用它来获取地图上的触摸拖动事件。

步骤1:我们创建 MySupportMapFragment extends SupportMapFragment 并将其用作我们的 .xml 文件。

 <fragment
        android:id="@+id/map"
        android:layout_width="fill_parent"
        android:layout_height="fill_parent"
        class="pkg_name.MySupportMapFragment" /> 

步骤2:创建一个MapWrapperLayout extends FrameLayout,以便我们可以在其中设置触摸或拖动监听器,并将其视图与地图视图嵌入其中。因此,我们需要一个接口,在Root_Map.java中使用。

MySupportMapFragment.Java

public class MySupportMapFragment extends SupportMapFragment {
    public View mOriginalContentView;
    public MapWrapperLayout mMapWrapperLayout;

    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup parent, Bundle savedInstanceState) {

        mOriginalContentView = super.onCreateView(inflater, parent, savedInstanceState);
        mMapWrapperLayout = new MapWrapperLayout(getActivity());
        mMapWrapperLayout.addView(mOriginalContentView);
        return mMapWrapperLayout;
    }

    @Override
    public View getView() {
        return mOriginalContentView;
    }

    public void setOnDragListener(MapWrapperLayout.OnDragListener onDragListener) {
        mMapWrapperLayout.setOnDragListener(onDragListener);
    }
}

MapWrapperLayout.java

    public class MapWrapperLayout extends FrameLayout {
     private OnDragListener mOnDragListener;

     public MapWrapperLayout(Context context) {
         super(context);
     }

     public interface OnDragListener {
         public void onDrag(MotionEvent motionEvent);
     }

     @Override
     public boolean dispatchTouchEvent(MotionEvent ev) {
         if (mOnDragListener != null) {
             mOnDragListener.onDrag(ev);
         }
         return super.dispatchTouchEvent(ev);
     }

     public void setOnDragListener(OnDragListener mOnDragListener) {
         this.mOnDragListener = mOnDragListener;
     }

 }

Root_Map.Java

->

根映射(Root_Map).Java

public class Root_Map extends FragmentActivity {

    private GoogleMap mMap;
    public static boolean mMapIsTouched = false;
    MySupportMapFragment customMapFragment;
    Projection projection;
    public double latitude;
    public double longitude;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.root_map);
        MySupportMapFragment customMapFragment = ((MySupportMapFragment) getSupportFragmentManager().findFragmentById(R.id.map));
        mMap = customMapFragment.getMap();

        customMapFragment.setOnDragListener(new MapWrapperLayout.OnDragListener() {               @Override
            public void onDrag(MotionEvent motionEvent) {
                Log.i("ON_DRAG", "X:" + String.valueOf(motionEvent.getX()));
                Log.i("ON_DRAG", "Y:" + String.valueOf(motionEvent.getY()));

                float x = motionEvent.getX();
                float y = motionEvent.getY();

                int x_co = Integer.parseInt(String.valueOf(Math.round(x)));
                int y_co = Integer.parseInt(String.valueOf(Math.round(y)));

                projection = mMap.getProjection();
                Point x_y_points = new Point(x_co, y_co);
                LatLng latLng = mMap.getProjection().fromScreenLocation(x_y_points);
                latitude = latLng.latitude;
                longitude = latLng.longitude;

                Log.i("ON_DRAG", "lat:" + latitude);
                Log.i("ON_DRAG", "long:" + longitude);

                // Handle motion event:
            }
        });
    }}

参考 Link1Link2

到目前为止,我已经能够根据X,Y屏幕坐标获取LatLong。现在我只需要将其存储在数组中。该数组将用于在地图上绘制,最终看起来像一个自由形状的多边形。

enter image description here

希望这一定会对你有所帮助。

更新:

备选方案2

如我们所知,Frame layout 是一个透明的布局,因此我使用 Frame Layout 实现了这个功能。

在这种情况下,无需创建自定义片段。我只是使用Frame Layout作为根布局。因此,基本上我将在根布局中获得触摸事件,就像我们之前在自定义片段中获得的那样返回屏幕坐标。

现在,我在“Free Draw”中创建了一个按钮。因此,当您单击该按钮时,可以在地图上移动手指并绘制自由手多边形,并且这将禁用地图在屏幕上的可移动性。当您重新单击同一按钮时,屏幕进入理想模式。

root_map.xml

<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="fill_parent"
    android:layout_height="fill_parent" >

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

    <FrameLayout
        android:id="@+id/fram_map"
        android:layout_width="fill_parent"
        android:layout_height="fill_parent" >

        <Button
            android:id="@+id/btn_draw_State"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="Free Draw" />
    </FrameLayout>

</FrameLayout>

Root_Map.java

FrameLayout fram_map = (FrameLayout) findViewById(R.id.fram_map);
Button btn_draw_State = (Button) findViewById(R.id.btn_draw_State);
Boolean Is_MAP_Moveable = false; // to detect map is movable 

// 按钮将改变地图可移动状态

btn_draw_State.setOnClickListener(new View.OnClickListener() {
    @Override
    public void onClick(View v) {
        Is_MAP_Moveable = !Is_MAP_Moveable;
    }
});

通过 Frame Layout 的触摸点击,并借助它来完成某些任务。

fram_map.setOnTouchListener(new View.OnTouchListener() {     @Override
    public boolean onTouch(View v, MotionEvent event) {
        float x = event.getX();
        float y = event.getY();

        int x_co = Math.round(x);
        int y_co = Math.round(y);

        projection = mMap.getProjection();
        Point x_y_points = new Point(x_co, y_co);

        LatLng latLng = mMap.getProjection().fromScreenLocation(x_y_points);
        latitude = latLng.latitude;

        longitude = latLng.longitude;

        int eventaction = event.getAction();
        switch (eventaction) {
            case MotionEvent.ACTION_DOWN:
                // finger touches the screen
                val.add(new LatLng(latitude, longitude));

            case MotionEvent.ACTION_MOVE:
                // finger moves on the screen
                val.add(new LatLng(latitude, longitude));

            case MotionEvent.ACTION_UP:
                // finger leaves the screen
                Draw_Map();
                break;
        }

        return Is_MAP_Moveable;

    }
});

// 绘制您的地图

public void Draw_Map() {
    rectOptions = new PolygonOptions();
    rectOptions.addAll(val);
    rectOptions.strokeColor(Color.BLUE);
    rectOptions.strokeWidth(7);
    rectOptions.fillColor(Color.CYAN);
    polygon = mMap.addPolygon(rectOptions);
}

然而,现在您必须在绘图时保持列表的更新,因此您需要清除先前的列表数据。


@chintan... 做得好伙计,感谢分享这有价值的信息。这个值+1 :) - AndroidHacker
做得好!我喜欢你对“自己动手做”的热爱。非常有帮助。唯一可以建议的是,如果DrawMap()MotionEvent.ACTION_MOVE调用,它看起来会更好。继续保持良好的工作和社区贡献。谢谢。 - Muhammad Babar
@Emilla,你从onTouch返回了true吗? - Muhammad Babar
1
你还忘了在MOVE和DOWN中加上break - Muhammad Babar
我修改了XML,使FrameLayout包裹我的地图片段。然而,它从未进入onTouchListener。有人知道为什么吗? - Ryan Newman
显示剩余8条评论

3

请看这个...我相信您能够展示谷歌地图 v2

在 AsyncTask 中查看 "decodePoly" 和 "drawPath" 方法

"drawPath" 是主要重点..

PolylineOptions options = new PolylineOptions().width(5).color(Color.BLUE).geodesic(true);
for (int z = 0; z < list.size(); z++) {
    LatLng point = list.get(z);
    options.add(point);
}
line = myMap.addPolyline(options);

以下是供参考的完整类:

package com.example.androidhackergooglemap;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.UnsupportedEncodingException;
import java.util.ArrayList;
import java.util.List;

import org.apache.http.HttpEntity;
import org.apache.http.HttpResponse;
import org.apache.http.client.ClientProtocolException;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.impl.client.DefaultHttpClient;
import org.json.JSONArray;
import org.json.JSONObject;

import com.google.android.gms.maps.CameraUpdateFactory;
import com.google.android.gms.maps.GoogleMap;
import com.google.android.gms.maps.SupportMapFragment;
import com.google.android.gms.maps.model.BitmapDescriptorFactory;
import com.google.android.gms.maps.model.LatLng;
import com.google.android.gms.maps.model.Marker;
import com.google.android.gms.maps.model.MarkerOptions;
import com.google.android.gms.maps.model.Polyline;
import com.google.android.gms.maps.model.PolylineOptions;

import android.app.ProgressDialog;
import android.content.Context;
import android.content.Intent;
import android.graphics.Color;
import android.location.Location;
import android.location.LocationManager;
import android.os.AsyncTask;
import android.os.Bundle;
import android.provider.Settings;
import android.support.v4.app.FragmentActivity;
import android.util.Log;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.Toast;

public class MainActivity extends FragmentActivity implements OnClickListener {

    private GoogleMap myMap;
    Polyline line;
    Context context;

    Location location;
    boolean check_provider_enabled = false;

    // Static LatLng
    LatLng startLatLng = new LatLng(30.707104, 76.690749);
    LatLng endLatLng = new LatLng(30.721419, 76.730017);

    public void onCreate(Bundle bd) {
        super.onCreate(bd);
        setContentView(R.layout.activity_main);
        context = MainActivity.this;

        // GoogleMap myMap
        myMap = ((SupportMapFragment) getSupportFragmentManager()
            .findFragmentById(R.id.map)).getMap();
        myMap.setMyLocationEnabled(true);
        myMap.moveCamera(CameraUpdateFactory.newLatLng(startLatLng));
        myMap.animateCamera(CameraUpdateFactory.zoomTo(12));

        LocationManager service = (LocationManager) getSystemService(LOCATION_SERVICE);
        boolean enabled = service.isProviderEnabled(LocationManager.GPS_PROVIDER);
        location = service.getLastKnownLocation(LocationManager.GPS_PROVIDER);

        // check if enabled and if not send user to the GSP settings
        // Better solution would be to display a dialog and suggesting to 
        // go to the settings
        if (!enabled) {
            /*Intent intent = new Intent(Settings.ACTION_LOCATION_SOURCE_SETTINGS);
            startActivity(intent);*/
            Intent intent = new Intent(Settings.ACTION_LOCATION_SOURCE_SETTINGS);
            startActivity(intent);
            Toast.makeText(getApplicationContext(), "Enable GPS servcies to use this app.", Toast.LENGTH_LONG).show();
        } else {
            try {
                String urlTopass = makeURL(startLatLng.latitude,
                    startLatLng.longitude, endLatLng.latitude,
                    endLatLng.longitude);
                new connectAsyncTask(urlTopass).execute();
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
        // Now auto clicking the button
        // btntemp.performClick();
    }


    private class connectAsyncTask extends AsyncTask < Void, Void, String > {
        private ProgressDialog progressDialog;
        String url;

        connectAsyncTask(String urlPass) {
            url = urlPass;
        }

        @Override
        protected void onPreExecute() {
            // TODO Auto-generated method stub
            super.onPreExecute();
            progressDialog = new ProgressDialog(context);
            progressDialog.setMessage("Fetching route, Please wait...");
            progressDialog.setIndeterminate(true);
            progressDialog.show();
        }

        @Override
        protected String doInBackground(Void...params) {
            JSONParser jParser = new JSONParser();
            String json = jParser.getJSONFromUrl(url);
            return json;
        }

        @Override
        protected void onPostExecute(String result) {
            super.onPostExecute(result);
            progressDialog.hide();
            if (result != null) {
                drawPath(result);
            }
        }
    }

    public String makeURL(double sourcelat, double sourcelog, double destlat,
        double destlog) {
        StringBuilder urlString = new StringBuilder();
        urlString.append("http://maps.googleapis.com/maps/api/directions/json");
        urlString.append("?origin="); // from
        urlString.append(Double.toString(sourcelat));
        urlString.append(",");
        urlString.append(Double.toString(sourcelog));
        urlString.append("&destination="); // to
        urlString.append(Double.toString(destlat));
        urlString.append(",");
        urlString.append(Double.toString(destlog));
        urlString.append("&sensor=false&mode=driving&alternatives=true");
        return urlString.toString();
    }

    public class JSONParser {

        InputStream is = null;
        JSONObject jObj = null;
        String json = "";

        // constructor
        public JSONParser() {}

        public String getJSONFromUrl(String url) {

            // Making HTTP request
            try {
                // defaultHttpClient
                DefaultHttpClient httpClient = new DefaultHttpClient();
                HttpPost httpPost = new HttpPost(url);

                HttpResponse httpResponse = httpClient.execute(httpPost);
                HttpEntity httpEntity = httpResponse.getEntity();
                is = httpEntity.getContent();

            } catch (UnsupportedEncodingException e) {
                e.printStackTrace();
            } catch (ClientProtocolException e) {
                e.printStackTrace();
            } catch (IOException e) {
                e.printStackTrace();
            }
            try {
                BufferedReader reader = new BufferedReader(
                    new InputStreamReader(is, "iso-8859-1"), 8);
                StringBuilder sb = new StringBuilder();
                String line = null;
                while ((line = reader.readLine()) != null) {
                    sb.append(line + "\n");
                }

                json = sb.toString();
                is.close();
            } catch (Exception e) {
                Log.e("Buffer Error", "Error converting result " + e.toString());
            }
            return json;
        }
    }

    public void drawPath(String result) {
        if (line != null) {
            myMap.clear();
        }
        myMap.addMarker(new MarkerOptions().position(endLatLng).icon(
            BitmapDescriptorFactory.fromResource(R.drawable.ic_launcher)));
        myMap.addMarker(new MarkerOptions().position(startLatLng).icon(
            BitmapDescriptorFactory.fromResource(R.drawable.ic_launcher)));
        try {
            // Tranform the string into a json object
            final JSONObject json = new JSONObject(result);
            JSONArray routeArray = json.getJSONArray("routes");
            JSONObject routes = routeArray.getJSONObject(0);
            JSONObject overviewPolylines = routes
                .getJSONObject("overview_polyline");
            String encodedString = overviewPolylines.getString("points");
            List < LatLng > list = decodePoly(encodedString);

            PolylineOptions options = new PolylineOptions().width(5).color(Color.BLUE).geodesic(true);
            for (int z = 0; z < list.size(); z++) {
                LatLng point = list.get(z);
                options.add(point);
            }
            line = myMap.addPolyline(options);

            /*for (int z = 0; z < list.size() - 1; z++) {
                LatLng src = list.get(z);
                LatLng dest = list.get(z + 1);
                line = myMap.addPolyline(new PolylineOptions()
                        .add(new LatLng(src.latitude, src.longitude),
                                new LatLng(dest.latitude, dest.longitude))
                        .width(5).color(Color.BLUE).geodesic(true));
            }*/

        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    private List < LatLng > decodePoly(String encoded) {

        List < LatLng > poly = new ArrayList < LatLng > ();
        int index = 0, len = encoded.length();
        int lat = 0, lng = 0;

        while (index < len) {
            int b, shift = 0, result = 0;
            do {
                b = encoded.charAt(index++) - 63;
                result |= (b & 0x1f) << shift;
                shift += 5;
            } while (b >= 0x20);
            int dlat = ((result & 1) != 0 ? ~(result >> 1) : (result >> 1));
            lat += dlat;

            shift = 0;
            result = 0;
            do {
                b = encoded.charAt(index++) - 63;
                result |= (b & 0x1f) << shift;
                shift += 5;
            } while (b >= 0x20);
            int dlng = ((result & 1) != 0 ? ~(result >> 1) : (result >> 1));
            lng += dlng;

            LatLng p = new LatLng((((double) lat / 1E5)),
                (((double) lng / 1E5)));
            poly.add(p);
        }

        return poly;
    }

    @Override
    public void onClick(View arg0) {
        // TODO Auto-generated method stub
    }
}

希望这些能帮到你,加油!

更新

看看这个链接:如何为Google Map v2 Fragment创建OnDragListener

还有这个链接: 如何通过触摸Google Map v2上的地图片段来画形状

这里还有更多参考链接:如何从谷歌地图v2 Android标记中获取屏幕坐标


很好的策略,使用“makeURL()”来生成URL。对此给予加1。 - TheLittleNaruto
@TheLittleNaruto .. 谢谢。 - AndroidHacker
这是一种静态实现的方式,我已经完成了。我想从用户端绘制并获取该多边形的“纬度-经度数组”。 - Chintan Khetiya

2

我们有一些关于在地图V2上自由手绘的解决方案。 在您的地图活动中实现GoogleMap.OnMarkerDragListener。它将重写onMarkerDrag函数。

@Override
public void onMarkerDrag(Marker marker) {

   //add the marker's latlng in a arraylist of LatLng and pass it to the loop
    for (int i = 0; i < arraylistoflatlng.size(); i++) {
         myMap.addPolyline(new PolylineOptions()
        .addAll(arraylistoflatlng)
        .width(5)
        .color(Color.RED));
    }
    }

您可以通过某种方式免费地传递hack,例如用户一触摸地图,您将需要检测该坐标并将其传递给onMarkerDrag。由于您将需要使用该区域的信息进行进一步处理。对于触摸事件,您可以实现GoogleMap.OnMapClickListener并从其参数中获取坐标。

希望这有所帮助 :)


我不想使用马克笔进行绘图,这已经在我的问题中提到了。顺便说一下,感谢您的帮助。 - Chintan Khetiya

1
这是Google Maps API V2教程:

public class MapPane extends Activity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.map_activity);
        GoogleMap map = ((MapFragment) getFragmentManager()
                .findFragmentById(R.id.map)).getMap();

        map.moveCamera(CameraUpdateFactory.newLatLngZoom(
                new LatLng(-18.142, 178.431), 2));

        // Polylines are useful for marking paths and routes on the map.
        map.addPolyline(new PolylineOptions().geodesic(true)
                .add(new LatLng(-33.866, 151.195))  // Sydney
                .add(new LatLng(-18.142, 178.431))  // Fiji
                .add(new LatLng(21.291, -157.821))  // Hawaii
                .add(new LatLng(37.423, -122.091))  // Mountain View
        );
    }
}

链接: https://developers.google.com/maps/documentation/android/


谢谢你的回答,但我昨晚已经做了+1。 - Chintan Khetiya

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接