Google Map Tile Provider (GeoServer And WMS) - android

WMS Webservice GeoServer WMS
I try to get Tile Information(I, J , BBOX) on selected Latitude and Longitude with zooming level in Google Map.
I used this formula to get I, J , BBOX Formula Source
private void getXYFromLatLon(double lat, double lon, final int zoom) {
int tileSize = 256;
// double initialResolution = 2 * Math.PI * 6378137 / tileSize;
double initialResolution = 156543.03392804062;
double originShift = 20037508.342789244;
// LatLong to Meter
double mx = lon * originShift / 180.0;
double my = Math.log(Math.tan((90 + lat) * Math.PI / 360.0))
/ (Math.PI / 180.0);
my = my * originShift / 180.0;
// Meter to Pixels
double res = initialResolution / (2 * zoom);
double px = (mx + originShift) / res;
double py = (my + originShift) / res;
getBoundingBox(Double.valueOf(px).intValue(), Double.valueOf(py)
.intValue(), zoom);
// Pixel to tiles
final int tx = (int) Math.ceil(px / ((tileSize)) - 1);
final int ty = (int) Math.ceil(py / ((tileSize)) - 1);
getTileBound(tx, ty, zoom, tileSize);
Toast.makeText(getApplicationContext(), "X: " + tx + ",Y: " + ty,
Toast.LENGTH_SHORT).show();
}private void getTileBound(int tx, int ty, int zoom, int tileSize) {
double[] min = pixelToMeter(tx * tileSize, ty * tileSize, zoom);
double[] max = pixelToMeter((tx + 1) * tileSize, (ty + 1) * tileSize,
zoom);
builder.append("\nMIN-X:" + min[0]).append("\nMIN-Y:" + min[1])
.append("\nMAX-X:" + max[0]).append("\nMAX-Y:" + max[1])
.append("\nI:" + (tx)).append("\nJ:" + (ty));
((TextView) findViewById(R.id.textView1)).setText(builder.toString());
/*
* Toast.makeText(getApplicationContext(), "X: " + min.toString() +
* ",Y: " + max.toString(), Toast.LENGTH_SHORT).show();
*/
}public String getTileNumber(final double lat, final double lon,
final int zoom) {
int xtile = (int) Math.floor((lon + 180) / 360 * (1 << zoom));
int ytile = (int) Math
.floor((1 - Math.log(Math.tan(Math.toRadians(lat)) + 1
/ Math.cos(Math.toRadians(lat)))
/ Math.PI)
/ 2 * (1 << zoom));
if (xtile < 0)
xtile = 0;
if (xtile >= (1 << zoom))
xtile = ((1 << zoom) - 1);
if (ytile < 0)
ytile = 0;
if (ytile >= (1 << zoom))
ytile = ((1 << zoom) - 1);
System.out.println("xtile" + xtile);
// Toast.makeText(getApplicationContext(),
// xtile + "YY" + ytile + "Zoom" + (1 << zoom), Toast.LENGTH_LONG)
// .show();
return ("" + zoom + "/" + xtile + "/" + ytile);
}private double[] pixelToMeter(int x, int y, int zoom) {
int tileSize = 256;
double initialResolution = 2 * Math.PI * 6378137 / tileSize;
double originShift = 2 * Math.PI * 6378137 / 2;
double res = initialResolution / (2 * zoom);
double mx = x * res - originShift;
double my = y * res - originShift;
return new double[] { mx, my };
}
The problem based on zooming level i'm not able to find the exact value ..
Based on correct value i to have call the WMS webservices
Thanks in advance...
http://192.168.1.102:1005/geoserver/estater/wms?SERVICE=WMS&VERSION=1.3.0&REQUEST=GetFeatureInfo&FORMAT=image%2Fpng&TRANSPARENT=true&QUERY_LAYERS=buildings&LAYERS=kwt_buildings&INFO_FORMAT=application%2Fjson&propertyName=grid_id%2Cbuild_id&I=90&J=161&WIDTH=256&HEIGHT=256&CRS=EPSG%3A3857&STYLES=&BBOX=5342031.032794397%2C3420709.8898182083%2C5343254.02524696%2C3421932.882270771

