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.
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
How to Change Size of Title's Text on Action Bar
Method Called After Release() Exception Unable to Resume with Android Camera
What's the Best Practice to Round a Float to 2 Decimals
Sqliteopenhelper Failing to Call Oncreate
How to Iterate Through the Id Properties of R.Java Class
Simple Program to Call R from Java Using Eclipse and Rserve
Calling Java Methods in JavaScript Code
What Would Be the Fastest Method to Test for Primality in Java
Check Chains of "Get" Calls for Null
:App:Dexdebug Execexception Finished with Non-Zero Exit Value 2
Automatically Log Android Lifecycle Events Using Activitylifecyclecallbacks
How to Convert a String Date to Long Millseconds
Fatal Exception: Firebase-Messaging-Intent-Handle -- Java.Lang.Noclassdeffounderror
Endless Scrolling Listview Not Working
Java Object Analogue to R Data.Frame
Alternative to Jzebra/Qz Java Raw Print Plugin After Npapi Being Dropped on Chrome Browser