I am trying to start a simple map activity that displays a map an a couple of markers using osmdroid-android-3.0.7 library. The code worked under an older version (1.10). I am getting the following error:
02-03 15:14:30.574: E/AndroidRuntime(10277): Caused by: java.lang.IllegalArgumentException: Resource not found: marker_default.png
When I unzipped the jar file I can see a list of icons in the top level directory and one of them is indeed marker_default.png. This is the snippet of the code:
public class MapDisplay extends Activity
{
private MapView mapView;
private ResourceProxy resProxy;
private ItemizedOverlay<OverlayItem> locationOverlay;
public void onCreate(final Bundle savedState)
{
super.onCreate(savedState);
resProxy = new DefaultResourceProxyImpl(getApplicationContext());
final RelativeLayout rl = new RelativeLayout(this);
mapView = new MapView(this, 256);
mapView.setBuiltInZoomControls(true);
mapView.getController().setZoom(6);
mapView.getController().setCenter(new GeoPoint(51500000, 5400000));
rl.addView(mapView, new RelativeLayout.LayoutParams
(LayoutParams.FILL_PARENT, LayoutParams.FILL_PARENT));
final ArrayList<OverlayItem> items = new ArrayList<OverlayItem>();
OverlayItem o1 = new OverlayItem("Location 1", "Comment",
new GeoPoint(51500000, 5400000));
o1.setMarker(getResources().getDrawable(R.drawable.icall));
items.add(o1);
OverlayItem o2 = new OverlayItem("Location 2", "Comment",
new GeoPoint(52500000, 5500000));
o2.setMarker(getResources().getDrawable(R.drawable.icall));
items.add(o2);
this.locationOverlay = new ItemizedIconOverlay<OverlayItem>(items,
new ItemizedIconOverlay.OnItemGestureListener<OverlayItem>()
{
#Override
public boolean onItemSingleTapUp(final int index,
final OverlayItem item)
{
Toast.makeText(
MapDisplay.this,
"Item '" + item.mTitle + "' (index=" + index
+ ") got single tapped up", Toast.LENGTH_LONG).show();
return true; // We 'handled' this event.
}
#Override
public boolean onItemLongPress(final int index,
final OverlayItem item)
{
Toast.makeText(
MapDisplay.this,
"Item '" + item.mTitle + "' (index=" + index
+ ") got long pressed", Toast.LENGTH_LONG).show();
return false;
}
}, resProxy);
this.mapView.getOverlays().add(this.locationOverlay);
mapView.invalidate();
this.setContentView(rl);
}
}
When I tried to use a simple overlay and therefore did not set any icons for the marker then I got the same error except it referred to person.png (that icon is also in the unzipped jar file).
I have added the jar file via project properties and can access the api just fine.
Why can't I access the icons?
BTW, I tried to copy the icons into my project but I got the same error.
Thanks, Ania
I had the same problem - in the latest jar (3.0.8) the issue is fixed.
There is an issue in the bug tracker which I think has a solution for this. Have not had the issue myself I provide my own bitmap. I do remember all the proxy stuff being confusing and troublesome.
Related
I am using the following to show a corresponding marker image based on the returned business name using Google Places and Maps:
if (name.contains("walmart")) {
mIcon = R.drawable.ic_wm_poi;
} if (name.contains("speedco")) {
mIcon = R.drawable.ic_poi_speedco;
}
The logcat shows "Unkown icon: business name" which does correspond with one of my if statements but doesn't show the corresponding icon. I've tried using if/else and switch statements. I've also tried name.contains, name.equals and name.contentEquals. For example, if I search "Walmart", the response returns the business name "Walmart" as shown in the logcat, and the marker shows the name "Walmart" once clicked yet the corresponding icon doesn't show. I am setting the marker icon like so:
markerOptions.icon(BitmapDescriptorFactory.fromResource(markerIcon(placeName)));
EDIT
After implementing crickets' comment, the icons for the various locations still don't show. This is what I have:
public class MarkerIcons {
public static HashMap<String, Integer> poiIcons;
public static HashMap<String, Integer> getpoiIcons(){
poiIcons = new HashMap<>();
poiIcons.put("Walmart", R.drawable.ic_wm_poi);
poiIcons.put("Walmart Supercenter", R.drawable.ic_wm_poi);
poiIcons.put("Walmart Neighborhood Market", R.drawable.ic_wm_poi);
return poiIcons;
}
public static Integer markerIcon(String name){
String TAG_MI = "marker";
Log.d(TAG_MI, "Loc Name: " + name);
int mIcon = 0;
if(getpoiIcons().containsKey(name)){
mIcon = getpoiIcons().get(name).intValue();
return mIcon;
}else{
mIcon = R.drawable.default_marker;
return mIcon;
}
}
}
How I am calling these functions:
Get Place Class
public class GetNearbyBusinessData extends AsyncTask<Object, String, String> {
private String TAG = "getplace";
private String googlePlacesData;
private GoogleMap mMap;
private String url;
#Override
protected String doInBackground(Object... params) {
try {
Log.d(TAG, "doInBackground entered");
mMap = (GoogleMap) params[0];
url = (String) params[1];
DownloadUrl downloadUrl = new DownloadUrl();
googlePlacesData = downloadUrl.readUrl(url);
Log.d(TAG, "doInBackground Exit");
} catch (Exception e) {
Log.d(TAG, e.toString());
}
return googlePlacesData;
}
#Override
protected void onPostExecute(String result) {
Log.d(TAG, "onPostExecute Entered");
List<HashMap<String, String>> nearbyPlacesList;
DataParser dataParser = new DataParser();
nearbyPlacesList = dataParser.parse(result);
ShowNearbyBusiness(nearbyPlacesList);
Log.d(TAG, "onPostExecute Exit");
Log.d(TAG, result);
}
private void ShowNearbyBusiness(List<HashMap<String, String>> nearbyPlacesList) {
for (int i = 0; i < nearbyPlacesList.size(); i++) {
Log.d(TAG,"Entered into showing business");
MarkerOptions markerOptions = new MarkerOptions();
HashMap<String, String> googlePlace = nearbyPlacesList.get(i);
double lat = Double.parseDouble(googlePlace.get("lat"));
double lng = Double.parseDouble(googlePlace.get("lng"));
String placeName = googlePlace.get("place_name");
String vicinity = googlePlace.get("vicinity");
LatLng latLng = new LatLng(lat, lng);
markerOptions.position(latLng);
markerOptions.title(placeName + "-" + vicinity);
markerOptions.icon(BitmapDescriptorFactory.fromResource(markerIcon(placeName)));
mMap.addMarker(markerOptions);
//move map camera
mMap.moveCamera(CameraUpdateFactory.newLatLng(latLng));
mMap.animateCamera(CameraUpdateFactory.zoomTo(11));
}
}
}
EDIT 2
After setting break points for the markerIcon method and the line that calls the markerIcon method, the debug log shows that the returned response string is in fact "Walmart". Which is matching the key value in the HashMap. The returned icon for the marker is being returned as "0" though. It isn't returning the "value" from the hash map. I am setting the marker icon using this line:
mIcon = getpoiIcons().get(name).intValue();
Given the options available, I assumed that getting the matching "key" (in this case the string "name") would return its int "value". Am I missing something?
Even though the logcat shows the returned business names of Walmart, Walmart Supercenter and Walmart Neighborhood Market, it still shows the default marker. I have no idea why it is not showing the correct marker icon. Also, if it helps, my marker icons are vector images.
I think every thing work fine. but the error is, so far i have seen your code is,
markerOptions.icon(BitmapDescriptorFactory.fromResource(markerIcon(placeName)));
what you want to do is,
BitmapDescriptor icon = BitmapDescriptorFactory.fromResource(markerIcon(placeName)));
markerOptions.icon(icon);
Hope it helps.
U have function in separate class try this
MarkerIcons.markerIcon(placeName))
or update me if ur problem is cleared.
After trying the answers that were given and attempting to debug my code to find what was wrong, I found the error.
For anyone else that may have the same issue of the marker icons not showing, through trial and error I had found that you cannot use vector images that are in xml format as your markers. Once I switched them all over to png files, every icon showed without flaw.
Hope this answer helps someone else in the future. Thank you everyone for your contributions.
From what I have read so far, it seems that there is no way to style Google Maps via the Android API.
Has anyone seen differently or know a way to style Google Maps on Android (change feature colors, etc.)?
From what I have seen, the only alternative to a full map library for Android (or iOS) is Mapbox, but their Android library is still under heavy development.
Google introduced Cloud-based Map Styling (however it is in Beta now). Using this feature, it is possible to set up a Map Id and a Map Style in Google Cloud Platform and to use it in the Android App (the only thing you need to do in the app is to specify the Map Id).
Here are the details:
https://developers.google.com/maps/documentation/android-sdk/cloud-based-map-styling
PS Here is the description of the 'old' option where you need to create a special json file and use it in the Android app to get a styled map:
https://developers.google.com/maps/documentation/android-sdk/styling
We can create styled maps as follows in android:
StyledMapDemoActivity.java
public class StyledMapDemoActivity extends AppCompatActivity implements OnMapReadyCallback {
private GoogleMap mMap = null;
private static final String TAG = StyledMapDemoActivity.class.getSimpleName();
private static final String SELECTED_STYLE = "selected_style";
// Stores the ID of the currently selected style, so that we can re-apply it when
// the activity restores state, for example when the device changes orientation.
private int mSelectedStyleId = R.string.style_label_default;
// These are simply the string resource IDs for each of the style names. We use them
// as identifiers when choosing which style to apply.
private int mStyleIds[] = {
R.string.style_label_retro,
R.string.style_label_night,
R.string.style_label_grayscale,
R.string.style_label_no_pois_no_transit,
R.string.style_label_default,
};
private static final LatLng SYDNEY = new LatLng(-33.8688, 151.2093);
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
if (savedInstanceState != null) {
mSelectedStyleId = savedInstanceState.getInt(SELECTED_STYLE);
}
setContentView(R.layout.styled_map_demo);
SupportMapFragment mapFragment =
(SupportMapFragment) getSupportFragmentManager().findFragmentById(R.id.map);
mapFragment.getMapAsync(this);
}
#Override
public void onSaveInstanceState(Bundle outState) {
// Store the selected map style, so we can assign it when the activity resumes.
outState.putInt(SELECTED_STYLE, mSelectedStyleId);
super.onSaveInstanceState(outState);
}
#Override
public void onMapReady(GoogleMap map) {
mMap = map;
mMap.moveCamera(CameraUpdateFactory.newLatLngZoom(SYDNEY, 14));
setSelectedStyle();
}
#Override
public boolean onCreateOptionsMenu(Menu menu) {
getMenuInflater().inflate(R.menu.styled_map, menu);
return true;
}
#Override
public boolean onOptionsItemSelected(MenuItem item) {
if (item.getItemId() == R.id.menu_style_choose) {
showStylesDialog();
}
return true;
}
/**
* Shows a dialog listing the styles to choose from, and applies the selected
* style when chosen.
*/
private void showStylesDialog() {
// mStyleIds stores each style's resource ID, and we extract the names here, rather
// than using an XML array resource which AlertDialog.Builder.setItems() can also
// accept. We do this since using an array resource would mean we would not have
// constant values we can switch/case on, when choosing which style to apply.
List<String> styleNames = new ArrayList<>();
for (int style : mStyleIds) {
styleNames.add(getString(style));
}
AlertDialog.Builder builder = new AlertDialog.Builder(this);
builder.setTitle(getString(R.string.style_choose));
builder.setItems(styleNames.toArray(new CharSequence[styleNames.size()]),
new DialogInterface.OnClickListener() {
#Override
public void onClick(DialogInterface dialog, int which) {
mSelectedStyleId = mStyleIds[which];
String msg = getString(R.string.style_set_to, getString(mSelectedStyleId));
Toast.makeText(getBaseContext(), msg, Toast.LENGTH_SHORT).show();
Log.d(TAG, msg);
setSelectedStyle();
}
});
builder.show();
}
/**
* Creates a {#link MapStyleOptions} object via loadRawResourceStyle() (or via the
* constructor with a JSON String), then sets it on the {#link GoogleMap} instance,
* via the setMapStyle() method.
*/
private void setSelectedStyle() {
MapStyleOptions style;
switch (mSelectedStyleId) {
case R.string.style_label_retro:
// Sets the retro style via raw resource JSON.
style = MapStyleOptions.loadRawResourceStyle(this, R.raw.mapstyle_retro);
break;
case R.string.style_label_night:
// Sets the night style via raw resource JSON.
style = MapStyleOptions.loadRawResourceStyle(this, R.raw.mapstyle_night);
break;
case R.string.style_label_grayscale:
// Sets the grayscale style via raw resource JSON.
style = MapStyleOptions.loadRawResourceStyle(this, R.raw.mapstyle_grayscale);
break;
case R.string.style_label_no_pois_no_transit:
// Sets the no POIs or transit style via JSON string.
style = new MapStyleOptions("[" +
" {" +
" \"featureType\":\"poi.business\"," +
" \"elementType\":\"all\"," +
" \"stylers\":[" +
" {" +
" \"visibility\":\"off\"" +
" }" +
" ]" +
" }," +
" {" +
" \"featureType\":\"transit\"," +
" \"elementType\":\"all\"," +
" \"stylers\":[" +
" {" +
" \"visibility\":\"off\"" +
" }" +
" ]" +
" }" +
"]");
break;
case R.string.style_label_default:
// Removes previously set style, by setting it to null.
style = null;
break;
default:
return;
}
mMap.setMapStyle(style);
}
}
StyledMapDemoActivity.xml
<?xml version="1.0" encoding="utf-8"?>
<fragment xmlns:android="http://schemas.android.com/apk/res/android"
android:id="#+id/map"
android:layout_width="match_parent"
android:layout_height="match_parent"
class="com.google.android.gms.maps.SupportMapFragment" />
I don't understand the purpose of the aBaseUrl parameter in the class OnlineTileSourceBase. My reason for inquiring is that I am trying to get offline tiles to display and thus far can't get it to work. I see the overlay I created, but no map data (just that grey grid) and I wonder if I need to set aBaseUrl to something appropriate.
The data is on the device is in sdcard/osmdroid/tiles/Mapnik/.
Mapnik contains folders 0, 1, ... 14, which themselves contains folders which contain .jpg files.
Online, this code works (removing the call setUseDataConnection(false) and setting tile source to MAPNIK). Based on code by #nightfixed here.
public class MapActivity extends AppCompatActivity {
final private int MIN_ZOOM_LEVEL = 0;
final private int MAX_ZOOM_LEVEL = 14;
final private int TILE_SIZE = 256;
final private String IMAGE_EXTENSION = ".jpg";
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar);
setSupportActionBar(toolbar);
CustomTileSource tileSource = new CustomTileSource ("Default",
MIN_ZOOM_LEVEL,
MAX_ZOOM_LEVEL,
TILE_SIZE,
IMAGE_EXTENSION,
CustomTileSource.TILE_URL);
final MapView mapView = (MapView) findViewById(R.id.mapview);
mapView.setTileSource(tileSource);
// mapView.setTileSource(TileSourceFactory.MAPNIK);
mapView.setUseDataConnection(false); // keeps the mapView from loading online tiles using network connection.
}
}
public class CustomTileSource extends OnlineTileSourceBase {
public static String[] TILE_URL = {"my_url"};
public CustomTileSource (String aName,
int aZoomMinLevel,
int aZoomMaxLevel,
int aTileSizePixels,
String aImageFilenameEnding,
String[] urlArray) {
super(
aName,
aZoomMinLevel,
aZoomMaxLevel,
aTileSizePixels,
aImageFilenameEnding,
urlArray);
}
// returns the url for each tile, depending on zoom level
// this is where I changed the return statement to take the first url from the string array of urls
#Override
public String getTileURLString(MapTile aTile) {
return TILE_URL[0] + aTile.getX() + "+" + aTile.getY() + "+" + aTile.getZoomLevel();
}
}
I suggest you to follow closely this post: Download maps for osmdroid
No need for CustomTileSource, just use mapView.setTileSource(TileSourceFactory.MAPNIK);
If your tiles are in "Mapnik" dir (sdcard/osmdroid/tiles/Mapnik) then TileSource aName should be set to "Mapnik", not to "Default".
When offline, aBaseUrl doesn't matter.
aBaseUrl is basically the primary url to the online map server. For example
http://tiles.mymapserver.com/mapdata (note, complete fictitious)
After the aBaseUrl, osmdroid calculates with tiles to load via various algorithms and then appends something like /Z/X/Y.jpg to the end of the aBaseUrl string when downloading.
I have offline osmdroid maps working using version 4.0. Upgrading to 4.1, they no longer work. I have narrowed the problem down to the XYTileSource, in which aBaseUrl changed from being a string in 4.0 to and array in 4.1. How do I get offline tiles to work in 4.1?
Old 4.0 code that worked. The tiles are in /sdcard/osmdroid/tiles.zip
XYTileSource ts = new XYTileSource ( "tiles",
ResourceProxy.string.offline_mode,
13,
17,
256,
".png",
"http://127.0.0.1");
mapView = (MapView) findViewById(R.id.mapview);
mapView.setTileSource(ts);
mapView.setMultiTouchControls(true);
mapView.setBuiltInZoomControls(false);
mapView.setUseDataConnection(false);
mapView.getController().setZoom(15);
GeoPoint point = new GeoPoint(40.715,-73.945);
mapView.getController().setCenter(point);
I tried changing it to this, but it doesn't work.
String[] urls = {"http://127.0.0.1"};
XYTileSource ts = new XYTileSource ( "tiles",
ResourceProxy.string.offline_mode,
13,
17,
256,
".png",
urls);
I tried to provide a full answer here:
Download maps for osmdroid
If you have an "old" tiles.zip, open it, and check:
the root directory name => put it as the "aName" of XYTileSource constructor (is it really "tiles"?)
the tiles images extension => put it as the aImageFileNameEnding (is it really ".png"?)
The aResourceId and aBaseUrl params are not used for zip files.
I see that you are using XYTileSource, which by default extends OnlineTileSourceBase.
I have found a workaround for the Url issue, by creating a CustomTileSource class. Something like below:
public class CustomTileSource extends OnlineTileSourceBase {
public static String[] TILE_URL = {"my_url"};
//constructor is default - I changed nothing here
public CustomTileSource (String aName, string aResourceId, int aZoomMinLevel, int aZoomMaxLevel,
int aTileSizePixels, String aImageFilenameEnding, String[] url) {
super(
aName,
aResourceId,
aZoomMinLevel,
aZoomMaxLevel,
aTileSizePixels,
aImageFilenameEnding,
url);
// TODO Auto-generated constructor stub
}
/**
* returns the url for each tile, depending on zoom level
*/
//this is where I changed the return statement to take the first url from the string array of urls
#Override
public String getTileURLString(MapTile aTile) {
return TILE_URL[0] + aTile.getX() + "+" + aTile.getY() + "+" + aTile.getZoomLevel();
}
}
In my code, where I need to instantiate the tilesource, I use:
CustomTileSource tileSource = new CustomTileSource ("Default", ResourceProxy.string.offline_mode, MIN_ZOOM_LVL, MAX_ZOOM_LVL, DEFAULT_TILE_SIZE, TILE_FORMAT, CustomTileSource.TILE_URL);
//MIN_ZOOM_LVL, MAX_ZOOM_LVL, DEFAULT_TILE_SIZE, TILE_FORMAT are constants that I defined elsewhere
Hope it helps.
I have a onMarkerClickListener on my google map which fires when a marker is pressed as it should.
the markers are created in a Tag class that creates the marker within itself on a map that is passed though:
instance of the list at the start of the map activity:
//tags
List<Tag> tags = new ArrayList<Tag>();
In the onCreate() of the activity that contains the googleMap I add the markers to a list:
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
....other code here
//add the markers to the map
tags.add(new Tag(map,1,"City Of Dundee", "Home Of The Jakey", DUNDEE, this.getResources(),R.drawable.ic_launcher));
tags.add(new Tag(map,2,"Some Place","This is some place",LOCATION_Z,this.getResources(),R.drawable.ic_launcher));
....other code here
}//end on create
Constructor for the Tag class:
public Tag(GoogleMap map, int atagID,String tagTitle, String tagSnippet, LatLng tagPosition, Resources resource, int id){
this.tagID = atagID;
this.position = tagPosition;
this.title = tagTitle;
this.snippet = tagSnippet;
this.icon = BitmapDescriptorFactory.fromResource(id);
this.theTag = map.addMarker(new MarkerOptions()
.position(tagPosition)
.title(tagTitle)
.snippet(tagSnippet)
.icon(icon));
}
This creates the tag and it display on the map properly
In the listener for the onMarkerClickedListener i compare the marker clicked on the map to the marker from the list but the if statement never passes, even when I compare the titles which are identical.
The Listener:
onMarkerClickListener = new OnMarkerClickListener(){
#Override
public boolean onMarkerClick(Marker marker) {
//for loop that goes over array or marker
//if marker is equal to mark in array
//do marker functionality
for(Tag currentTag : tags){
if(currentTag.theTag == marker){
switch(currentTag.tagID){
case 1:
//do something for that button
Toast.makeText(getApplicationContext(), "marker 1", Toast.LENGTH_SHORT).show();
return true;
case 2:
Toast.makeText(getApplicationContext(), "marker 2", Toast.LENGTH_SHORT).show();
return false;
default:
Toast.makeText(getApplicationContext(), "default", Toast.LENGTH_SHORT).show();
return false;
}
}else{
Toast.makeText(getApplicationContext(), "theTag " + currentTag.tagID + ": " + currentTag.theTag.getTitle(), Toast.LENGTH_SHORT).show();
Toast.makeText(getApplicationContext(), "marker: " + marker.getTitle(), Toast.LENGTH_SHORT).show();
}
}
return false;
}
};
I hove no idea why it never reaches the switch statement any ideas?
I found the documentation i was looking for, don't know how i missed it.
As I have read you can't compare a marker using the '==' but you can using
if(markerA.equals(markerB)
{
}
Like so:
if(theTag.equals(marker){
//it will compare properly this way instead of returning false every time
}
Reference to website:
https://developers.google.com/maps/documentation/android/marker
You do not need the for loop in your code. When you do the equality test on the markers(using ==) it will return false, and never reach your switch. Instead try removing the for loop and just us.
switch(marker.getId()){
case 1:
//do something for that button
Toast.makeText(getApplicationContext(), "marker 1", Toast.LENGTH_SHORT).show();
return true;
case 2:
Toast.makeText(getApplicationContext(), "marker 2", Toast.LENGTH_SHORT).show();
return false;
default:
Toast.makeText(getApplicationContext(), "default", Toast.LENGTH_SHORT).show();
return false;
}
}else{
Toast.makeText(getApplicationContext(), "theTag " + currentTag.tagID + ": " + currentTag.theTag.getTitle(), Toast.LENGTH_SHORT).show();
Toast.makeText(getApplicationContext(), "marker: " + marker.getTitle(), Toast.LENGTH_SHORT).show();
}
Expanding on #MaciejGórski comment from the selected answer I would suggest using the ID of the marker (which is oddly a string).
This helps you avoid keeping hard references to Markers which themselves have references to the GoogleMap object, and thus the entire MapView/MapFragment/SupportMapFragment
In my class I have:
private Map< String, MyObject > markersMap = new HashMap< String, MyObject >();
Then when I create the markers I add them to the map:
markersMap.put( marker.getId(), myObj );
Then in the listener I am able to retrieve myObj like this
#Override
public void onInfoWindowClick( Marker m )
{
Log.v( TAG, "onInfoWindowClick m.getId = " + m.getId() );
MyObject myObj = markersMap.get( m.getId() );
Log.v( TAG, "onInfoWindowClick myObj = " + myObj );
}
This way I avoid leaking the Views/Activity/Map etc
The == operator will compare the direction of memory of both objects (which are not the same, since are different instances) instead of the object properties.
the equals method, will compare the value of the attributes of your object or if you override the equals you can compare what ever you want, which will be more suitable to you.