I did not look at your code, but I have mine working since years, so here it is the WMS tile provider class:
public abstract class WMSTileProvider extends UrlTileProvider {
// Web Mercator n/w corner of the map.
private static final double[] TILE_ORIGIN = { -20037508.34789244, 20037508.34789244 };
// array indexes for that data
private static final int ORIG_X = 0;
private static final int ORIG_Y = 1; // "
// Size of square world map in meters, using WebMerc projection.
private static final double MAP_SIZE = 20037508.34789244 * 2;
// array indexes for array to hold bounding boxes.
protected static final int MINX = 0;
protected static final int MAXX = 1;
protected static final int MINY = 2;
protected static final int MAXY = 3;
// cql filters
private String cqlString = "";
// Construct with tile size in pixels, normally 256, see parent class.
public WMSTileProvider(int x, int y) {
super(x, y);
}
#SuppressWarnings("deprecation")
protected String getCql() {
try {
return URLEncoder.encode(cqlString, Charset.defaultCharset().name());
} catch (UnsupportedEncodingException e) {
e.printStackTrace();
return URLEncoder.encode(cqlString);
}
}
public void setCql(String c) {
cqlString = c;
}
// Return a web Mercator bounding box given tile x/y indexes and a zoom
// level.
protected double[] getBoundingBox(int x, int y, int zoom) {
double tileSize = MAP_SIZE / Math.pow(2, zoom);
double minx = TILE_ORIGIN[ORIG_X] + x * tileSize;
double maxx = TILE_ORIGIN[ORIG_X] + (x + 1) * tileSize;
double miny = TILE_ORIGIN[ORIG_Y] - (y + 1) * tileSize;
double maxy = TILE_ORIGIN[ORIG_Y] - y * tileSize;
double[] bbox = new double[4];
bbox[MINX] = minx;
bbox[MINY] = miny;
bbox[MAXX] = maxx;
bbox[MAXY] = maxy;
return bbox;
}
}
And here is something on how i use it:
public static WMSTileProvider getWMSTileProviderByName(String layerName) {
final String OSGEO_WMS = "http://yourserver/geoserver/gwc/service/wms/?"
+ "LAYERS=" + layerName
+ "&FORMAT=image/png8&"
+ "PROJECTION=EPSG:3857&"
+ "TILEORIGIN=lon=-20037508.34,lat=-20037508.34&"
+ "TILESIZE=w=256,h=256"
+ "&MAXEXTENT=-20037508.34,-20037508.34,20037508.34,20037508.34&SERVICE=WMS&VERSION=1.1.1&REQUEST=GetMap&STYLES=&SRS=EPSG:3857"
+ "&BBOX=%f,%f,%f,%f&WIDTH=256&HEIGHT=256";
return new WMSTileProvider(256, 256) {
#Override
public synchronized URL getTileUrl(int x, int y, int zoom) {
final double[] bbox = getBoundingBox(x, y, zoom);
String s = String.format(Locale.US, OSGEO_WMS, bbox[MINX], bbox[MINY], bbox[MAXX], bbox[MAXY]);
try {
return new URL(s);
} catch (MalformedURLException e) {
throw new AssertionError(e);
}
}
};
}
Can't give you more code, but I hope can help you
EDIT: Given the comment, now i see what you need.
Here it is some code (old but working) on which you have to work a bit, it was a sort of hack:
private static final double[] TILES_ORIGIN = {-20037508.34789244, 20037508.34789244};//TODO Duplicate from WMS PROVIDER, put as utils
// Size of square world map in meters, using WebMerc projection.
private static final double MAP_SIZE = 20037508.34789244 * 2;//TODO Duplicate from WMS PROVIDER, put as utils
private static final double ORIGIN_SHIFT = Math.PI * 6378137d;
/**
* Transform the y map meter in y cordinate
*
* #param latitude the latitude of map
* #return meters of y cordinate
*/
private double inMetersYCoordinate(double latitude) {
if (latitude < 0) {
return -inMetersYCoordinate(-latitude);
}
return (Math.log(Math.tan((90d + latitude) * Math.PI / 360d)) / (Math.PI / 180d)) * ORIGIN_SHIFT / 180d;
}
/**
* Transform the x map meter in x cordinate
*
* #param longitude the longitude of map
* #return meters of x cordinate
*/
private double inMetersXCoordinate(double longitude) {
return longitude * ORIGIN_SHIFT / 180.0;
}
/**
* Get the Tile from x and y cordinates
*
* #param pointX x of the map
* #param pointY y of the map
* #param zoomLevel zoom of Tile
* #return the relative TileDataInfo
*/
private TileDataInfo getTileByCoordinate(double pointX, double pointY, int zoomLevel) {
final double tileDim = MAP_SIZE / Math.pow(2d, zoomLevel);
final int tileX = (int) ((pointX - TILES_ORIGIN[0]) / tileDim);
final int tileY = (int) ((TILES_ORIGIN[1] - pointY) / tileDim);
return new TileDataInfo(tileX, tileY, zoomLevel);
}
private static class TileDataInfo {
int tileX;
int tileY;
int tileZoom;
public TileDataInfo(int tileX, int tileY, int tileZoom) {
this.tileX = tileX;
this.tileY = tileY;
this.tileZoom = tileZoom;
}
}
In order to get the code right, you have to convert latitude in meters using the "inMetersYCoordinate", the longitude using "inMetersXCoordinate" and then use "getTileByCoordinate" to calculate the tile x,y,z (i,j,zoom for you)

