package net.osmand.plus.views;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.net.URL;
import java.net.URLConnection;
import java.net.URLEncoder;
import java.util.ArrayList;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import net.osmand.LogUtil;
import net.osmand.osm.LatLon;
import net.osmand.plus.OsmandSettings;
import net.osmand.plus.R;
import org.apache.commons.logging.Log;
import android.app.Activity;
import android.app.AlertDialog;
import android.app.AlertDialog.Builder;
import android.content.Context;
import android.content.DialogInterface;
import android.content.DialogInterface.OnClickListener;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.PointF;
import android.graphics.RectF;
import android.os.Handler;
import android.os.Looper;
import android.os.Message;
import android.util.DisplayMetrics;
import android.view.LayoutInflater;
import android.view.View;
import android.view.WindowManager;
import android.widget.EditText;
import android.widget.Toast;
public class OsmBugsLayer implements OsmandMapLayer, ContextMenuLayer.IContextMenuProvider {
private static final Log log = LogUtil.getLog(OsmBugsLayer.class);
private final static int startZoom = 8;
private final int SEARCH_LIMIT = 100;
private OsmandMapTileView view;
private Handler handlerToLoop;
private List<OpenStreetBug> objects = new ArrayList<OpenStreetBug>();
private Paint pointClosedUI;
private Paint pointOpenedUI;
private Pattern patternToParse = Pattern.compile("putAJAXMarker\\((\\d*), (-?(?:\\d|\\.)+), (-?(?:\\d|\\.)+), '([^']*)', (\\d)\\);"); //$NON-NLS-1$
// private SimpleDateFormat dateFormat = new SimpleDateFormat("dd.MM.yyyy hh:mm aaa", Locale.US); //$NON-NLS-1$
private double cTopLatitude;
private double cBottomLatitude;
private double cLeftLongitude;
private double cRightLongitude;
private int czoom;
private final Activity activity;
private DisplayMetrics dm;
public OsmBugsLayer(Activity activity){
this.activity = activity;
}
@Override
public void initLayer(OsmandMapTileView view) {
this.view = view;
dm = new DisplayMetrics();
WindowManager wmgr = (WindowManager) view.getContext().getSystemService(Context.WINDOW_SERVICE);
wmgr.getDefaultDisplay().getMetrics(dm);
synchronized (this) {
if (handlerToLoop == null) {
new Thread("Open street bugs layer") { //$NON-NLS-1$
@Override
public void run() {
Looper.prepare();
handlerToLoop = new Handler();
Looper.loop();
}
}.start();
}
}
pointOpenedUI = new Paint();
pointOpenedUI.setColor(Color.RED);
pointOpenedUI.setAlpha(200);
pointOpenedUI.setAntiAlias(true);
pointClosedUI = new Paint();
pointClosedUI.setColor(Color.GREEN);
pointClosedUI.setAlpha(200);
pointClosedUI.setAntiAlias(true);
}
@Override
public void destroyLayer() {
synchronized (this) {
if(handlerToLoop != null){
handlerToLoop.post(new Runnable(){
@Override
public void run() {
Looper.myLooper().quit();
}
});
handlerToLoop = null;
}
}
}
@Override
public boolean drawInScreenPixels() {
return false;
}
@Override
public void onDraw(Canvas canvas, RectF latLonBounds, boolean nightMode) {
if (view.getZoom() >= startZoom) {
// request to load
requestToLoad(latLonBounds.top, latLonBounds.left, latLonBounds.bottom, latLonBounds.right, view.getZoom());
for (OpenStreetBug o : objects) {
int x = view.getMapXForPoint(o.getLongitude());
int y = view.getMapYForPoint(o.getLatitude());
canvas.drawCircle(x, y, getRadiusBug(view.getZoom()), o.isOpened()? pointOpenedUI: pointClosedUI);
}
}
}
public int getRadiusBug(int zoom) {
int z;
if (zoom < startZoom) {
z = 0;
} else if (zoom <= 12) {
z = 8;
} else if (zoom <= 15) {
z = 10;
} else if (zoom == 16) {
z = 13;
} else if (zoom == 17) {
z = 15;
} else {
z = 16;
}
return (int) (z * dm.density);
}
public void requestToLoad(double topLatitude, double leftLongitude, double bottomLatitude,double rightLongitude, final int zoom){
boolean inside = cTopLatitude >= topLatitude && cLeftLongitude <= leftLongitude && cRightLongitude >= rightLongitude
&& cBottomLatitude <= bottomLatitude;
if((!inside || (czoom != zoom && objects.size() >= SEARCH_LIMIT)) && handlerToLoop != null){
handlerToLoop.removeMessages(1);
final double nTopLatitude = topLatitude + (topLatitude -bottomLatitude);
final double nBottomLatitude = bottomLatitude - (topLatitude -bottomLatitude);
final double nLeftLongitude = leftLongitude - (rightLongitude - leftLongitude);
final double nRightLongitude = rightLongitude + (rightLongitude - leftLongitude);
Message msg = Message.obtain(handlerToLoop, new Runnable() {
@Override
public void run() {
if(handlerToLoop != null && !handlerToLoop.hasMessages(1)){
boolean inside = cTopLatitude >= nTopLatitude && cLeftLongitude <= nLeftLongitude && cRightLongitude >= nRightLongitude
&& cBottomLatitude <= nBottomLatitude;
if (!inside || czoom != zoom) {
objects = loadingBugs(nTopLatitude, nLeftLongitude, nBottomLatitude, nRightLongitude);
cTopLatitude = nTopLatitude;
cLeftLongitude = nLeftLongitude;
cRightLongitude = nRightLongitude;
cBottomLatitude = nBottomLatitude;
czoom = zoom;
view.refreshMap();
}
}
}
});
msg.what = 1;
handlerToLoop.sendMessage(msg);
}
}
@Override
public boolean onLongPressEvent(PointF point) {
return false;
}
public OpenStreetBug getBugFromPoint(PointF point){
OpenStreetBug result = null;
if (objects != null) {
int ex = (int) point.x;
int ey = (int) point.y;
int radius = getRadiusBug(view.getZoom()) * 3 / 2;
try {
for (int i = 0; i < objects.size(); i++) {
OpenStreetBug n = objects.get(i);
int x = view.getRotatedMapXForPoint(n.getLatitude(), n.getLongitude());
int y = view.getRotatedMapYForPoint(n.getLatitude(), n.getLongitude());
if (Math.abs(x - ex) <= radius && Math.abs(y - ey) <= radius) {
radius = Math.max(Math.abs(x - ex), Math.abs(y - ey));
result = n;
}
}
} catch (IndexOutOfBoundsException e) {
// that's really rare case, but is much efficient than introduce synchronized block
}
}
return result;
}
@Override
public boolean onTouchEvent(PointF point) {
OpenStreetBug bug = getBugFromPoint(point);
if(bug != null){
String format = view.getContext().getString(R.string.osb_bug_name)+ " : " + bug.getName(); //$NON-NLS-1$
Toast.makeText(view.getContext(), format, Toast.LENGTH_LONG).show();
return true;
}
return false;
}
public void clearCache() {
objects.clear();
cTopLatitude = 0;
cBottomLatitude = 0;
cLeftLongitude = 0;
cRightLongitude = 0;
}
public boolean createNewBug(double latitude, double longitude, String text, String authorName){
StringBuilder b = new StringBuilder();
b.append("http://openstreetbugs.schokokeks.org/api/0.1/addPOIexec?"); //$NON-NLS-1$
b.append("lat=").append(latitude); //$NON-NLS-1$
b.append("&lon=").append(longitude); //$NON-NLS-1$
text = text + " [" + authorName + "]"; //$NON-NLS-1$ //$NON-NLS-2$
b.append("&text=").append(URLEncoder.encode(text)); //$NON-NLS-1$
b.append("&name=").append(URLEncoder.encode(authorName)); //$NON-NLS-1$
return editingPOI(b.toString(), "creating bug"); //$NON-NLS-1$
}
public boolean addingComment(long id, String text, String authorName){
StringBuilder b = new StringBuilder();
b.append("http://openstreetbugs.schokokeks.org/api/0.1/editPOIexec?"); //$NON-NLS-1$
b.append("id=").append(id); //$NON-NLS-1$
text = text + " [" + authorName + "]"; //$NON-NLS-1$ //$NON-NLS-2$
b.append("&text=").append(URLEncoder.encode(text)); //$NON-NLS-1$
b.append("&name=").append(URLEncoder.encode(authorName)); //$NON-NLS-1$
return editingPOI(b.toString(), "adding comment"); //$NON-NLS-1$
}
public boolean closingBug(long id){
StringBuilder b = new StringBuilder();
b.append("http://openstreetbugs.schokokeks.org/api/0.1/closePOIexec?"); //$NON-NLS-1$
b.append("id=").append(id); //$NON-NLS-1$
return editingPOI(b.toString(),"closing bug"); //$NON-NLS-1$
}
private boolean editingPOI(String urlStr, String debugAction){
try {
log.debug("Action " + debugAction + " " + urlStr); //$NON-NLS-1$ //$NON-NLS-2$
URL url = new URL(urlStr);
URLConnection connection = url.openConnection();
BufferedReader reader = new BufferedReader(new InputStreamReader(connection.getInputStream()));
while(reader.readLine() != null){
}
log.debug("Action " + debugAction + " successfull"); //$NON-NLS-1$ //$NON-NLS-2$
return true;
} catch (IOException e) {
log.error("Error " +debugAction, e); //$NON-NLS-1$
} catch (RuntimeException e) {
log.error("Error "+debugAction, e); //$NON-NLS-1$
}
return false;
}
protected List<OpenStreetBug> loadingBugs(double topLatitude, double leftLongitude, double bottomLatitude,double rightLongitude){
List<OpenStreetBug> bugs = new ArrayList<OpenStreetBug>();
StringBuilder b = new StringBuilder();
b.append("http://openstreetbugs.schokokeks.org/api/0.1/getBugs?"); //$NON-NLS-1$
b.append("b=").append(bottomLatitude); //$NON-NLS-1$
b.append("&t=").append(topLatitude); //$NON-NLS-1$
b.append("&l=").append(leftLongitude); //$NON-NLS-1$
b.append("&r=").append(rightLongitude); //$NON-NLS-1$
try {
log.info("Loading bugs " + b.toString()); //$NON-NLS-1$
URL url = new URL(b.toString());
URLConnection connection = url.openConnection();
BufferedReader reader = new BufferedReader(new InputStreamReader(connection.getInputStream()));
String st = null;
while((st = reader.readLine()) != null){
Matcher matcher = patternToParse.matcher(st);
if(matcher.find()){
OpenStreetBug bug = new OpenStreetBug();
bug.setId(Long.parseLong(matcher.group(1)));
bug.setLongitude(Double.parseDouble(matcher.group(2)));
bug.setLatitude(Double.parseDouble(matcher.group(3)));
bug.setName(matcher.group(4).replace("<hr />", "\n")); //$NON-NLS-1$ //$NON-NLS-2$
bug.setOpened(matcher.group(5).equals("0")); //$NON-NLS-1$
bugs.add(bug);
}
}
} catch (IOException e) {
log.warn("Error loading bugs", e); //$NON-NLS-1$
} catch (NumberFormatException e) {
log.warn("Error loading bugs", e); //$NON-NLS-1$
} catch (RuntimeException e) {
log.warn("Error loading bugs", e); //$NON-NLS-1$
}
return bugs;
}
private void openBugAlertDialog(final Context ctx, final LayoutInflater layoutInflater, final OsmandMapTileView mapView, final double latitude, final double longitude, String message, String authorName){
final View openBug = layoutInflater.inflate(R.layout.open_bug, null);
Builder builder = new AlertDialog.Builder(ctx);
builder.setTitle(R.string.osb_add_dialog_title);
builder.setView(openBug);
((EditText)openBug.findViewById(R.id.BugMessage)).setText(message);
((EditText)openBug.findViewById(R.id.AuthorName)).setText(authorName);
builder.setNegativeButton(R.string.default_buttons_cancel, null);
builder.setPositiveButton(R.string.default_buttons_add, new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
String text = ((EditText)openBug.findViewById(R.id.BugMessage)).getText().toString();
String author = ((EditText)openBug.findViewById(R.id.AuthorName)).getText().toString();
// do not set name as author it is ridiculous in that case
OsmandSettings.setUserNameForOsmBug(ctx, author);
boolean bug = createNewBug(latitude, longitude, text, author);
if (bug) {
Toast.makeText(ctx, ctx.getResources().getString(R.string.osb_add_dialog_success), Toast.LENGTH_LONG).show();
clearCache();
if (mapView.getLayers().contains(OsmBugsLayer.this)) {
mapView.refreshMap();
}
} else {
Toast.makeText(ctx, ctx.getResources().getString(R.string.osb_add_dialog_error), Toast.LENGTH_LONG).show();
openBugAlertDialog(ctx, layoutInflater, mapView, latitude, longitude, text, author);
}
}
});
builder.show();
}
public void openBug(final Context ctx, LayoutInflater layoutInflater, final OsmandMapTileView mapView, final double latitude, final double longitude){
openBugAlertDialog(ctx, layoutInflater, mapView, latitude, longitude, "", OsmandSettings.getUserNameForOsmBug(OsmandSettings.getPrefs(ctx)));
}
public void commentBug(final Context ctx, LayoutInflater layoutInflater, final OpenStreetBug bug){
Builder builder = new AlertDialog.Builder(ctx);
builder.setTitle(R.string.osb_comment_dialog_title);
final View view = layoutInflater.inflate(R.layout.open_bug, null);
builder.setView(view);
((EditText)view.findViewById(R.id.AuthorName)).setText(OsmandSettings.getUserNameForOsmBug(OsmandSettings.getPrefs(ctx)));
builder.setNegativeButton(R.string.default_buttons_cancel, null);
builder.setPositiveButton(R.string.osb_comment_dialog_add_button, new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
String text = ((EditText)view.findViewById(R.id.BugMessage)).getText().toString();
String author = ((EditText)view.findViewById(R.id.AuthorName)).getText().toString();
OsmandSettings.setUserNameForOsmBug(ctx, author);
boolean added = addingComment(bug.getId(), text, author);
if (added) {
Toast.makeText(ctx, ctx.getResources().getString(R.string.osb_comment_dialog_success), Toast.LENGTH_LONG).show();
clearCache();
if (OsmBugsLayer.this.view.getLayers().contains(OsmBugsLayer.this)) {
OsmBugsLayer.this.view.refreshMap();
}
} else {
Toast.makeText(ctx, ctx.getResources().getString(R.string.osb_comment_dialog_error), Toast.LENGTH_LONG).show();
}
}
});
builder.show();
}
public void closeBug(final Context ctx, LayoutInflater layoutInflater, final OpenStreetBug bug){
Builder builder = new AlertDialog.Builder(ctx);
builder.setTitle(R.string.osb_close_dialog_title);
builder.setNegativeButton(R.string.default_buttons_cancel, null);
builder.setPositiveButton(R.string.osb_close_dialog_close_button, new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
boolean closed = closingBug(bug.getId());
if (closed) {
Toast.makeText(ctx, ctx.getResources().getString(R.string.osb_close_dialog_success), Toast.LENGTH_LONG).show();
clearCache();
if (OsmBugsLayer.this.view.getLayers().contains(OsmBugsLayer.this)) {
OsmBugsLayer.this.view.refreshMap();
}
} else {
Toast.makeText(ctx, ctx.getResources().getString(R.string.osb_close_dialog_error), Toast.LENGTH_LONG).show();
}
}
});
builder.show();
}
@Override
public OnClickListener getActionListener(List<String> actionsList, Object o) {
final OpenStreetBug bug = (OpenStreetBug) o;
actionsList.add(view.getContext().getString(R.string.osb_comment_menu_item));
actionsList.add(view.getContext().getString(R.string.osb_close_menu_item));
return new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
if (which == 0) {
commentBug(view.getContext(), activity.getLayoutInflater(), bug);
} else if (which == 1) {
closeBug(view.getContext(), activity.getLayoutInflater(), bug);
}
}
};
}
@Override
public String getObjectDescription(Object o) {
if(o instanceof OpenStreetBug){
return view.getContext().getString(R.string.osb_bug_name) + " : " + ((OpenStreetBug)o).getName(); //$NON-NLS-1$
}
return null;
}
@Override
public Object getPointObject(PointF point) {
return getBugFromPoint(point);
}
@Override
public LatLon getObjectLocation(Object o) {
if(o instanceof OpenStreetBug){
return new LatLon(((OpenStreetBug)o).getLatitude(), ((OpenStreetBug)o).getLongitude());
}
return null;
}
public static class OpenStreetBug {
private double latitude;
private double longitude;
private String name;
private long id;
private boolean opened;
public double getLatitude() {
return latitude;
}
public void setLatitude(double latitude) {
this.latitude = latitude;
}
public double getLongitude() {
return longitude;
}
public void setLongitude(double longitude) {
this.longitude = longitude;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public long getId() {
return id;
}
public void setId(long id) {
this.id = id;
}
public boolean isOpened() {
return opened;
}
public void setOpened(boolean opened) {
this.opened = opened;
}
}
}