package net.osmand.plus.rastermaps;
import android.app.Activity;
import android.content.DialogInterface;
import android.os.AsyncTask;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.support.v7.app.AlertDialog;
import android.view.View;
import android.widget.AdapterView;
import android.widget.AdapterView.OnItemSelectedListener;
import android.widget.ArrayAdapter;
import android.widget.CheckBox;
import android.widget.EditText;
import android.widget.Spinner;
import android.widget.Toast;
import net.osmand.IndexConstants;
import net.osmand.ResultMatcher;
import net.osmand.StateChangedListener;
import net.osmand.map.ITileSource;
import net.osmand.map.TileSourceManager;
import net.osmand.map.TileSourceManager.TileSourceTemplate;
import net.osmand.plus.ContextMenuAdapter;
import net.osmand.plus.ContextMenuAdapter.ItemClickListener;
import net.osmand.plus.ContextMenuItem;
import net.osmand.plus.OsmandApplication;
import net.osmand.plus.OsmandPlugin;
import net.osmand.plus.OsmandSettings;
import net.osmand.plus.OsmandSettings.CommonPreference;
import net.osmand.plus.OsmandSettings.LayerTransparencySeekbarMode;
import net.osmand.plus.R;
import net.osmand.plus.Version;
import net.osmand.plus.activities.DownloadTilesDialog;
import net.osmand.plus.activities.MapActivity;
import net.osmand.plus.activities.MapActivityLayers;
import net.osmand.plus.dashboard.DashboardOnMap.DashboardType;
import net.osmand.plus.dialogs.RasterMapMenu;
import net.osmand.plus.views.MapTileLayer;
import net.osmand.plus.views.OsmandMapTileView;
import net.osmand.util.Algorithms;
import java.io.File;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
public class OsmandRasterMapsPlugin extends OsmandPlugin {
public static final String ID = "osmand.rastermaps";
private OsmandSettings settings;
private OsmandApplication app;
private MapTileLayer overlayLayer;
private MapTileLayer underlayLayer;
private StateChangedListener<Integer> overlayLayerListener;
public OsmandRasterMapsPlugin(OsmandApplication app) {
this.app = app;
settings = app.getSettings();
}
@Override
public int getLogoResourceId() {
return R.drawable.ic_world_globe_dark;
}
@Override
public int getAssetResourceName() {
return R.drawable.online_maps;
}
@Override
public String getId() {
return ID;
}
@Override
public String getDescription() {
return app.getString(R.string.osmand_rastermaps_plugin_description);
}
@Override
public String getName() {
return app.getString(R.string.shared_string_online_maps);
}
@Override
public String getHelpFileName() {
return "feature_articles/online-maps-plugin.html";
}
@Override
public void registerLayers(MapActivity activity) {
createLayers();
}
private void createLayers() {
underlayLayer = new MapTileLayer(false);
// mapView.addLayer(underlayLayer, -0.5f);
overlayLayer = new MapTileLayer(false);
overlayLayerListener = new StateChangedListener<Integer>() {
@Override
public void stateChanged(Integer change) {
overlayLayer.setAlpha(change);
}
};
// mapView.addLayer(overlayLayer, 0.7f);
settings.MAP_OVERLAY_TRANSPARENCY.addListener(overlayLayerListener);
}
@Override
public void updateLayers(OsmandMapTileView mapView, MapActivity activity) {
updateMapLayers(mapView, null, activity.getMapLayers());
}
public void updateMapLayers(OsmandMapTileView mapView, CommonPreference<String> settingsToWarnAboutMap,
final MapActivityLayers layers) {
if (overlayLayer == null) {
createLayers();
}
overlayLayer.setAlpha(settings.MAP_OVERLAY_TRANSPARENCY.get());
if (isActive()) {
updateLayer(mapView, settings, overlayLayer, settings.MAP_OVERLAY, 0.7f, settings.MAP_OVERLAY == settingsToWarnAboutMap);
} else {
mapView.removeLayer(overlayLayer);
overlayLayer.setMap(null);
}
if (isActive()) {
updateLayer(mapView, settings, underlayLayer, settings.MAP_UNDERLAY, -0.5f, settings.MAP_UNDERLAY == settingsToWarnAboutMap);
} else {
mapView.removeLayer(underlayLayer);
underlayLayer.setMap(null);
}
layers.updateMapSource(mapView, settingsToWarnAboutMap);
}
public void updateLayer(OsmandMapTileView mapView, OsmandSettings settings,
MapTileLayer layer, CommonPreference<String> preference, float layerOrder, boolean warnWhenSelected) {
ITileSource overlay = settings.getTileSourceByName(preference.get(), warnWhenSelected);
if (!Algorithms.objectEquals(overlay, layer.getMap())) {
if (overlay == null) {
mapView.removeLayer(layer);
} else if (mapView.getMapRenderer() == null) {
mapView.addLayer(layer, layerOrder);
}
layer.setMap(overlay);
mapView.refreshMap();
}
}
public void selectMapOverlayLayer(@NonNull final OsmandMapTileView mapView,
@NonNull final CommonPreference<String> mapPref,
@NonNull final CommonPreference<String> exMapPref,
boolean force,
@NonNull final MapActivity activity,
@Nullable final OnMapSelectedCallback callback) {
final MapActivityLayers layers = activity.getMapLayers();
if (!force && exMapPref.get() != null) {
mapPref.set(exMapPref.get());
if (callback != null) {
callback.onMapSelected(false);
}
updateMapLayers(mapView, mapPref, layers);
return;
}
final OsmandSettings settings = app.getSettings();
Map<String, String> entriesMap = settings.getTileSourceEntries();
final ArrayList<String> keys = new ArrayList<>(entriesMap.keySet());
AlertDialog.Builder builder = new AlertDialog.Builder(activity);
final String[] items = new String[entriesMap.size() + 1];
int i = 0;
for (String it : entriesMap.values()) {
items[i++] = it;
}
items[i] = app.getString(R.string.install_more);
builder.setSingleChoiceItems(items, -1, new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
if (which == items.length - 1) {
installMapLayers(activity, new ResultMatcher<TileSourceTemplate>() {
TileSourceTemplate template = null;
int count = 0;
@Override
public boolean publish(TileSourceTemplate object) {
if (object == null) {
if (count == 1) {
mapPref.set(template.getName());
exMapPref.set(template.getName());
if (callback != null) {
callback.onMapSelected(false);
}
updateMapLayers(mapView, mapPref, layers);
} else {
selectMapOverlayLayer(mapView, mapPref, exMapPref, false, activity, null);
}
} else {
count++;
template = object;
}
return false;
}
@Override
public boolean isCancelled() {
return false;
}
});
} else {
mapPref.set(keys.get(which));
exMapPref.set(keys.get(which));
if (callback != null) {
callback.onMapSelected(false);
}
updateMapLayers(mapView, mapPref, layers);
}
dialog.dismiss();
}
})
.setNegativeButton(R.string.shared_string_cancel, null)
.setOnDismissListener(new DialogInterface.OnDismissListener() {
@Override
public void onDismiss(DialogInterface dialog) {
if (callback != null) {
callback.onMapSelected(true);
}
}
});
builder.show();
}
@Override
public void registerLayerContextMenuActions(final OsmandMapTileView mapView,
ContextMenuAdapter adapter,
final MapActivity mapActivity) {
ContextMenuAdapter.ItemClickListener listener = new ContextMenuAdapter.OnRowItemClick() {
@Override
public boolean onRowItemClick(ArrayAdapter<ContextMenuItem> adapter, View view, int itemId, int position) {
if (itemId == R.string.layer_overlay) {
mapActivity.getDashboard().setDashboardVisibility(true, DashboardType.OVERLAY_MAP);
return false;
} else if (itemId == R.string.layer_underlay) {
mapActivity.getDashboard().setDashboardVisibility(true, DashboardType.UNDERLAY_MAP);
return false;
}
return true;
}
@Override
public boolean onContextMenuClick(final ArrayAdapter<ContextMenuItem> adapter, int itemId, final int pos, boolean isChecked) {
final OsmandSettings settings = mapActivity.getMyApplication().getSettings();
switch (itemId) {
case R.string.layer_overlay:
toggleUnderlayState(mapActivity, RasterMapType.OVERLAY,
new OnMapSelectedCallback() {
@Override
public void onMapSelected(boolean canceled) {
ContextMenuItem item = adapter.getItem(pos);
String overlayMapDescr = settings.MAP_OVERLAY.get();
boolean hasOverlayDescription = overlayMapDescr != null;
overlayMapDescr = hasOverlayDescription ? overlayMapDescr
: mapActivity.getString(R.string.shared_string_none);
item.setDescription(overlayMapDescr);
item.setSelected(hasOverlayDescription);
item.setColorRes(hasOverlayDescription ? R.color.osmand_orange : ContextMenuItem.INVALID_ID);
adapter.notifyDataSetChanged();
}
});
return false;
case R.string.layer_underlay:
toggleUnderlayState(mapActivity, RasterMapType.UNDERLAY,
new OnMapSelectedCallback() {
@Override
public void onMapSelected(boolean canceled) {
ContextMenuItem item = adapter.getItem(pos);
String underlayMapDescr = settings.MAP_UNDERLAY.get();
boolean hasUnderlayDescription = underlayMapDescr != null;
underlayMapDescr = hasUnderlayDescription ? underlayMapDescr
: mapActivity.getString(R.string.shared_string_none);
item.setDescription(underlayMapDescr);
item.setSelected(hasUnderlayDescription);
item.setColorRes(hasUnderlayDescription ? R.color.osmand_orange : ContextMenuItem.INVALID_ID);
adapter.notifyDataSetChanged();
final OsmandSettings.CommonPreference<Boolean> hidePolygonsPref =
mapActivity.getMyApplication().getSettings().getCustomRenderBooleanProperty("noPolygons");
hidePolygonsPref.set(hasUnderlayDescription);
RasterMapMenu.refreshMapComplete(mapActivity);
}
});
return false;
}
return true;
}
};
if (overlayLayer.getMap() == null) {
settings.MAP_OVERLAY.set(null);
settings.MAP_OVERLAY_PREVIOUS.set(null);
}
if (underlayLayer.getMap() == null) {
settings.MAP_UNDERLAY.set(null);
settings.MAP_UNDERLAY_PREVIOUS.set(null);
}
String overlayMapDescr = settings.MAP_OVERLAY.get();
boolean hasOverlayDescription = overlayMapDescr != null;
overlayMapDescr = hasOverlayDescription ? overlayMapDescr : mapActivity.getString(R.string.shared_string_none);
adapter.addItem(new ContextMenuItem.ItemBuilder().setTitleId(R.string.layer_overlay, mapActivity)
.setDescription(overlayMapDescr)
.setSelected(hasOverlayDescription)
.setColor(hasOverlayDescription ? R.color.osmand_orange : ContextMenuItem.INVALID_ID)
.setIcon(R.drawable.ic_layer_top_dark)
.setSecondaryIcon(R.drawable.ic_action_additional_option)
.setListener(listener)
.setPosition(14)
.createItem());
String underlayMapDescr = settings.MAP_UNDERLAY.get();
boolean hasUnderlayDescription = underlayMapDescr != null;
underlayMapDescr = hasUnderlayDescription ? underlayMapDescr : mapActivity.getString(R.string.shared_string_none);
adapter.addItem(new ContextMenuItem.ItemBuilder().setTitleId(R.string.layer_underlay, mapActivity)
.setDescription(underlayMapDescr)
.setSelected(hasUnderlayDescription)
.setColor(hasUnderlayDescription ? R.color.osmand_orange : ContextMenuItem.INVALID_ID)
.setIcon(R.drawable.ic_layer_bottom_dark)
.setSecondaryIcon(R.drawable.ic_action_additional_option)
.setListener(listener)
.setPosition(15)
.createItem());
}
@Override
public void registerMapContextMenuActions(final MapActivity mapActivity, final double latitude, final double longitude, ContextMenuAdapter adapter,
Object selectedObj) {
final OsmandMapTileView mapView = mapActivity.getMapView();
if (mapView.getMainLayer() instanceof MapTileLayer) {
ItemClickListener listener = new ContextMenuAdapter.ItemClickListener() {
@Override
public boolean onContextMenuClick(ArrayAdapter<ContextMenuItem> adapter, int resId, int pos, boolean isChecked) {
if (resId == R.string.context_menu_item_update_map) {
mapActivity.getMapActions().reloadTile(mapView.getZoom(), latitude, longitude);
} else if (resId == R.string.shared_string_download_map) {
DownloadTilesDialog dlg = new DownloadTilesDialog(mapActivity, (OsmandApplication) mapActivity.getApplication(), mapView);
dlg.openDialog();
}
return true;
}
};
adapter.addItem(new ContextMenuItem.ItemBuilder()
.setTitleId(R.string.context_menu_item_update_map, mapActivity)
.setIcon(R.drawable.ic_action_refresh_dark)
.setListener(listener).createItem());
adapter.addItem(new ContextMenuItem.ItemBuilder()
.setTitleId(R.string.shared_string_download_map, mapActivity)
.setIcon(R.drawable.ic_action_import)
.setListener(listener).createItem());
}
}
@Override
public Class<? extends Activity> getSettingsActivity() {
return SettingsRasterMapsActivity.class;
}
public static void installMapLayers(final Activity activity, final ResultMatcher<TileSourceTemplate> result) {
final OsmandApplication app = (OsmandApplication) activity.getApplication();
final OsmandSettings settings = app.getSettings();
final Map<String, String> entriesMap = settings.getTileSourceEntries();
if (!settings.isInternetConnectionAvailable(true)) {
Toast.makeText(activity, R.string.internet_not_available, Toast.LENGTH_LONG).show();
return;
}
AsyncTask<Void, Void, List<TileSourceTemplate>> t = new AsyncTask<Void, Void, List<TileSourceTemplate>>() {
@Override
protected List<TileSourceTemplate> doInBackground(Void... params) {
return TileSourceManager.downloadTileSourceTemplates(Version.getVersionAsURLParam(app));
}
protected void onPostExecute(final java.util.List<TileSourceTemplate> downloaded) {
if (downloaded == null || downloaded.isEmpty()) {
Toast.makeText(activity, R.string.shared_string_io_error, Toast.LENGTH_SHORT).show();
return;
}
AlertDialog.Builder builder = new AlertDialog.Builder(activity);
String[] names = new String[downloaded.size()];
for (int i = 0; i < names.length; i++) {
names[i] = downloaded.get(i).getName();
}
final boolean[] selected = new boolean[downloaded.size()];
builder.setMultiChoiceItems(names, selected, new DialogInterface.OnMultiChoiceClickListener() {
@Override
public void onClick(DialogInterface dialog, int which, boolean isChecked) {
selected[which] = isChecked;
if (entriesMap.containsKey(downloaded.get(which).getName()) && isChecked) {
Toast.makeText(activity, R.string.tile_source_already_installed, Toast.LENGTH_SHORT).show();
}
}
});
builder.setNegativeButton(R.string.shared_string_cancel, null);
builder.setTitle(R.string.select_tile_source_to_install);
builder.setPositiveButton(R.string.shared_string_apply, new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
List<TileSourceTemplate> toInstall = new ArrayList<>();
for (int i = 0; i < selected.length; i++) {
if (selected[i]) {
toInstall.add(downloaded.get(i));
}
}
for (TileSourceTemplate ts : toInstall) {
if (settings.installTileSource(ts)) {
if (result != null) {
result.publish(ts);
}
}
}
// at the end publish null to show end of process
if (!toInstall.isEmpty() && result != null) {
result.publish(null);
}
}
});
builder.show();
}
};
t.execute();
}
public static void defineNewEditLayer(final Activity activity, final ResultMatcher<TileSourceTemplate> resultMatcher) {
final OsmandApplication app = (OsmandApplication) activity.getApplication();
final OsmandSettings settings = app.getSettings();
final Map<String, String> entriesMap = settings.getTileSourceEntries(false);
TileSourceTemplate ts = new TileSourceTemplate("NewMapnik", "http://mapnik.osmand.net/{0}/{1}/{2}.png",
"png", 17, 5, 256, 16, 32000);
final TileSourceTemplate[] result = new TileSourceTemplate[]{ts};
AlertDialog.Builder bld = new AlertDialog.Builder(activity);
View view = activity.getLayoutInflater().inflate(R.layout.editing_tile_source, null);
final EditText name = (EditText) view.findViewById(R.id.Name);
final Spinner existing = (Spinner) view.findViewById(R.id.TileSourceSpinner);
final EditText urlToLoad = (EditText) view.findViewById(R.id.URLToLoad);
final EditText minZoom = (EditText) view.findViewById(R.id.MinZoom);
final EditText maxZoom = (EditText) view.findViewById(R.id.MaxZoom);
final EditText expire = (EditText) view.findViewById(R.id.ExpirationTime);
final CheckBox elliptic = (CheckBox) view.findViewById(R.id.EllipticMercator);
updateTileSourceEditView(ts, name, urlToLoad, minZoom, maxZoom, expire, elliptic);
final ArrayList<String> templates = new ArrayList<>(entriesMap.keySet());
templates.add(0, "");
ArrayAdapter<String> adapter = new ArrayAdapter<>(view.getContext(),
android.R.layout.simple_spinner_item,
templates
);
adapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
existing.setAdapter(adapter);
existing.setSelection(0);
existing.setOnItemSelectedListener(new OnItemSelectedListener() {
@Override
public void onItemSelected(AdapterView<?> parent, View view, int position, long id) {
if (position > 0) {
File f = ((OsmandApplication) activity.getApplication()).getAppPath(IndexConstants.TILES_INDEX_DIR + templates.get(position));
TileSourceTemplate template = TileSourceManager.createTileSourceTemplate(f);
if (template != null) {
result[0] = template.copy();
updateTileSourceEditView(result[0], name, urlToLoad, minZoom, maxZoom, expire, elliptic);
}
}
}
@Override
public void onNothingSelected(AdapterView<?> parent) {
}
});
bld.setView(view);
bld.setPositiveButton(R.string.shared_string_save, new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
TileSourceTemplate r = result[0];
try {
r.setName(name.getText().toString());
r.setExpirationTimeMinutes(expire.getText().length() == 0 ? -1 :
Integer.parseInt(expire.getText().toString()));
r.setMinZoom(Integer.parseInt(minZoom.getText().toString()));
r.setMaxZoom(Integer.parseInt(maxZoom.getText().toString()));
r.setEllipticYTile(elliptic.isChecked());
r.setUrlToLoad(urlToLoad.getText().toString().equals("") ? null : urlToLoad.getText().toString().replace("{$x}", "{1}")
.replace("{$y}", "{2}").replace("{$z}", "{0}"));
if (r.getName().length() > 0) {
if (settings.installTileSource(r)) {
Toast.makeText(activity, activity.getString(R.string.edit_tilesource_successfully, r.getName()),
Toast.LENGTH_SHORT).show();
resultMatcher.publish(r);
}
}
} catch (RuntimeException e) {
Toast.makeText(activity, e.getMessage(), Toast.LENGTH_SHORT).show();
}
}
});
bld.setNegativeButton(R.string.shared_string_cancel, null);
bld.show();
}
private static void updateTileSourceEditView(TileSourceTemplate ts, EditText name, final EditText urlToLoad, final EditText minZoom,
final EditText maxZoom, EditText expire, final CheckBox elliptic) {
minZoom.setText(String.valueOf(ts.getMinimumZoomSupported()));
maxZoom.setText(String.valueOf(ts.getMaximumZoomSupported()));
name.setText(ts.getName());
expire.setText(ts.getExpirationTimeMinutes() < 0 ? "" : ts.getExpirationTimeMinutes() + "");
urlToLoad.setText(ts.getUrlTemplate() == null ? "" :
ts.getUrlTemplate().replace("{$x}", "{1}").replace("{$y}", "{2}").replace("{$z}", "{0}"));
elliptic.setChecked(ts.isEllipticYTile());
}
public MapTileLayer getUnderlayLayer() {
return underlayLayer;
}
public MapTileLayer getOverlayLayer() {
return overlayLayer;
}
public void toggleUnderlayState(@NonNull MapActivity mapActivity,
@NonNull RasterMapType type,
@Nullable OnMapSelectedCallback callback) {
OsmandMapTileView mapView = mapActivity.getMapView();
CommonPreference<String> mapTypePreference;
CommonPreference<String> exMapTypePreference;
OsmandSettings.CommonPreference<Integer> mapTransparencyPreference;
ITileSource map;
if (type == RasterMapType.OVERLAY) {
mapTransparencyPreference = settings.MAP_OVERLAY_TRANSPARENCY;
mapTypePreference = settings.MAP_OVERLAY;
exMapTypePreference = settings.MAP_OVERLAY_PREVIOUS;
map = overlayLayer.getMap();
} else {
// Underlay expected
mapTransparencyPreference = settings.MAP_TRANSPARENCY;
mapTypePreference = settings.MAP_UNDERLAY;
exMapTypePreference = settings.MAP_UNDERLAY_PREVIOUS;
map = underlayLayer.getMap();
}
boolean isChecked = map == null;
boolean showSeekbar = isChecked && RasterMapMenu.isSeekbarVisible(app, type);
boolean hideSeekbar = !isChecked && RasterMapMenu.isSeekbarVisible(app, type);
MapActivityLayers mapLayers = mapActivity.getMapLayers();
CommonPreference<LayerTransparencySeekbarMode> seekbarModePref = settings.LAYER_TRANSPARENCY_SEEKBAR_MODE;
if (showSeekbar) {
mapLayers.getMapControlsLayer().showTransparencyBar(mapTransparencyPreference);
mapLayers.getMapControlsLayer().setTransparencyBarEnabled(true);
if (seekbarModePref.get() == LayerTransparencySeekbarMode.UNDEFINED) {
final OsmandSettings.LayerTransparencySeekbarMode currentMapTypeSeekbarMode =
type == OsmandRasterMapsPlugin.RasterMapType.OVERLAY ? OsmandSettings.LayerTransparencySeekbarMode.OVERLAY : OsmandSettings.LayerTransparencySeekbarMode.UNDERLAY;
seekbarModePref.set(currentMapTypeSeekbarMode);
}
} else if (hideSeekbar) {
mapLayers.getMapControlsLayer().hideTransparencyBar(mapTransparencyPreference);
mapLayers.getMapControlsLayer().setTransparencyBarEnabled(false);
}
if (map != null) {
mapTypePreference.set(null);
if (callback != null) {
callback.onMapSelected(false);
}
updateMapLayers(mapView, null, mapLayers);
} else {
selectMapOverlayLayer(mapView, mapTypePreference, exMapTypePreference, false, mapActivity, callback);
}
}
public enum RasterMapType {
OVERLAY,
UNDERLAY
}
public interface OnMapSelectedCallback {
void onMapSelected(boolean canceled);
}
}