Related

android mapview with custom wms tile provider

i want to load my wms server to show tiles from there. i used carto/nutiteq and everything is ok but i want it open source or free license . then i try osmdroid library but i can't implement my wms can anyone help me ?
// Web Mercator n/w corner of the map.
private static final double[] TILE_ORIGIN = {-20037508.34789244, 20037508.34789244};
//array indexes for that data
private static final int ORIG_X = 0;
private static final int ORIG_Y = 1; //
private static String mLayername = "";
// Size of square world map in meters, using WebMerc projection.
private static final double MAP_SIZE = 20037508.34789244 * 2;
// array indexes for array to hold bounding boxes.
protected static final int MINX = 0;
protected static final int MAXX = 1;
protected static final int MINY = 2;
protected static final int MAXY = 3;
private String layer = "";
/**
* Constructor
*
* #param aName a human-friendly name for this tile source
* #param aBaseUrl the base url(s) of the tile server used when constructing the url to download the tiles http://sedac.ciesin.columbia.edu/geoserver/wms
*/
public WMSTileProvider(String aName, String[] aBaseUrl, String layername) {
super(aName, 8, 22, 255, "image/png", aBaseUrl);
mLayername = layername;
}
final String WMS_FORMAT_STRING =
"http://dev.shiveh.com/shiveh?service=WMS&request=GetMap&layers=Shiveh%3AShivehGSLD256&styles=&format=image%2Fpng&transparent=false&version=1.1.1&height=256&width=256&srs=EPSG%3A4326&bbox=";
protected double[] getBoundingBox(int x, int y, int zoom) {
double tileSize = MAP_SIZE / Math.pow(2, zoom);
double minx = TILE_ORIGIN[ORIG_X] + x * tileSize;
double maxx = TILE_ORIGIN[ORIG_X] + (x + 1) * tileSize;
double miny = TILE_ORIGIN[ORIG_Y] - (y + 1) * tileSize;
double maxy = TILE_ORIGIN[ORIG_Y] - y * tileSize;
double[] bbox = new double[4];
bbox[MINX] = minx;
bbox[MINY] = miny;
bbox[MAXX] = maxx;
bbox[MAXY] = maxy;
return bbox;
}
The coordinate reference system in your url EPSG:4326 which is lat/lon wgs84. However the equations you have appear to look like the equations to convert from slippy map coordinates to EPSG:900913.
Does your server support that CRS? Try switching the CRS url parameter to EPSG:900913 or change the equation to compute the coordinates for the requested tile

