Polygon Touch Detection Google Map API V2

Polygon Touch detection Google Map API V2

The problem you're trying to solve is the Point in Polygon test.

To help visualize the concept of Ray Casting:

Draw a Polygon on a piece of paper. Then, starting at any random point, draw a straight line to the right of the page. If your line intersected with your polygon an odd number of times, this means your starting point was inside the Polygon.


So, how do you do that in code?

Your polygon is comprised of a list of vertices: ArrayList<Geopoint> vertices. You need to look at each Line Segment individually, and see if your Ray intersects it

private boolean isPointInPolygon(Geopoint tap, ArrayList<Geopoint> vertices) {
int intersectCount = 0;
for(int j=0; j<vertices.size()-1; j++) {
if( rayCastIntersect(tap, vertices.get(j), vertices.get(j+1)) ) {
intersectCount++;
}
}

return (intersectCount%2) == 1); // odd = inside, even = outside;
}

private boolean rayCastIntersect(Geopoint tap, Geopoint vertA, Geopoint vertB) {

double aY = vertA.getLatitude();
double bY = vertB.getLatitude();
double aX = vertA.getLongitude();
double bX = vertB.getLongitude();
double pY = tap.getLatitude();
double pX = tap.getLongitude();

if ( (aY>pY && bY>pY) || (aY<pY && bY<pY) || (aX<pX && bX<pX) ) {
return false; // a and b can't both be above or below pt.y, and a or b must be east of pt.x
}

double m = (aY-bY) / (aX-bX); // Rise over run
double bee = (-aX) * m + aY; // y = mx + b
double x = (pY - bee) / m; // algebra is neat!

return x > pX;
}

android google maps which polygon touched

I found a way to store my polygon ID. I could not modify polygon's ID but it is possible to set a float value of polygon's zIndex.

I'm using integer part of zIndex for polygon's display order and the decimal part of zIndex for my ID value.

For example : 4.12322 4 is display order and 12322 is my ID.

Google Maps Android API v2 - detect touch on map

@ape wrote an answer here on how to intercept the map clicks, but I need to intercept the touches, and then he suggested the following link in a comment of its answer, How to handle onTouch event for map in Google Map API v2?.

That solution seems to be a possible workaround, but the suggested code was incomplete. For this reason I rewrote and tested it, and now it works.

Here it is the working code:

I created the class MySupportMapFragment.java

import com.google.android.gms.maps.SupportMapFragment;
import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;

public class MySupportMapFragment extends SupportMapFragment {
public View mOriginalContentView;
public TouchableWrapper mTouchView;

@Override
public View onCreateView(LayoutInflater inflater, ViewGroup parent, Bundle savedInstanceState) {
mOriginalContentView = super.onCreateView(inflater, parent, savedInstanceState);
mTouchView = new TouchableWrapper(getActivity());
mTouchView.addView(mOriginalContentView);
return mTouchView;
}

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

I even created the class TouchableWrapper.java:

import android.content.Context;
import android.view.MotionEvent;
import android.widget.FrameLayout;

public class TouchableWrapper extends FrameLayout {

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

@Override
public boolean dispatchTouchEvent(MotionEvent event) {

switch (event.getAction()) {

case MotionEvent.ACTION_DOWN:
MainActivity.mMapIsTouched = true;
break;

case MotionEvent.ACTION_UP:
MainActivity.mMapIsTouched = false;
break;
}
return super.dispatchTouchEvent(event);
}
}

In the layout I declare it this way:

<fragment xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/mapFragment"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_alignParentBottom="true"
android:layout_below="@+id/buttonBar"
class="com.myFactory.myApp.MySupportMapFragment"
/>

Just for test in the main Activity I wrote only the following:

public class MainActivity extends FragmentActivity {
public static boolean mMapIsTouched = false;

@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
}
}

How to tell if a Marker is in a Polygon googleMap v2

The easiest way to do this would be using the Google Maps Android API Utility Library, which contains the PolyUtil class.

First import the library by adding the current version to your build.gradle, currently 0.3.4
for example:

dependencies {
compile fileTree(dir: 'libs', include: ['*.jar'])
compile 'com.android.support:appcompat-v7:22.1.1'
compile 'com.google.android.gms:play-services-maps:7.3.0'
compile 'com.google.maps.android:android-maps-utils:0.3.4'
}

For this simple example we'll assume the Map Activity class definition and member variables look like this, and only one Polygon defined in polygonList:

import com.google.maps.android.PolyUtil;
//other imports.....

public class MapsActivity extends AppCompatActivity implements OnMapReadyCallback {

private GoogleMap mMap;
private Marker marker;
List<LatLng> polygonList = new ArrayList<LatLng>();
//.............

You would then set up your GoogleMap.OnMapClickListener like this in order to only add one Marker inside the Polygon.:

  mMap.setOnMapClickListener(new GoogleMap.OnMapClickListener() {

@Override
public void onMapClick(LatLng point) {

if (PolyUtil.containsLocation(point, polygonList, false)) {

if (marker == null) {
//only add Marker if there is not one already inside the Polygon
marker = mMap.addMarker(new MarkerOptions()
.position(point)
.title("test")
.icon(BitmapDescriptorFactory.defaultMarker(BitmapDescriptorFactory.HUE_RED)));
Log.v("Marker", "ADDing Marker");

}

}
}
});

Multiple Polygons, Multiple Markers Solution:

In order to make it work with multiple Ploygons, you could use a POJO to store Polygon/Marker pairs:

public class PolyMarkerObject{
Polygon polygon;
Marker marker;
}

Then define a new member variable polyMarkerList:

public class MapsActivity extends AppCompatActivity implements OnMapReadyCallback {

private GoogleMap mMap;
List<PolyMarkerObject> polyMarkerList = new ArrayList<>();
//.............

Add each Polygon to the list when drawing it:

    List<LatLng> newPolygon = new ArrayList<>();
//set up the points in the Polygon.......

Polygon p = mMap.addPolygon(new PolygonOptions()
.addAll(newPolygon)
.strokeColor(Color.RED)
.fillColor(Color.BLUE));

PolyMarkerObject newPolyMarkerObj = new PolyMarkerObject();
newPolyMarkerObj.polygon = p;
polyMarkerList.add(newPolyMarkerObj);

Then cycle through the list on each Map click to see whether the current Ploygon already has a Marker. If it does not have a Marker already, then remove any Marker already placed in a different Polygon, and create one in the current Polygon:

 mMap.setOnMapClickListener(new GoogleMap.OnMapClickListener() {

@Override
public void onMapClick(LatLng point) {

for (PolyMarkerObject pmObj : polyMarkerList) {
//only add Marker if there is not one already inside the Polygon
if (PolyUtil.containsLocation(point, pmObj.polygon.getPoints(), false)) {
if (pmObj.marker == null) {

//Add Marker to current Polygon
Marker newMarker = mMap.addMarker(new MarkerOptions()
.position(point)
.title("test")
.icon(BitmapDescriptorFactory.defaultMarker(BitmapDescriptorFactory.HUE_RED)));

pmObj.marker = newMarker;
Log.v("Marker", "ADDing Marker");
break;
}

}
}
}
});

Multiple Polygons, One Marker Solution:

In this case, you just need one Marker reference, and a list of Polygons:

public class MapsActivity extends AppCompatActivity implements OnMapReadyCallback {

private GoogleMap mMap;
Marker marker;
List<Polygon> polyList = new ArrayList<>();
//................

Add the Polygon to the list when it's added to the Map:

    List<LatLng> newPolygon = new ArrayList<>();
//set up the points in the Polygon.......

Polygon p = mMap.addPolygon(new PolygonOptions()
.addAll(newPolygon)
.strokeColor(Color.RED)
.fillColor(Color.BLUE));

polyList.add(p);

Then, in the Map click listener, you have two cases, one for if the Marker reference is null (no Marker added yet), and one for if the Marker is in a different Polygon. If the Marker is in the current Polygon already, it will not be moved.

 mMap.setOnMapClickListener(new GoogleMap.OnMapClickListener() {

@Override
public void onMapClick(LatLng point) {

for (Polygon pObj : polyList) {
//find Polygon user tapped inside of
if (PolyUtil.containsLocation(point, pObj.getPoints(), false)) {
//first case, no Marker
if (marker == null) {

//Add Marker to current Polygon
marker = mMap.addMarker(new MarkerOptions()
.position(point)
.title("test")
.icon(BitmapDescriptorFactory.defaultMarker(BitmapDescriptorFactory.HUE_RED)));

Log.v("Marker", "ADDing first Marker");
break;
}
else if (!PolyUtil.containsLocation(marker.getPosition(), pObj.getPoints(), false)) {
//Marker exists already in a different Polygon
//remove Marker from previous Polygon
marker.remove();
//Add Marker to current Polygon
marker = mMap.addMarker(new MarkerOptions()
.position(point)
.title("test")
.icon(BitmapDescriptorFactory.defaultMarker(BitmapDescriptorFactory.HUE_RED)));

Log.v("Marker", "Moving Marker to new Polygon");
break;
}

}
}
}
});

Make clickable polygons on Google Maps (for Android)

Here's how I did it.

