package org.osmdroid.views.overlay.gridlines;
import android.content.Context;
import android.graphics.Color;
import org.osmdroid.util.BoundingBox;
import org.osmdroid.views.overlay.FolderOverlay;
import org.osmdroid.views.overlay.Marker;
import org.osmdroid.views.overlay.Polyline;
import org.osmdroid.util.GeoPoint;
import org.osmdroid.views.MapView;
import java.text.DecimalFormat;
import java.util.ArrayList;
import java.util.List;
/**
* Latitude/Longitude gridline overlay
*
* It's not perfect and has issues with osmdroid's global wrap around (where north pole turns into the south pole).
* There's probably room for more optimizations too, pull requests are welcome.
*
* @since 5.2+
* Created by alex on 12/15/15.
*/
public class LatLonGridlineOverlay {
final static DecimalFormat df = new DecimalFormat("#.#####");
public static int lineColor = Color.BLACK;
public static int fontColor=Color.WHITE;
public static short fontSizeDp=24;
public static int backgroundColor=Color.BLACK;
public static float lineWidth = 1f;
//extra debugging options
public static boolean DEBUG = false;
public static boolean DEBUG2 = false;
//used to adjust the number of grid lines displayed on screen
private static float multiplier = 1f;
private static void applyMarkerAttributes(Marker m){
m.setTextLabelBackgroundColor(backgroundColor);
m.setTextLabelFontSize(fontSizeDp);
m.setTextLabelForegroundColor(fontColor);
}
public static FolderOverlay getLatLonGrid(Context ctx, MapView mapView) {
BoundingBox box = mapView.getBoundingBox();
int zoom = mapView.getZoomLevel();
Marker.ENABLE_TEXT_LABELS_WHEN_NO_IMAGE = true;
if (DEBUG) {
System.out.println("######### getLatLonGrid ");
}
FolderOverlay gridlines = new FolderOverlay();
if (zoom < 2) {
/* commented out for performance reasons
the calculations due to wrap around screw things up because the bounds is more than 1 globe.
for (int i = -90; i <= 90; i = i + 45) {
Polyline p = new Polyline(ctx);
p.setColor(lineColor);
p.setWidth(lineWidth);
List<GeoPoint> pts = new ArrayList<GeoPoint>();
GeoPoint x = new GeoPoint((double) i, 180);
pts.add(x);
x = new GeoPoint((double) i, 0);
pts.add(x);
x = new GeoPoint((double) i, -180);
pts.add(x);
p.setPoints(pts);
gridlines.add(p);
}
//vertical lines
for (int i = -180; i < 180; i = i + 45) {
Polyline p = new Polyline(ctx);
p.setColor(lineColor);
p.setWidth(lineWidth);
List<GeoPoint> pts = new ArrayList<GeoPoint>();
GeoPoint x = new GeoPoint((double) 90, (double) i);
pts.add(x);
x = new GeoPoint((double) -90, (double) i);
pts.add(x);
p.setPoints(pts);
gridlines.add(p);
}*/
} else {
double north = box.getLatNorth();
double south = box.getLatSouth();
double east = box.getLonEast();
double west = box.getLonWest();
double north_south_delta = 0d;
if (north < south) {
//we're vertically wrapping, abort.
return gridlines;
}
if (DEBUG) {
System.out.println("N " + north + " S " + south + ", " + north_south_delta);
}
boolean dateLineVisible = false;
if (east < 0 && west > 0) {
//we're at the date line
dateLineVisible = true;
}
if (DEBUG) {
System.out.println("delta " + north_south_delta);
}
//drop a line every this many degrees
double incrementor = getIncrementor(zoom);
//this should be starting south at the nearest logical value, 90,45, 15, 10, 5, 1, 0.5, 0.25, 0.125, based on the incrementer,
//that way doesn't look like the lines are dancing everywhere
//FIXME also draw 2x as wide as the screen, to support rotation?
double[] startend = getStartEndPointsNS(north, south, zoom);
double sn_start_point = startend[0];
double sn_stop_point = startend[1];
for (double i = sn_start_point; i <= sn_stop_point; i = i + incrementor) {
Polyline p = new Polyline();
p.setWidth(lineWidth);
p.setColor(lineColor);
List<GeoPoint> pts = new ArrayList<GeoPoint>();
GeoPoint gx = new GeoPoint((double) i, east);
pts.add(gx);
gx = new GeoPoint((double) i, west);
pts.add(gx);
if (DEBUG) {
System.out.println("drawing NS " + (double) i + "," + east + " to " + (double) i + "," + west + ", zoom " + zoom);
}
p.setPoints(pts);
gridlines.add(p);
Marker m = new Marker(mapView);
applyMarkerAttributes(m);
if (i > 0) {
m.setTitle(df.format(i) + "N");
} else {
m.setTitle(df.format(i) + "S");
}
//must set the icon last
m.setIcon(null);
m.setPosition(new GeoPoint(i, west + incrementor));
gridlines.add(m);
}
double[] ew = getStartEndPointsWE(west, east, zoom);
double we_startpoint = ew[1];
double ws_stoppoint = ew[0];
for (double i = we_startpoint; i <= ws_stoppoint; i = i + incrementor) {
Polyline p = new Polyline();
p.setWidth(lineWidth);
p.setColor(lineColor);
List<GeoPoint> pts = new ArrayList<GeoPoint>();
GeoPoint gx = new GeoPoint((double) north, i);
pts.add(gx);
gx = new GeoPoint((double) south, i);
pts.add(gx);
p.setPoints(pts);
if (DEBUG) {
System.err.println("drawing EW " + (double) south + "," + i + " to " + (double) north + "," + i + ", zoom " + zoom);
}
gridlines.add(p);
Marker m = new Marker(mapView);
applyMarkerAttributes(m);
m.setRotation(-90f);
if (i > 0) {
m.setTitle(df.format(i) + "E");
} else {
m.setTitle(df.format(i) + "W");
}
//must set the icon last
m.setIcon(null);
m.setPosition(new GeoPoint(south + (incrementor), i));
gridlines.add(m);
}
if (dateLineVisible) {
if (DEBUG)
System.out.println("DATELINE zoom " + zoom + " " + we_startpoint + " " + ws_stoppoint);
//special case to ensure that vertical lines are visible when the date line is visible.
//in this case western point is very positive and eastern part is very negative
for (double i = we_startpoint; i <= 180; i = i + incrementor) {
Polyline p = new Polyline();
p.setWidth(lineWidth);
p.setColor(lineColor);
List<GeoPoint> pts = new ArrayList<GeoPoint>();
GeoPoint gx = new GeoPoint((double) north, i);
pts.add(gx);
gx = new GeoPoint((double) south, i);
pts.add(gx);
p.setPoints(pts);
if (DEBUG2) {
System.out.println("DATELINE drawing NS" + (double) south + "," + i + " to " + (double) north + "," + i + ", zoom " + zoom);
}
gridlines.add(p);
}
for (double i = -180; i <= ws_stoppoint; i = i + incrementor) {
Polyline p = new Polyline();
p.setWidth(lineWidth);
p.setColor(lineColor);
List<GeoPoint> pts = new ArrayList<GeoPoint>();
GeoPoint gx = new GeoPoint((double) north, i);
pts.add(gx);
gx = new GeoPoint((double) south, i);
pts.add(gx);
p.setPoints(pts);
if (DEBUG2) {
System.out.println("DATELINE drawing EW" + (double) south + "," + i + " to " + (double) north + "," + i + ", zoom " + zoom);
}
gridlines.add(p);
Marker m = new Marker(mapView);
applyMarkerAttributes(m);
m.setRotation(-90f);
if (i > 0) {
m.setTitle(df.format(i) + "E");
} else {
m.setTitle(df.format(i) + "W");
}
//must set the icon last
m.setIcon(null);
m.setPosition(new GeoPoint(south + (incrementor), i));
gridlines.add(m);
}
for (double i = we_startpoint; i < 180; i = i + incrementor) {
Marker m = new Marker(mapView);
applyMarkerAttributes(m);
m.setRotation(-90f);
if (i > 0) {
m.setTitle(df.format(i) + "E");
} else {
m.setTitle(df.format(i) + "W");
}
//must set the icon last in order for the text label to show
m.setIcon(null);
m.setPosition(new GeoPoint(south + (incrementor), i));
gridlines.add(m);
}
}
}
return gridlines;
}
/**
* gets the start and end points for a latitude line
*
* @param north
* @param south
* @param zoom
* @return
*/
private static double[] getStartEndPointsNS(double north, double south, int zoom) {
//brute force when zoom is less than 10
if (zoom < 10) {
double sn_start_point = Math.floor(south);
double incrementor = getIncrementor(zoom);
double x = -90;
while (x < sn_start_point)
x = x + incrementor;
sn_start_point = x;
double sn_stop_point = Math.ceil(north);
x = 90;
while (x > sn_stop_point)
x = x - incrementor;
sn_stop_point = x;
if (sn_stop_point > 90) {
sn_stop_point = 90;
}
if (sn_start_point < -90) {
sn_start_point = -90;
}
return new double[]{sn_start_point, sn_stop_point};
} else {
//hmm start at origin, add inc until we go too far, then back off, go to the next zoom level
double sn_start_point = -90;
if (south > 0) {
sn_start_point = 0;
}
double sn_stop_point = 90;
if (north < 0) {
sn_stop_point = 0;
}
for (int xx = 2; xx <= zoom; xx++) {
double inc = getIncrementor(xx);
while (sn_start_point < south - inc) {
sn_start_point += inc;
if (DEBUG) {
System.out.println("south " + sn_start_point);
}
}
while (sn_stop_point > north + inc) {
sn_stop_point -= inc;
if (DEBUG) {
System.out.println("north " + sn_stop_point);
}
}
}
return new double[]{sn_start_point, sn_stop_point};
}
}
/**
* gets the start and stop point for a longitude line
*
* @param west
* @param east
* @param zoom
* @return
*/
private static double[] getStartEndPointsWE(double west, double east, int zoom) {
double incrementor = getIncrementor(zoom);
//brute force when zoom is less than 10
if (zoom < 10) {
double we_startpoint = Math.floor(west);
double x = 180;
while (x > we_startpoint)
x = x - incrementor;
we_startpoint = x;
//System.out.println("WS " + we_startpoint);
double ws_stoppoint = Math.ceil(east);
x = -180;
while (x < ws_stoppoint)
x = x + incrementor;
if (we_startpoint < -180) {
we_startpoint = -180;
}
if (ws_stoppoint > 180) {
ws_stoppoint = 180;
}
return new double[]{ws_stoppoint, we_startpoint};
} else {
//hmm start at origin, add inc until we go too far, then back off, go to the next zoom level
double west_start_point = -180;
if (west > 0) {
west_start_point = 0;
}
double easter_stop_point = 180;
if (east < 0) {
easter_stop_point = 0;
}
for (int xx = 2; xx <= zoom; xx++) {
double inc = getIncrementor(xx);
while (easter_stop_point > east + inc) {
easter_stop_point -= inc;
//System.out.println("east " + easter_stop_point);
}
while (west_start_point < west - inc) {
west_start_point += inc;
if (DEBUG) {
System.out.println("west " + west_start_point);
}
}
}
if (DEBUG) {
System.out.println("return EW set as " + west_start_point + " " + easter_stop_point);
}
return new double[]{easter_stop_point, west_start_point};
}
}
/**
* this gets the distance in decimal degrees in between each line on the grid based on zoom level.
* i had had it at more logical increments (90, 45, 30, etc) but changing to factors of 90 helps visualization
* (i.e. when you zoom in on a particular crosshair, the crosshair is still there at the next zoom level, for the most part
*
* @param zoom mapview's osm zoom level
* @return a double indicating the distance in degrees/decimal from which to place the gridlines on screen
*/
private static double getIncrementor(int zoom) {
switch (zoom) {
case 0:
case 1:
return 30d * multiplier;
case 2:
return 15d * multiplier;
case 3:
return 9d * multiplier;
case 4:
return 6d * multiplier;
case 5:
return 3d * multiplier;
case 6:
return 2d * multiplier;
case 7:
return 1d * multiplier;
case 8:
return 0.5d * multiplier;
case 9:
return 0.25d * multiplier;
/* default:
return 0.1d * (1/(Math.pow(2, (10-zoom))));*/
case 10:
return 0.1d * multiplier;
case 11:
return 0.05d * multiplier;
case 12:
return 0.025d * multiplier;
case 13:
return 0.0125d * multiplier;
case 14:
return 0.00625d * multiplier;
case 15:
return 0.003125d * multiplier;
case 16:
return 0.0015625 * multiplier;
case 17:
return 0.00078125 * multiplier;
case 18:
return 0.000390625 * multiplier;
case 19:
return 0.0001953125 * multiplier;
case 20:
return 0.00009765625 * multiplier;
case 21:
return 0.000048828125 * multiplier;
default:
return 0.0000244140625 * multiplier;
}
}
/**
* resets the settings
* @since 5.6.3
*/
public static void setDefaults() {
lineColor = Color.BLACK;
fontColor=Color.WHITE;
backgroundColor=Color.BLACK;
lineWidth = 1f;
fontSizeDp=32;
DEBUG=false;
DEBUG2=false;
}
}