Maps, test if current location is on or near polyline

I'm using google directions api to draw a polyline for a route. Does anyone have any examples of checking if current location is on/near a polyline? Trying to determine if users current location is within x meters of that line and if not i'll make a new request and redraw a new route.
Cheers!
Here is my solution: just add the bdccGeoDistanceAlgorithm class I have created to your project and use bdccGeoDistanceCheckWithRadius method to check if your current location is on or near polyline (polyline equals to a list of LatLng of points)
Your can also get the distance from the method
Class bdccGeoDistanceAlgorithm
import com.google.android.gms.maps.model.LatLng;
import java.util.List;
public class bdccGeoDistanceAlgorithm {
// distance in meters from GLatLng point to GPolyline or GPolygon poly
public static boolean bdccGeoDistanceCheckWithRadius(List<LatLng> poly, LatLng point, int radius)
{
int i;
bdccGeo p = new bdccGeo(point.latitude,point.longitude);
for(i=0; i < (poly.size()-1) ; i++)
{
LatLng p1 = poly.get(i);
bdccGeo l1 = new bdccGeo(p1.latitude,p1.longitude);
LatLng p2 = poly.get(i+1);
bdccGeo l2 = new bdccGeo(p2.latitude,p2.longitude);
double distance = p.function_distanceToLineSegMtrs(l1, l2);
if(distance < radius)
return true;
}
return false;
}
// object
public static class bdccGeo
{
public double lat;
public double lng;
public double x;
public double y;
public double z;
public bdccGeo(double lat, double lon) {
this.lat = lat;
this.lng = lng;
double theta = (lon * Math.PI / 180.0);
double rlat = function_bdccGeoGeocentricLatitude(lat * Math.PI / 180.0);
double c = Math.cos(rlat);
this.x = c * Math.cos(theta);
this.y = c * Math.sin(theta);
this.z = Math.sin(rlat);
}
//returns in meters the minimum of the perpendicular distance of this point from the line segment geo1-geo2
//and the distance from this point to the line segment ends in geo1 and geo2
public double function_distanceToLineSegMtrs(bdccGeo geo1,bdccGeo geo2)
{
//point on unit sphere above origin and normal to plane of geo1,geo2
//could be either side of the plane
bdccGeo p2 = geo1.function_crossNormalize(geo2);
// intersection of GC normal to geo1/geo2 passing through p with GC geo1/geo2
bdccGeo ip = function_bdccGeoGetIntersection(geo1,geo2,this,p2);
//need to check that ip or its antipode is between p1 and p2
double d = geo1.function_distance(geo2);
double d1p = geo1.function_distance(ip);
double d2p = geo2.function_distance(ip);
//window.status = d + ", " + d1p + ", " + d2p;
if ((d >= d1p) && (d >= d2p))
return function_bdccGeoRadiansToMeters(this.function_distance(ip));
else
{
ip = ip.function_antipode();
d1p = geo1.function_distance(ip);
d2p = geo2.function_distance(ip);
}
if ((d >= d1p) && (d >= d2p))
return function_bdccGeoRadiansToMeters(this.function_distance(ip));
else
return function_bdccGeoRadiansToMeters(Math.min(geo1.function_distance(this),geo2.function_distance(this)));
}
// More Maths
public bdccGeo function_crossNormalize(bdccGeo b)
{
double x = (this.y * b.z) - (this.z * b.y);
double y = (this.z * b.x) - (this.x * b.z);
double z = (this.x * b.y) - (this.y * b.x);
double L = Math.sqrt((x * x) + (y * y) + (z * z));
bdccGeo r = new bdccGeo(0,0);
r.x = x / L;
r.y = y / L;
r.z = z / L;
return r;
}
// Returns the two antipodal points of intersection of two great
// circles defined by the arcs geo1 to geo2 and
// geo3 to geo4. Returns a point as a Geo, use .antipode to get the other point
public bdccGeo function_bdccGeoGetIntersection(bdccGeo geo1,bdccGeo geo2, bdccGeo geo3,bdccGeo geo4)
{
bdccGeo geoCross1 = geo1.function_crossNormalize(geo2);
bdccGeo geoCross2 = geo3.function_crossNormalize(geo4);
return geoCross1.function_crossNormalize(geoCross2);
}
public double function_distance(bdccGeo v2)
{
return Math.atan2(v2.function_crossLength(this), v2.function_dot(this));
}
//More Maths
public double function_crossLength(bdccGeo b)
{
double x = (this.y * b.z) - (this.z * b.y);
double y = (this.z * b.x) - (this.x * b.z);
double z = (this.x * b.y) - (this.y * b.x);
return Math.sqrt((x * x) + (y * y) + (z * z));
}
//Maths
public double function_dot(bdccGeo b)
{
return ((this.x * b.x) + (this.y * b.y) + (this.z * b.z));
}
//from Radians to Meters
public double function_bdccGeoRadiansToMeters(double rad)
{
return rad * 6378137.0; // WGS84 Equatorial Radius in Meters
}
// point on opposite side of the world to this point
public bdccGeo function_antipode()
{
return this.function_scale(-1.0);
}
//More Maths
public bdccGeo function_scale(double s)
{
bdccGeo r = new bdccGeo(0,0);
r.x = this.x * s;
r.y = this.y * s;
r.z = this.z * s;
return r;
}
// Convert from geographic to geocentric latitude (radians).
public double function_bdccGeoGeocentricLatitude(double geographicLatitude)
{
double flattening = 1.0 / 298.257223563;//WGS84
double f = (1.0 - flattening) * (1.0 - flattening);
return Math.atan((Math.tan(geographicLatitude) * f));
}
}
}