    Polygon polygon = getMap().addPolygon(new PolygonOptions()
.add(new LatLng(12.780712, 77.770956), new LatLng(12.912006, 77.229738), new LatLng(12.412006, 77.629738), new LatLng(12.912006, 77.229738))
.strokeColor(0xFF00AA00)
.fillColor(0x2200FFFF)
.strokeWidth(2)
);

polygon.setClickable(true);

getMap().setOnPolygonClickListener(new GoogleMap.OnPolygonClickListener() {
public void onPolygonClick(Polygon polygon) {

mClusterManager = new ClusterManager<MyItem>(getApplicationContext(), getMap());
getMap().setOnCameraChangeListener(mClusterManager);
getMap().moveCamera(CameraUpdateFactory.newLatLngZoom(getMap().getCameraPosition().target, getMap().getCameraPosition().zoom));

try {
readItems();
} catch (JSONException e) {
Toast.makeText(getApplicationContext(), "Problem reading list of markers.", Toast.LENGTH_LONG).show();
}

}
});

Hope that helps.

google map editable/draggable polygons

I solved this. I add markers for each vertex, save it to list and add onDragListener. Every time when onMarkerDrag callback occurs, i get latLng from markers and set them to polygon.

 @Override
public void onMarkerDrag(Marker marker) {
if (mPolygon == null) {
return;
}
mPolygon.setPoints(markersToLatLng(mVertexMarkers));
}

private List<LatLng> markersToLatLng(List<Marker> markers) {
List<LatLng> latLngs = new ArrayList<>();
if (markers == null) {
return latLngs;
}
for (Marker m : markers) {
latLngs.add(m.getPosition());
}
return latLngs;
}

How to draw free hand polygon in Google map V2 in Android?

After spending a whole day in Rnd and testing some alternatives I have found a solution. Actually I have found two alternatives for the same issue but I would like to suggest the using of Alternative 2 because that is really very easy compared to Alternative 1.

Actually I have found Alternative 1 with the help of TheLittleNaruto , AndroidHacker and some other developers & Alternative 2 with the help of Khan so thanks to all.

Alternative 1

How to Draw Free style polygon in Map V2 (as we can do with Map V1) ? Is it feasible in Map V2 ?

Yes, that is feasible but you can't get directly OnTouch() & OnDraw() on the map. So we must have to think some other way to achieve this.

Is there any trick or alternative way to achieve this thing , if yes how ?

Yes, Google Map V2 doesn't support OnTouch() or OnDraw() on a Map using class="com.google.android.gms.maps.SupportMapFragment" so we have to plan for a custom Fragment.

Is it possible to return array of lat-long with touch event ?

Yes, if we create any custom map fragment and use it we can get that Touch or Drag event over the map.

How can I get Lat-long base on screen coordinates on setOnDragListener ?

setOnDragListener will return screen coordinates (x,y). Now for that, there are some techniques to convert (x,y) to LatLng and they include Projection along with Point & LatLng.

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:
}
});

How does it work ?

As I have already mentioned before, we have to create a custom root view and using that we can get Touch or Drag Events over the map.

Step 1: We Create MySupportMapFragment extends SupportMapFragment and we will use that as our .xml file

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

Step 2: Create a MapWrapperLayout extends FrameLayout so that we can set a Touch or Drag listener inside and embed its view with map view. So, we need one Interface which we will use in 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

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:
}
});
}}

Reference Link1 , Link2

Up to here I am able to get LatLong based on X,Y screen coordinates. Now I just have to store it in Array. That array will be used for drawing on the map and finally it will look like a free shape polygon.

Sample Image

I hope this will definitely help you.

Update:

Alternative 2

As we know, Frame layout is a transparent layout so I have achieved this using Frame Layout.
In this case, there is no need to create a custom fragment. I have just used Frame Layout as root layout. So basically I will get Touch Events in the root layout and that will return screen coordinates, as we got in custom fragment previously.

Now, I have created a Button inside the "Free Draw". So when you click on that you can move your fingers on the map and draw a free hand polygon and that will disable your map being movable on screen. When you re-click the same button, the screen goes in ideal mode.

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

// Button will change Map movable state

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

Touch Click of Frame Layout and with the help of the do some task

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;

}
});

// Draw your map

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);
}

Yet, now you have to maintain your list while you draw, so you have to clear your previous list data.

Draw Polygon on GoogleMap API V2

 GoogleMap map;
// ... get a map.
// Add a triangle in the Gulf of Guinea
Polygon polygon = map.addPolygon(new PolygonOptions()
.add(new LatLng(0, 0), new LatLng(0, 5), new LatLng(3, 5), new LatLng(0, 0))
.strokeColor(Color.RED)
.fillColor(Color.BLUE));

reference: Google Maps Android API v2 - Polygon



Related Topics



Leave a reply



Submit