I currently have an Android Mapview that displays a very basic subclass of OverlayItem with some custom properties:
public class StreetLocation extends OverlayItem{
public int id;
public float latitude, longitude;
public float distance;
boolean isHouse = false;
boolean hasSnacks = false;
... and so on, with a bunch of getters/setters
}
These are retrieved from a database, via building a co-ordinate span region (based on the user's view of the map) with no problems at all.
Now I have added a few simple Button's on my UI which I wish to use to toggle the display of markers with isHouse and / or hasSnacks. Currently the only way I can think of doing this is by completely clearing the MapView of all markers, then adding only the ones I want back on to the MapView, by filtering the array of StreetLocation's I get back from my database for the users view.
My issue with this is that this seems like a lot of work - is there any other way to simply hide or toggle the visual representation of the marker without clearing the entire map, since all markers are retrieved initially regardless of properties?
My apologies if this was a bit long, and I'll be glad to explain more / post code if required.
Related
I have an android map with google maps that dynamically loads markers from the server based on user screen position. So for example if user moves the map I simply made request to the server and I'm sending screen bounds and based on that I get markers data (id and corrdinates) which is later parsed and created to actual pointers. Problem is that when user goes back to the same area (which markers was previously created) I still making same request and get same data (but obviously I won't let to recreate that marker so I just run for loop throught all markers that there are on map and check if map marker id is equal server send data marker id and if it is equal I simply break the loop)
try {
Collection<MarkerItemData> mapMarkers = algorithm.getItems();
JSONObject jsonObject = new JSONObject(strings[0]);
JSONArray respondArray = jsonObject.getJSONArray("respond");
list = new ArrayList();
for (int i = 0; i < respondArray.length(); i++) {
JSONObject station = respondArray.getJSONObject(i);
int id = station.getInt("_id");
boolean skip = false;
for (final MarkerItemData m : mapMarkers) {
if (m.getId() == id) {
skip = true;
break;
}
}
}
}
However I don't think that this approach is the best. I also have other ideas that should work (at least I think)
send to server screen bounds and also all marker id's that are visible on the screen (I could select all markers that is in screen range and which ids are not in screen bounds)
each time delete markers from android app and basically recreate all the markers from server each time (Personally I think this is a bad solution)
So which of those ideas are the best? Any other ideas? (Sorry for my english)
Sending screen bounds to the server and id-s of currently visible markers on screen is your best bet. But still there are a couple of troublesome issues. How will you find all markers contained within the range you specified with screen bounds? What if new markers come to your server or some markers are deleted? Can you come with a maintainable structure in such a situation or will you test each point that corresponds a marker in the database one by one to see whether it locates in the range or not? Considering all these, you need to find a way to optimize storing and querying points, in other words latitude and longitude pairs that point markers. You should perform spatial indexing by using one of the common spatial index methods.
There are lots of methods for spatial indexing and one might be slightly better than the other one depending on use case. To make long story short, since you need to query range in this scenario, you should implement a quadtree. The quadtree is called as a tree data structure in which each internal node has exactly four children (northwest, northeast, southwest, southeast). If you have no knowledge about this structure, I believe you can understand its basics in an hour but explaining it in detail from scratch will cause loss of too much time for me. Therefore, I am skipping implementation details about quadtrees. There are several sources which already explained this structure much better than I could, and you can easily find an open source library.
I will only give pseudo-ish Java method for your quadtree to find all points that appear within the range of screen bounds excluding the ones that are already in the previous screen:
ArrayList<LatLng> queryRange(QuadLevel level, float[] screenBounds, ArrayList<LatLng> prevPoints) {
// Initialize a list to hold the found points
ArrayList<LatLng> pointsInRange = new ArrayList<>();
if (!quadtreeBounds.intersects(screenBounds))
return pointsInRange;
// Find the points that are in the current quad level
for (LatLng point : level.getPoints()) {
// If the current point is in screen bounds and it is not contained by prevPoints
if (point.isInRange(screenBounds)
&& !prevPoints.contains(point))
pointsInRange.add(point);
}
// If there are no children, return
if (level.hasNoChildren())
return pointsInRange;
// Else, continue to look up children
pointsInRange.addAll(queryRange(level.northwest, screenBounds, prevPoints));
pointsInRange.addAll(queryRange(level.northeast, screenBounds, prevPoints));
pointsInRange.addAll(queryRange(level.southwest, screenBounds, prevPoints));
pointsInRange.addAll(queryRange(level.southeast, screenBounds, prevPoints));
return pointsInRange;
}
Store all the Visible Markers on Screen in List or Local Db or Some place And Iterate over the Existing Markers in list if Not Present Request From Server
Extend HashSet<MarkerOptions>, so that when a MarkerOptions is added you add it to your map.
class MarkerSet extends HashSet<MarkerOptions> {
#Override
public boolean add(MarkerOptions markerOptions) {
boolean didAdd = super.add(markerOptions);
if(didAdd) {
mMap.addMarker(markerOptions);
}
return didAdd;
}
}
Maintain a LatLngBounds object in your maps activity. This LatLngBounds object will store bounds for which you already have requested data from the server.
When map's camera moves check if new bounds are within currently stored bounds if not request the server and increase bounds else do not request the server.
mMap.setOnCameraMoveListener(new GoogleMap.OnCameraMoveListener() {
#Override
public void onCameraMove() {
LatLngBounds currentBounds = mMap.getProjection().getVisibleRegion().latLngBounds;
if (mBounds == null) {
mBounds = currentBounds;
// Perform server request and get markers passing in currentBounds
// Following to be performed in server request's callback
MarkerOptions markerOptions = new MarkerOptions();
// set marker options here
mMarkerSet.add(markerOptions);
} else {
if (!(mBounds.contains(currentBounds.northeast) || mBounds.contains(currentBounds.southwest))) {
mBounds = mBounds.including(currentBounds.northeast);
mBounds = mBounds.including(currentBounds.southwest);
// Perform server request and get markers passing in currentBounds
// Following to be performed in server request's callback
MarkerOptions markerOptions = new MarkerOptions();
// set marker options here
mMarkerSet.add(markerOptions);
}
}
}
});
Ensure that you are always invoking server request for currentBounds. This algorithm can be improved if you manage to shorten currentBounds when currentBounds and mBounds are overlapping.
Assumption
Please note that assumption here is that when the camera moves, currentBounds will fall slightly out of mBounds. Otherwise, new markers are not loaded.
Referring to the image, if the user moves from visible green region to orange the mBounds value will be the blue rectangle, and therefore white area will not be covered. However, in practice, it is not easy to move camera perfectly horizontally or vertically, this will cause invocation of server method and new markers for the unventured area to be loaded.
Hope this helps. :)
Forgive me for being new to android app building.
My plan is to build an app that would take a city and open a new activity for it. The problem is I really don't know how to go about that. My plan would be very similar to how the app yik yak does it where you go put a marker in a certain area and it brings you to the activity for that location. I believe yik yak only shows the ones that are close to you but my plan was to take you to an activity for that location. Is that possible to do it that way or should I take yik yak's route on it and only show things that are within a certain radius of you?
Where can I get started on learning how to do that?
First, you need to choose a provider for your maps.
I would say google map is a good start as you suggested in the tag, but beware, it might not be available on some Chinese devices without play services.
You can follow the documentation here for a quick start
https://developers.google.com/maps/documentation/android/
Regarding the number of pins you want to put on your map, I see no problem to display a lot of them, you just have to group markers as the user zooms out, so you don't have 10 pins overlapping themselves.
So if in a quite zoomed state, the user clicks a group of markers, you can display a dialog to choose the city.
If the user zooms in a lot and clicks a particular pin, then you can start an activity.
Your main concern is listening for a zoom level change, like that:
mMap.setOnCameraChangeListener(new OnCameraChangeListener() {
private float currentZoom = -1;
#Override
public void onCameraChange(CameraPosition pos) {
if (pos.zoom != currentZoom){
currentZoom = pos.zoom;
// do you action here
}
}
});
Credits: https://stackoverflow.com/a/15046761/689710
Then, you can create a Map of Lists, the keys of the map will be the a custom lat / long object. Let's call it Pin. Pin provides it a custom isCloseTo method (taking a zoomlevel in params, and an other Pin object).
For each city you want to add on the map
For each key in the map
If the Pin object of your city isCloseTo the Pin key of the map
Add it to the list for that key
Else
Add a new Map entry with you city Pin as key and a list with your city as value
You Pin.isCloseTo method will be somehow similar to that:
https://stackoverflow.com/a/18170277/689710
Returning true or false according to "dist" return and your zoom level.
When you processed all your cities, you can browse the map and create a marker for each Pin key.
In a zoomed in state, you will have a Map with lists containing only one item, but i don't think it's much a problem.
You know yik yak is not starting a new activity when the place on the map is clicked. It rather updates the data underneath it when a new place is clicked. I think what you mean is you want to refresh/change the data displayed when a new city on google maps is clicked?
I want to build an interactive Android map app. It will have different marker types and lots of of different options when clicking on them.
First approach :
I started with the notion I will use custom infowindows but figured out that a map can have only single InfoWindowAdapter, with that said, this approach has another fault. InfoWindows can't have click listeners registered to them and I need to have some clickable UI to show after marker click.
Second approach :
Marker click triggers an alertDialog which corresponds to the marker type. I'm hesitant because I'll have lots of switch case inside the OnActivityResult.
Example - dialog fragments with OnActivityResult
Any other ideas ? Am I missing something ?
I ran into similar problem some time ago and I "hacked" it as follows:
mGoogleMap.setInfoWindowAdapter(new InfoWindowAdapter() {
#Override
public View getInfoWindow(Marker pMarker) {
MarkerDescriptor descriptor = mMarkerDescriptorsMap.get(pMarker);
mGoogleMap.setOnInfoWindowClickListener(descriptor.getOnInfoWindowClickListener(MapActivity.this));
return descriptor.getInfoWindowView();
}
}
MarkerDescriptor should be simple interface that will be implemented for each specific marker type:
public interface MarkerDescriptor {
public View getInfoWindowView();
public OnInfoWindowClickListener getOnInfoWindowClickListener(Context pContext);
}
And to keep the references:
private Map<Marker, MarkerDescriptor> mMarkerDescriptorsMap = new HashMap<Marker, MarkerDescriptor>();
Basics of this idea is that GoogleMap can have only one marker selected at the time, so when user chooses another marker, we change the listeners.
I've got an Activity that holds a ListFragment on the left and a SupportMapFragment on the right.
List and Map are both backed by the same data. The visual representation of the data on the Map are Markers.
I want to be able to perform a click on either a list item or a Marker and get the corresponding item in the other visual representation.
Restrictions of the framework and my data are:
The Marker class is final and the Marker's id does not have a modificator. This is why I can't use the easiest possible way.
"It's important to not hold on to objects (e.g. Marker) beyond the
view's life. Otherwise it will cause a memory leak as the view cannot
be released." (see SupportMapFragment)
Titles of Markers can occur multiple times. This is why the expensive String comparison is not a way I can go.
Does anyone have a working solution for this issue or can provide a nudge in the right direction?
If you want to be able to register to clicks on a Marker, you'll need to override the InfoWindowAdapter.
You can provide unique information to the title and snippet of a Marker and implement the interface's methods in this style:
#Override
public View getInfoWindow(Marker marker) {
// do nothing here in order to obtain the original info window border.
return null;
}
#Override
public View getInfoContents(Marker marker) {
// Create your own view here. Obtain the unique information stored
// in title / snippet of the current marker.
return createdView;
}
Now you can set the OnInfoWindowClickListener and work with the unique information stored there.
Here's another option. The latest APIs provide an OnMarkerClickListener interface.
https://developers.google.com/maps/documentation/android/marker#marker_click_events
I created a custom OverlayItem class so that I could essentially have one kind of OverlayItem whose Drawable marker would set itself depending on the state of some data that I pass into it.
I have attempted to accomplish this by, on my first attempt, utilizing the setMarker method within the OverlayItem class. Once that did not work I attempt to override the getMarker method and have it return the appropriate marker to represent the data.
Both of these attempts ended with nothing being drawn on the map...however if they are commented out the markers draw just fine (except they of course use the default marker, which isn't what I want).
Here is my code for my custom OverlayItem class (the commented out methods I have tried and they have not worked):
private class MyOverlayItem extends OverlayItem {
private Context mContext;
private MyData mData;
public MyOverlayItem(GeoPoint point, MyData data, Context context) {
super(point, data.getWhat(), data.getWhere());
this.mContext = context;
this.mData = data;
/*if(data.getTemp() > 200)
this.setMarker(res.getDrawable(R.drawable.icon_data_hot_l));
else if(data.getTemp() > 100)
this.setMarker(res.getDrawable(R.drawable.icon_data_neutral_l));
else
this.setMarker(res.getDrawable(R.drawable.icon_data_frozen_l));*/
}
/*#Override
public Drawable getMarker(int stateBitset) {
Resources res = this.mContext.getResources();
if(this.mData.getTemp() > 200)
return res.getDrawable(R.drawable.icon_data_hot_l);
else if(this.mData.getTemp() > 100)
return res.getDrawable(R.drawable.icon_data_neutral_l);
return res.getDrawable(R.drawable.icon_data_frozen_l);
}*/
}
Is there a way to do what I am attempting to do...or do I need to make a unique OverlayItem class corresponding to each state of my data? (Ew.)
I have been able to make the markers appear, however they are appearing upside-down (at least, I think they are, it may just be that their shadow is at the top-left rather than the bottom-right).
All I needed to do was add this line:
this.mMarker.setBounds(0, 0, this.mMarker.getIntrinsicWidth(), this.mMarker.getIntrinsicHeight());
...as well as this line to correctly orient the markers (I added this when my ItemizedOverlay adds tan overlay item:
overlay.setMarker(boundCenterBottom(overlay.getMarker(0)));
The API states that the Marker needs bounds to be displayed...
#celestialorb Either one of your snippets does the job alone (or mine worked with either anyway)
when you use setBounds() alone, you get what you tell it to do... the bounding box for your marker is set. The orientation to the OverlayItem's Point and shadow state of the Marker, etc are set to default (didn't look those up, but they don't do what I want...) :)
mOverlay.setMarker(boundCenterBottom(mOverlay.getMarker(0))); does the complete job for you, setting the bounds and centering the bottom of the Marker on the Point (hence the stylish Method name).
Now, as to best practices, Brighter bulbs than I will have to chime in...
Try overriding the onDraw() method of your overlay class.
Here is another approach:
GoogleMaps: custom ItemizedOverlay and OverlayItem, the correct way to show different marker
I think I prefer this answer though... thanks!