Android change google map tile with custom tiles

Is it possible to change google map API v3 standart map to my own custom map coming from url? I know that OSMdroid provide it but i want work with google map API. Is it possible?
it is indeed possible by using WMS services (if you don't know what they are, please google it).
Here is some code you can use:
The WMSTile provider is used by GoogleMapsAPI to set the map provider:
public abstract class WMSTileProvider extends UrlTileProvider {
// Web Mercator n/w corner of the map.
private static final double[] TILE_ORIGIN = { -20037508.34789244, 20037508.34789244 };
// array indexes for that data
private static final int ORIG_X = 0;
private static final int ORIG_Y = 1; // "
// Size of square world map in meters, using WebMerc projection.
private static final double MAP_SIZE = 20037508.34789244 * 2;
// array indexes for array to hold bounding boxes.
protected static final int MINX = 0;
protected static final int MAXX = 1;
protected static final int MINY = 2;
protected static final int MAXY = 3;
// cql filters
private String cqlString = "";
// Construct with tile size in pixels, normally 256, see parent class.
public WMSTileProvider(int x, int y) {
super(x, y);
}
#SuppressWarnings("deprecation")
protected String getCql() {
try {
return URLEncoder.encode(cqlString, Charset.defaultCharset().name());
} catch (UnsupportedEncodingException e) {
e.printStackTrace();
return URLEncoder.encode(cqlString);
}
}
public void setCql(String c) {
cqlString = c;
}
// Return a web Mercator bounding box given tile x/y indexes and a zoom
// level.
protected double[] getBoundingBox(int x, int y, int zoom) {
double tileSize = MAP_SIZE / Math.pow(2, zoom);
double minx = TILE_ORIGIN[ORIG_X] + x * tileSize;
double maxx = TILE_ORIGIN[ORIG_X] + (x + 1) * tileSize;
double miny = TILE_ORIGIN[ORIG_Y] - (y + 1) * tileSize;
double maxy = TILE_ORIGIN[ORIG_Y] - y * tileSize;
double[] bbox = new double[4];
bbox[MINX] = minx;
bbox[MINY] = miny;
bbox[MAXX] = maxx;
bbox[MAXY] = maxy;
return bbox;
}
}
And you can instantiate a custom one from your URL in such a way:
public static WMSTileProvider getWMSTileProviderByName(String layerName) {
final String OSGEO_WMS = "http://YOURWMSSERVERURL?"
+ "LAYERS=" + layerName
+ "&FORMAT=image/png8&"
+ "PROJECTION=EPSG:3857&"
+ "TILEORIGIN=lon=-20037508.34,lat=-20037508.34&"
+ "TILESIZE=w=256,h=256"
+ "&MAXEXTENT=-20037508.34,-20037508.34,20037508.34,20037508.34&SERVICE=WMS&VERSION=1.1.1&REQUEST=GetMap&STYLES=&SRS=EPSG:3857"
+ "&BBOX=%f,%f,%f,%f&WIDTH=256&HEIGHT=256";
return new WMSTileProvider(256, 256) {
#Override
public synchronized URL getTileUrl(int x, int y, int zoom) {
final double[] bbox = getBoundingBox(x, y, zoom);
String s = String.format(Locale.US, OSGEO_WMS, bbox[MINX], bbox[MINY], bbox[MAXX], bbox[MAXY]);
try {
return new URL(s);
} catch (MalformedURLException e) {
throw new AssertionError(e);
}
}
};
}
Add to your map:
TileProvider tileProvider = getWMSTileProviderByName("MYLAYERNAME");
TileOverlay tileOverlay = myMap.addTileOverlay(new TileOverlayOptions()
.tileProvider(tileProvider));
You should also set the map type to MAP_NONE when using a custom tile provider (if it is not transparent), so you avoid to load gmaps tiles that are hidden behind your custom map.

How to reverse this mathematical conversion in android

I'm using this code to convert Google Map coordinates into an X,Y system on a custom overlay. This takes a latitude, longitude, and a zoom and converts it to x,y with a custom center point built in. What are the correct steps to have an x,y and convert it back to latitude and longitude.
static Double game_1_x = 1972.606;
static Double game_1_y = 3817.044;
static Double map_1_lng = 42.012002;
static Double map_1_lat = 42.850185;
static Double game_2_x = -1210.765;
static Double game_2_y = -3443.753;
static Double map_2_lng = -49.922088;
static Double map_2_lat = -83.293854;
public static String convertToXY(String lat, String lon, float zoom) {
int mapSize = 0;
if (zoom == 2.0f) {
mapSize = 1024;
} else if (zoom == 3.0f) {
mapSize = 2048;
} else if (zoom == 4.0f) {
mapSize = 4096;
} else if (zoom == 5.0f) {
mapSize = 8192;
}
Double LAT = Double.valueOf(lat);
Double LON = Double.valueOf(lon);
// get marker x value
Double markerLon = (LON + 180) * (mapSize / 360);
// convert Lat to Radians
Double markerLatRad = LAT * Math.PI / 180;
// get marker y value
Double mercN = Math.log(Math.tan((Math.PI / 4) + (markerLatRad / 2)));
Double markerLat = (mapSize / 2) - (mapSize * mercN / (2 * Math.PI));
// get map 1 x value
Double m1lng = (map_1_lng + 180) * (mapSize / 360);
// get map 2 x value
Double m2lng = (map_2_lng + 180) * (mapSize / 360);
// convert Lat to Radians
Double m1LatRad = map_1_lat * Math.PI / 180;
Double m2LatRad = map_2_lat * Math.PI / 180;
// get map 1 y value
Double mercNm1y = Math.log(Math.tan((Math.PI / 4) + (m1LatRad / 2)));
Double m1lat = (mapSize / 2) - (mapSize * mercNm1y / (2 * Math.PI));
// get map 2 y value
Double mercNm2y = Math.log(Math.tan((Math.PI / 4) + (m2LatRad / 2)));
Double m2lat = (mapSize / 2) - (mapSize * mercNm2y / (2 * Math.PI));
Double X = game_1_x + (markerLon - m1lng) * (game_1_x - game_2_x) / (m1lng - m2lng);
Double Y = game_1_y + (markerLat - m1lat) * (game_1_y - game_2_y) / (m1lat - m2lat);
return String.valueOf(X) + "," + String.valueOf(Y);
}
See if this is what you need:
mMap = mMapView.getMap();
mMap.getProjection().fromScreenLocation(Point p)
and the other way around
mMap.getProjection().toScreenLocation(LatLng l)

Calculating points for bezier curve for map

I'm calculating the necessary oints for a bezier curve between to geo location points to draw the line at Google Maps V2. My problem is that it doesn't work correctly if the line has to be drawn 'over the pacific ocean', e.g. start point is in Tokyo and end point is in Vancouver. The points are calculated in the wrong direction (to the east around the earth) and not to the west. Locations points across the Atlantic ocean or Asia are calculated and drawn correct.
Where is the error in my code or my thinking?
Here is the code for calculating:
public static ArrayList<LatLng> bezier(LatLng p1, LatLng p2, double arcHeight, double skew, boolean up){
ArrayList<LatLng> list = new ArrayList<LatLng>();
try {
if(p1.longitude > p2.longitude){
LatLng tmp = p1;
p1 = p2;
p2 = tmp;
}
LatLng c = midPoint(p1, p2, 0);
Log.v(TAG, "P1: " + p1.toString());
Log.v(TAG, "P2: " + p2.toString());
Log.v(TAG, "C: " + c.toString());
double cLat = c.latitude;
double cLon = c.longitude;
//add skew and arcHeight to move the midPoint
if(Math.abs(p1.longitude - p2.longitude) < 0.0001){
if(up){
cLon -= arcHeight;
}else{
cLon += arcHeight;
cLat += skew;
}
}else{
if(up){
cLat += arcHeight;
}else{
cLat -= arcHeight;
cLon += skew;
}
}
list.add(p1);
//calculating points for bezier
double tDelta = 1.0/10;
for (double t = 0; t <= 1.0; t+=tDelta) {
double oneMinusT = (1.0-t);
double t2 = Math.pow(t, 2);
double lon = oneMinusT * oneMinusT * p1.longitude
+ 2 * oneMinusT * t * cLon
+ t2 * p2.longitude;
double lat = oneMinusT * oneMinusT * p1.latitude
+ 2 * oneMinusT * t * cLat
+ t2 * p2.latitude;
Log.v(TAG, "t: " + t + "[" + lat +"|" + lon + "]");
list.add(new LatLng(lat, lon));
}
list.add(p2);
} catch (Exception e) {
Log.e(TAG, "bezier", e);
}
return list;
}
Here is the output from logcat with the calculated points;
P1: lat/lng: (35.76472,140.38639)
P2: lat/lng: (49.19489,-123.17923)
C: lat/lng: (53.760800330485814,-178.27615766444313)
t: 0.0[35.76472|140.38639]
t: 0.1[39.17431615948745|80.39147522040025]
t: 0.2[42.12467250575547|27.871749947378213]
t: 0.3[44.61578903880404|-17.172785819066128]
t: 0.4[46.647665758633195|-54.7421320789327]
t: 0.5[48.22030266524291|-84.83628883222157]
t: 0.6[49.333699758633195|-107.4552560789327]
t: 0.7[49.98785703880404|-122.59903381906611]
t: 0.7[50.18277450575546|-130.2676220526218]
t: 0.8[49.918452159487444|-130.46102077959978]
t: 0.9[49.19489|-123.17923000000002]
And here is a screenshot of the map:
I've decided to convert the geo locations to a cartesian coordinate system and then do the calculation. This worked.
Here are the changes:
//inside bezier(...)
CartesianCoordinates cart1 = new CartesianCoordinates(p1);
CartesianCoordinates cart2 = new CartesianCoordinates(p2);
CartesianCoordinates cart3 = new CartesianCoordinates(cLat, cLon);
for (double t = 0; t <= 1.0; t += tDelta) {
double oneMinusT = (1.0 - t);
double t2 = Math.pow(t, 2);
double y = oneMinusT * oneMinusT * cart1.y + 2 * t * oneMinusT * cart3.y + t2 * cart2.y;
double x = oneMinusT * oneMinusT * cart1.x + 2 * t * oneMinusT * cart3.x + t2 * cart2.x;
double z = oneMinusT * oneMinusT * cart1.z + 2 * t * oneMinusT * cart3.z + t2 * cart2.z;
LatLng control = CartesianCoordinates.toLatLng(x, y, z);
if (Config.DEBUG)
Log.v(TAG, "t: " + t + control.toString());
list.add(control);
}
with CartesianCoordinates:
private static class CartesianCoordinates {
private static final int R = 6371; // approximate radius of earth
double x;
double y;
double z;
public CartesianCoordinates(LatLng p) {
this(p.latitude, p.longitude);
}
public CartesianCoordinates(double lat, double lon) {
double _lat = Math.toRadians(lat);
double _lon = Math.toRadians(lon);
x = R * Math.cos(_lat) * Math.cos(_lon);
y = R * Math.cos(_lat) * Math.sin(_lon);
z = R * Math.sin(_lat);
}
public static LatLng toLatLng(double x, double y, double z){
return new LatLng(Math.toDegrees(Math.asin(z / R)), Math.toDegrees(Math.atan2(y, x)));
}
}
Method to calculate the midpoint of two coordinates (maybe not 100% perfect mathematically correct):
private static LatLng midPoint(LatLng p1, LatLng p2) throws IllegalArgumentException{
if(p1 == null || p2 == null)
throw new IllegalArgumentException("two points are needed for calculation");
double lat1;
double lon1;
double lat2;
double lon2;
//convert to radians
lat1 = Math.toRadians(p1.latitude);
lon1 = Math.toRadians(p1.longitude);
lat2 = Math.toRadians(p2.latitude);
lon2 = Math.toRadians(p2.longitude);
double x1 = Math.cos(lat1) * Math.cos(lon1);
double y1 = Math.cos(lat1) * Math.sin(lon1);
double z1 = Math.sin(lat1);
double x2 = Math.cos(lat2) * Math.cos(lon2);
double y2 = Math.cos(lat2) * Math.sin(lon2);
double z2 = Math.sin(lat2);
double x = (x1 + x2)/2;
double y = (y1 + y2)/2;
double z = (z1 + z2)/2;
double lon = Math.atan2(y, x);
double hyp = Math.sqrt(x*x + y*y);
// HACK: 0.9 and 1.1 was found by trial and error; this is probably *not* the right place to apply mid point shifting
double lat = Math.atan2(.9*z, hyp);
if(lat>0) lat = Math.atan2(1.1*z, hyp);
if(Config.DEBUG)
Log.v(TAG, Math.toDegrees(lat) + " " + Math.toDegrees(lon));
return new LatLng(Math.toDegrees(lat), Math.toDegrees(lon));
}

Categories

Resources