Related
I have two geo positions lets say A and B and distance between both of them say D.
So I know A, B, and D.
Now I want to get all the points (l,m,n,o... etc.) in a distance of 3 meters each.
Note:
If I draw a straight line between A and B then all the required points should lie on that straight line.
What I have done so far:
I have searched a lot and come across this website but there is an example which is in javascript and when I converted this code into c# then I do not get exact points.
Please help me! Thank you!
For those who believe this question as a duplicate for like this then I want to clarify that I do not need to calculate distance. In my question distance is already known.
Finally, I came across the following Code:
using System;
using System.IO;
using System.Collections;
using System.Collections.Generic;
namespace Calc
{
public static class Program
{
private static readonly long RADIUS_OF_EARTH = 6371000; // radius of earth in m
public static void Main(string[] args)
{
// point interval in meters
int interval =2;
// direction of line in degrees
//start point
double lat1 = 28.6514975008004;
double lng1 = 77.2216437757015;
// end point
double lat2 = 28.6514763167883;
double lng2 = 77.2221480309963;
MockLocation start = new MockLocation(lat1, lng1);
MockLocation end = new MockLocation(lat2, lng2);
double azimuth = calculateBearing(start, end);
Console.WriteLine(azimuth);
List<MockLocation> coords = getLocations(interval, azimuth, start, end);
foreach (MockLocation mockLocation in coords)
{
Console.WriteLine(mockLocation.lat + ", " + mockLocation.lng);
}
Console.ReadLine();
}
/**
* returns every coordinate pair in between two coordinate pairs given the desired interval
* #param interval
* #param azimuth
* #param start
* #param end
* #return
*/
private static List<MockLocation> getLocations(int interval, double azimuth, MockLocation start, MockLocation end)
{
Console.WriteLine("getLocations: " +
"\ninterval: " + interval +
"\n azimuth: " + azimuth +
"\n start: " + start.toString());
double d = getPathLength(start, end);
int dist = (int)d / interval;
int coveredDist = interval;
List<MockLocation> coords = new List<MockLocation>();
MockLocation mock = new MockLocation(start.lat, start.lng);
coords.Add(mock);
for (int distance = 0; distance < dist; distance += interval)
{
MockLocation coord = getDestinationLatLng(start.lat, start.lng, azimuth, coveredDist);
coveredDist += interval;
coords.Add(coord);
}
coords.Add(new MockLocation(end.lat, end.lng));
return coords;
}
public static double ToRadians(this double val)
{
return (Math.PI / 180) * val;
}
/**
* calculates the distance between two lat, long coordinate pairs
* #param start
* #param end
* #return
*/
private static double getPathLength(MockLocation start, MockLocation end)
{
double lat1Rads = ToRadians(start.lat);
double lat2Rads = ToRadians(end.lat);
double deltaLat = ToRadians(end.lat - start.lat);
double deltaLng = ToRadians(end.lng - start.lng);
double a = Math.Sin(deltaLat / 2) * Math.Sin(deltaLat / 2) + Math.Cos(lat1Rads) * Math.Cos(lat2Rads) * Math.Sin(deltaLng / 2) * Math.Sin(deltaLng / 2);
double c = 2 * Math.Atan2(Math.Sqrt(a), Math.Sqrt(1 - a));
double d = RADIUS_OF_EARTH * c;
return d;
}
/**
* returns the lat an long of destination point given the start lat, long, aziuth, and distance
* #param lat
* #param lng
* #param azimuth
* #param distance
* #return
*/
private static MockLocation getDestinationLatLng(double lat, double lng, double azimuth, double distance)
{
double radiusKm = RADIUS_OF_EARTH / 1000; //Radius of the Earth in km
double brng = ToRadians(azimuth); //Bearing is degrees converted to radians.
double d = distance / 1000; //Distance m converted to km
double lat1 = ToRadians(lat); //Current dd lat point converted to radians
double lon1 = ToRadians(lng); //Current dd long point converted to radians
double lat2 = Math.Asin(Math.Sin(lat1) * Math.Cos(d / radiusKm) + Math.Cos(lat1) * Math.Sin(d / radiusKm) * Math.Cos(brng));
double lon2 = lon1 + Math.Atan2(Math.Sin(brng) * Math.Sin(d / radiusKm) * Math.Cos(lat1), Math.Cos(d / radiusKm) - Math.Sin(lat1) * Math.Sin(lat2));
//convert back to degrees
lat2 = ToDegrees(lat2);
lon2 = ToDegrees(lon2);
return new MockLocation(lat2, lon2);
}
/**
* calculates the azimuth in degrees from start point to end point");
double startLat = ToRadians(start.lat);
* #param start
* #param end
* #return
*/
private static double calculateBearing(MockLocation start, MockLocation end)
{
double startLat = ToRadians(start.lat);
double startLong = ToRadians(start.lng);
double endLat = ToRadians(end.lat);
double endLong = ToRadians(end.lng);
double dLong = endLong - startLong;
double dPhi = Math.Log(Math.Tan((endLat / 2.0) + (Math.PI / 4.0)) / Math.Tan((startLat / 2.0) + (Math.PI / 4.0)));
if (Math.Abs(dLong) > Math.PI)
{
if (dLong > 0.0)
{
dLong = -(2.0 * Math.PI - dLong);
}
else
{
dLong = (2.0 * Math.PI + dLong);
}
}
double bearing = (ToDegrees(Math.Atan2(dLong, dPhi)) + 360.0) % 360.0;
return bearing;
}
public static double ToDegrees(double radians)
{
double degrees = (180 / Math.PI) * radians;
return (degrees);
}
public class MockLocation
{
public double lat;
public double lng;
public MockLocation(double lat, double lng)
{
this.lat = lat;
this.lng = lng;
}
public string toString()
{
return (lat + "," + lng).ToString();
}
}
}
}
I hope this will help other learners.
I have two geo coordinates (Start and End Points) and I am able to create a line between them. Suppose these two coordinates are at a distance of 30 meters then I need to find every geolocation of points at an interval of 3 meters. So there are 10 such points which are required.
I am able to find the points by some formulas but these points are not in the same direction as the line formed by start and end points.
What I have done so far is below...
using System;
namespace Test
{
public class AzimuthCalculator
{
public const double range = 0.00186411F; // in Miles
public const double rangeInMeter = 3F;
public const double factor = 0.003F;
public static void Main(String[] args)
{
double sLatitude = 28.6187763214111F;
double sLongitude = 77.2093048095703F;
double eLatitude = 28.6191763153134F;
double eLongitude = 77.2097146511078F;
Console.WriteLine($"Start Point : {sLatitude}, {sLongitude}");
Console.WriteLine($"End Point : {eLatitude},{eLongitude}");
double distance = CalculateDistance(sLatitude, sLongitude, eLatitude, eLongitude);
Console.WriteLine($"Distance : {distance} miles, {MilesToMeter(distance)} meter, {(distance * 1.60934)} kilometer");
distance = distance * 1.60934;
double numberOfIDS = distance/factor;
double azimuthAngle = DegreeBearing(sLatitude, sLongitude, eLatitude, eLongitude);
Console.WriteLine($"Azimuth : {azimuthAngle}");
Console.WriteLine($"IDS : {numberOfIDS}");
double constantAzimuth = (azimuthAngle/numberOfIDS);
azimuthAngle = constantAzimuth;
Console.WriteLine($"Original Azimuth : {azimuthAngle}");
double[] getAnotherPoint = pointRadialDistance(sLatitude, sLongitude, azimuthAngle, distance);
Console.WriteLine($"End Point : {getAnotherPoint[0]},{getAnotherPoint[1]}");
distance = 0.003; // 3 meter
getAnotherPoint = pointRadialDistance(sLatitude, sLongitude, azimuthAngle, distance);
int totalIDS = (Int32)(numberOfIDS);
for(int i=0;i<totalIDS;i++)
{
sLatitude = getAnotherPoint[0];
sLongitude = getAnotherPoint[1];
distance = 0.003;
Console.WriteLine($"new PointLatLng({getAnotherPoint[0]},{getAnotherPoint[1]}),");
getAnotherPoint = pointRadialDistance(sLatitude, sLongitude, azimuthAngle, distance);
}
Console.ReadLine();
}
static double rEarth = 6371.01F; // Earth radius in km
static double epsilon = 0.000001F;
public static double[] pointRadialDistance(double lat1, double lon1, double bearing, double distance)
{
double rlat1 = ToRad(lat1);
double rlon1 = ToRad(lon1);
double rbearing = ToRad(bearing);
double rdistance = distance / rEarth; // normalize linear distance to radian angle
double rlat = Math.Asin( Math.Sin(rlat1) * Math.Cos(rdistance) + Math.Cos(rlat1) * Math.Sin(rdistance) * Math.Cos(rbearing));
double rlon = 0.0F;
if ( Math.Cos(rlat) == 0 || Math.Abs(Math.Cos(rlat)) < epsilon) // Endpoint a pole
rlon=rlon1;
else
rlon = ((rlon1 - Math.Asin( Math.Sin(rbearing) * Math.Sin(rdistance) / Math.Cos(rlat) ) + Math.PI ) % (2*Math.PI) ) - Math.PI;
double lat = ToDegrees(rlat);
double lon = ToDegrees(rlon);
double[] listNew = new double[2];
listNew[0] = lat;
listNew[1] = lon;
return (listNew);
}
public static GeoLocation FindPointAtDistanceFrom(GeoLocation startPoint, double initialBearingRadians, double distanceKilometres)
{
const double radiusEarthKilometres = 6371.01;
var distRatio = distanceKilometres / radiusEarthKilometres;
var distRatioSine = Math.Sin(distRatio);
var distRatioCosine = Math.Cos(distRatio);
var startLatRad = DegreesToRadians(startPoint.Latitude);
var startLonRad = DegreesToRadians(startPoint.Longitude);
var startLatCos = Math.Cos(startLatRad);
var startLatSin = Math.Sin(startLatRad);
var endLatRads = Math.Asin((startLatSin * distRatioCosine) + (startLatCos * distRatioSine * Math.Cos(initialBearingRadians)));
var endLonRads = startLonRad
+ Math.Atan2(
Math.Sin(initialBearingRadians) * distRatioSine * startLatCos,
distRatioCosine - startLatSin * Math.Sin(endLatRads));
return new GeoLocation
{
Latitude = RadiansToDegrees(endLatRads),
Longitude = RadiansToDegrees(endLonRads)
};
}
public struct GeoLocation
{
public double Latitude { get; set; }
public double Longitude { get; set; }
}
public static double DegreesToRadians(double degrees)
{
const double degToRadFactor = Math.PI / 180;
return degrees * degToRadFactor;
}
public static double RadiansToDegrees(double radians)
{
const double radToDegFactor = 180 / Math.PI;
return radians * radToDegFactor;
}
public static double CalculateDistance(double sLatitude, double sLongitude, double eLatitude, double eLongitude)
{
var radiansOverDegrees = (Math.PI / 180.0);
var sLatitudeRadians = sLatitude * radiansOverDegrees;
var sLongitudeRadians = sLongitude * radiansOverDegrees;
var eLatitudeRadians = eLatitude * radiansOverDegrees;
var eLongitudeRadians = eLongitude * radiansOverDegrees;
var dLongitude = eLongitudeRadians - sLongitudeRadians;
var dLatitude = eLatitudeRadians - sLatitudeRadians;
var result1 = Math.Pow(Math.Sin(dLatitude / 2.0), 2.0) + Math.Cos(sLatitudeRadians) * Math.Cos(eLatitudeRadians) * Math.Pow(Math.Sin(dLongitude / 2.0), 2.0);
// Using 3956 as the number of miles around the earth
var result2 = 3956.0 * 2.0 * Math.Atan2(Math.Sqrt(result1), Math.Sqrt(1.0 - result1));
return result2;
}
static double DegreeBearing(double lat1, double lon1,double lat2, double lon2)
{
var dLon = ToRad(lon2 - lon1);
var dPhi = Math.Log(Math.Tan(ToRad(lat2) / 2 + Math.PI / 4) / Math.Tan(ToRad(lat1) / 2 + Math.PI / 4));
if (Math.Abs(dLon) > Math.PI)
dLon = dLon > 0 ? - (2 * Math.PI - dLon) : (2 * Math.PI + dLon);
return ToBearing(Math.Atan2(dLon, dPhi));
}
public static double ToRad(double degrees)
{
return degrees * (Math.PI / 180);
}
public static double ToDegrees(double radians)
{
return radians * 180 / Math.PI;
}
public static double ToBearing(double radians)
{
// convert radians to degrees (as bearing: 0...360)
return (ToDegrees(radians) + 360) % 360;
}
public static double MeterToMiles(double meter)
{
return (meter / 1609.344);
}
public static double MilesToMeter(double miles)
{
return (miles * 1609.344);
}
}
}
Why do you calculate wrong bearing from right one here constantAzimuth = (azimuthAngle/numberOfIDS); and use it later?
You can calculate intermediate points on the great circle path using approach described here (essentially it is SLERP - spherical linear interpolation)
Formula:
a = sin((1−f)⋅δ) / sin δ
b = sin(f⋅δ) / sin δ
x = a ⋅ cos φ1 ⋅ cos λ1 + b ⋅ cos φ2 ⋅ cos λ2
y = a ⋅ cos φ1 ⋅ sin λ1 + b ⋅ cos φ2 ⋅ sin λ2
z = a ⋅ sin φ1 + b ⋅ sin φ2
φi = atan2(z, √x² + y²)
λi = atan2(y, x)
where
f is fraction along great circle route (f=0 is point 1, f=1 is point 2),
δ is the angular distance d/R between the two points.
I am using a solution I've found in this post:
Polygon area calculation using Latitude and Longitude generated from Cartesian space and a world file
There is something wrong because the values I am getting are not real. For example we know a football field should have around 5,300.00 square meters, right? but the calculation is giving 5,759,154.21.
This is the code:
private static double CalculatePolygonArea(IList<Position> coordinates)
{
double area = 0;
if (coordinates.Count > 2)
{
for (var i = 0; i < coordinates.Count - 1; i++)
{
Position p1 = coordinates[i];
Position p2 = coordinates[i + 1];
area += (ConvertToRadian(p2.Longitude) - ConvertToRadian(p1.Longitude)) * (2 + Math.Sin(ConvertToRadian(p1.Latitude)) + Math.Sin(ConvertToRadian(p2.Latitude)));
}
area = area * 6378137 * 6378137 / 2;
}
return Math.Abs(area);
}
private static double ConvertToRadian(double input)
{
return input * Math.PI / 180;
}
What can be wrong here? Any help?
The area calculation you are using is just plain wrong.... :-/
I use the SphericalUtil.ComputeSignedArea method from Google's Android Maps Utils.
Note: Google's Java code for that is under Apache License Version 2.0, and I converted it to C#.
Looking up that football field up in one of my apps, I get: 4,461, not quite the actual 5,531 but not bad for using Google Map photos...
Here is just the ComputeSignedArea:
public static class SphericalUtil
{
const double EARTH_RADIUS = 6371009;
static double ToRadians(double input)
{
return input / 180.0 * Math.PI;
}
public static double ComputeSignedArea(IList<LatLng> path)
{
return ComputeSignedArea(path, EARTH_RADIUS);
}
static double ComputeSignedArea(IList<LatLng> path, double radius)
{
int size = path.Count;
if (size < 3) { return 0; }
double total = 0;
var prev = path[size - 1];
double prevTanLat = Math.Tan((Math.PI / 2 - ToRadians(prev.Latitude)) / 2);
double prevLng = ToRadians(prev.Longitude);
foreach (var point in path)
{
double tanLat = Math.Tan((Math.PI / 2 - ToRadians(point.Latitude)) / 2);
double lng = ToRadians(point.Longitude);
total += PolarTriangleArea(tanLat, lng, prevTanLat, prevLng);
prevTanLat = tanLat;
prevLng = lng;
}
return total * (radius * radius);
}
static double PolarTriangleArea(double tan1, double lng1, double tan2, double lng2)
{
double deltaLng = lng1 - lng2;
double t = tan1 * tan2;
return 2 * Math.Atan2(t * Math.Sin(deltaLng), 1 + t * Math.Cos(deltaLng));
}
}
I know, what I ask exist with Google Map, but I'm working with Xamarin.Forms.Map so.. I have to make it by my own.
However, I know how to get the center of my point, the POI (Point of Interest), but I don't know how to determine the zoom of the camera..
I searched on the web and from this post, I got redirected to the algorythm of Haversine.
However, I tried the code given but it doesn't work.. I know how to find the POI, the 2 farest point, but I can't determine the zoom..
Any idea please? :/
Note: There is the code if you want to know something about what I tried
#region Camera focus method
private static void OnCustomPinsPropertyChanged(BindableObject bindable, object oldValue, object newValue)
{
CustomMap customMap = ((CustomMap)bindable);
if (customMap.CameraFocusParameter == CameraFocusReference.OnPins)
{
List<Position> PositionPins = new List<Position>();
bool onlyOnePointPresent;
foreach (CustomPin pin in (newValue as List<CustomPin>))
{
PositionPins.Add(pin.Position);
}
Position CentralPosition = GetCentralPosition(PositionPins);
if (PositionPins.Count > 1)
{
Position[] FarestPoints = GetTwoFarestPointsOfCenterPointReference(PositionPins, CentralPosition);
customMap.CameraFocus = GetPositionAndZoomLevelForCameraAboutPositions(FarestPoints);
onlyOnePointPresent = false;
}
else
{
customMap.CameraFocus = new CameraFocusData() { Position = CentralPosition };
onlyOnePointPresent = true;
}
customMap.MoveToRegion(MapSpan.FromCenterAndRadius(customMap.CameraFocus.Position,
(!onlyOnePointPresent) ? (customMap.CameraFocus.Distance) : (new Distance(5))));
}
}
public static Position GetCentralPosition(List<Position> positions)
{
if (positions.Count == 1)
{
foreach (Position pos in positions)
{
return (pos);
}
}
double lat = 0;
double lng = 0;
foreach (var pos in positions)
{
lat += pos.Latitude;
lng += pos.Longitude;
}
var total = positions.Count;
lat = lat / total;
lng = lng / total;
return new Position(lat, lng);
}
public class DataCalc
{
public Position Pos { get; set; }
public double Distance { get; set; }
}
public static Position[] GetTwoFarestPointsOfCenterPointReference(List<Position> farestPosition, Position centerPosition)
{
Position[] FarestPos = new Position[2];
List<DataCalc> dataCalc = new List<DataCalc>();
Debug.WriteLine("So the center is on [{0}]/[{1}]", centerPosition.Latitude, centerPosition.Longitude);
foreach (Position pos in farestPosition)
{
dataCalc.Add(new DataCalc()
{
Pos = pos,
Distance = Math.Sqrt(Math.Pow(pos.Latitude - centerPosition.Latitude, 2) + Math.Pow(pos.Longitude - centerPosition.Longitude, 2))
});
}
DataCalc First = new DataCalc() { Distance = 0 };
foreach (DataCalc dc in dataCalc)
{
if (dc.Distance > First.Distance)
{
First = dc;
}
}
Debug.WriteLine("The farest one is on [{0}]/[{1}]", First.Pos.Latitude, First.Pos.Longitude);
DataCalc Second = new DataCalc() { Distance = 0 };
foreach (DataCalc dc in dataCalc)
{
if (dc.Distance > Second.Distance
&& (dc.Pos.Latitude != First.Pos.Latitude && dc.Pos.Longitude != First.Pos.Longitude))
{
Second = dc;
}
}
Debug.WriteLine("the second is on [{0}]/[{1}]", Second.Pos.Latitude, Second.Pos.Longitude);
FarestPos[0] = First.Pos;
FarestPos[1] = Second.Pos;
return (FarestPos);
}
public class CameraFocusData
{
public Position Position { get; set; }
public Distance Distance { get; set; }
}
//HAVERSINE
public static CameraFocusData GetPositionAndZoomLevelForCameraAboutPositions(Position[] FarestPoints)
{
double earthRadius = 6371000; //metros
Position pos1 = FarestPoints[0];
Position pos2 = FarestPoints[1];
double latitud1Radianes = pos1.Latitude * (Math.PI / 180.0);
double latitud2Radianes = pos2.Latitude * (Math.PI / 180.0);
double longitud1Radianes = pos2.Longitude * (Math.PI / 180.0);
double longitud2Radianes = pos2.Longitude * (Math.PI / 180.0);
double deltaLatitud = (pos2.Latitude - pos1.Latitude) * (Math.PI / 180.0);
double deltaLongitud = (pos2.Longitude - pos1.Longitude) * (Math.PI / 180.0);
double sum1 = Math.Sin(deltaLatitud / 2) * Math.Sin(deltaLatitud / 2);
double sum2 = Math.Cos(latitud1Radianes) * Math.Cos(latitud2Radianes) * Math.Sin(deltaLongitud / 2) * Math.Sin(deltaLongitud / 2);
var a = sum1 + sum2;
var c = 2 * Math.Atan2(Math.Sqrt(a), Math.Sqrt(1 - a));
var distance = earthRadius * c;
/* lt is deltaLatitud
* lng is deltaLongitud*/
var Bx = Math.Cos(latitud2Radianes) * Math.Cos(deltaLongitud);
var By = Math.Cos(latitud2Radianes) * Math.Sin(deltaLongitud);
var lt = Math.Atan2(Math.Sin(latitud1Radianes) + Math.Sin(latitud2Radianes),
Math.Sqrt((Math.Cos(latitud1Radianes) + Bx) * (Math.Cos(latitud2Radianes) + Bx) + By * By));//Latitud del punto medio
var lng = longitud1Radianes + Math.Atan2(By, Math.Cos(longitud1Radianes) + Bx);//Longitud del punto medio
Debug.WriteLine("the final pos of the camera is on [{0}]/[{1}]", lt, lng);
return (new CameraFocusData() { Position = new Position(lt, lng), Distance = new Distance(distance + 0.2) });
}
#endregion
I then found the solution, there is the code for it, it has been wrote to be put into your custom map.
Here, private static void OnCustomPinsPropertyChanged(BindableObject bindable, object oldValue, object newValue) is a method which is called by my List<CustomPins> but you can use a different method.
public static readonly BindableProperty CustomPinsProperty =
BindableProperty.Create(nameof(CustomPins), typeof(IList<CustomPin>), typeof(CustomMap), null,
propertyChanged: OnCustomPinsPropertyChanged);
Also, you can add the lat/long of the user, I didn't do it because of my needs which are without the pos of the user :).
Finaly, you can add a multiplicator for the zoom, I mean, you could say, hmm, the zoom is to far for me, then ok, do like me and multiplicate the double distance value by something as 0.7 or 0.6 :)
#region Camera focus definition
public class CameraFocusData
{
public Position Position { get; set; }
public Distance Distance { get; set; }
}
private static void OnCustomPinsPropertyChanged(BindableObject bindable, object oldValue, object newValue)
{
CustomMap customMap = ((CustomMap)bindable);
if (customMap.CameraFocusParameter == CameraFocusReference.OnPins)
{
List<double> latitudes = new List<double>();
List<double> longitudes = new List<double>();
foreach (CustomPin pin in (newValue as List<CustomPin>))
{
latitudes.Add(pin.Position.Latitude);
longitudes.Add(pin.Position.Longitude);
}
double lowestLat = latitudes.Min();
double highestLat = latitudes.Max();
double lowestLong = longitudes.Min();
double highestLong = longitudes.Max();
double finalLat = (lowestLat + highestLat) / 2;
double finalLong = (lowestLong + highestLong) / 2;
double distance = DistanceCalculation.GeoCodeCalc.CalcDistance(lowestLat, lowestLong, highestLat, highestLong, DistanceCalculation.GeoCodeCalcMeasurement.Kilometers);
customMap.MoveToRegion(MapSpan.FromCenterAndRadius(new Position(finalLat, finalLong), Distance.FromKilometers(distance * 0.7)));
}
}
private class DistanceCalculation
{
public static class GeoCodeCalc
{
public const double EarthRadiusInMiles = 3956.0;
public const double EarthRadiusInKilometers = 6367.0;
public static double ToRadian(double val) { return val * (Math.PI / 180); }
public static double DiffRadian(double val1, double val2) { return ToRadian(val2) - ToRadian(val1); }
public static double CalcDistance(double lat1, double lng1, double lat2, double lng2)
{
return CalcDistance(lat1, lng1, lat2, lng2, GeoCodeCalcMeasurement.Miles);
}
public static double CalcDistance(double lat1, double lng1, double lat2, double lng2, GeoCodeCalcMeasurement m)
{
double radius = GeoCodeCalc.EarthRadiusInMiles;
if (m == GeoCodeCalcMeasurement.Kilometers) { radius = GeoCodeCalc.EarthRadiusInKilometers; }
return radius * 2 * Math.Asin(Math.Min(1, Math.Sqrt((Math.Pow(Math.Sin((DiffRadian(lat1, lat2)) / 2.0), 2.0) + Math.Cos(ToRadian(lat1)) * Math.Cos(ToRadian(lat2)) * Math.Pow(Math.Sin((DiffRadian(lng1, lng2)) / 2.0), 2.0)))));
}
}
public enum GeoCodeCalcMeasurement : int
{
Miles = 0,
Kilometers = 1
}
}
#endregion
Have fun !
UPDATE of DEC 2018
I worked on a solution which I hosted a long time ago on my repo, with the other POCs https://github.com/Emixam23/XamarinByEmixam23/tree/master/Detailed%20Part/Controls/Map/MapPinsProject
I wanted a class that can convert one system to another.
I've found a source code in python and tried to port it into C#.
This is the python source. From here
import math
class GlobalMercator(object):
def __init__(self, tileSize=256):
"Initialize the TMS Global Mercator pyramid"
self.tileSize = tileSize
self.initialResolution = 2 * math.pi * 6378137 / self.tileSize
# 156543.03392804062 for tileSize 256 pixels
self.originShift = 2 * math.pi * 6378137 / 2.0
# 20037508.342789244
def LatLonToMeters(self, lat, lon ):
"Converts given lat/lon in WGS84 Datum to XY in Spherical Mercator EPSG:900913"
mx = lon * self.originShift / 180.0
my = math.log( math.tan((90 + lat) * math.pi / 360.0 )) / (math.pi / 180.0)
my = my * self.originShift / 180.0
return mx, my
def MetersToLatLon(self, mx, my ):
"Converts XY point from Spherical Mercator EPSG:900913 to lat/lon in WGS84 Datum"
lon = (mx / self.originShift) * 180.0
lat = (my / self.originShift) * 180.0
lat = 180 / math.pi * (2 * math.atan( math.exp( lat * math.pi / 180.0)) - math.pi / 2.0)
return lat, lon
def PixelsToMeters(self, px, py, zoom):
"Converts pixel coordinates in given zoom level of pyramid to EPSG:900913"
res = self.Resolution( zoom )
mx = px * res - self.originShift
my = py * res - self.originShift
return mx, my
def MetersToPixels(self, mx, my, zoom):
"Converts EPSG:900913 to pyramid pixel coordinates in given zoom level"
res = self.Resolution( zoom )
px = (mx + self.originShift) / res
py = (my + self.originShift) / res
return px, py
def PixelsToTile(self, px, py):
"Returns a tile covering region in given pixel coordinates"
tx = int( math.ceil( px / float(self.tileSize) ) - 1 )
ty = int( math.ceil( py / float(self.tileSize) ) - 1 )
return tx, ty
def PixelsToRaster(self, px, py, zoom):
"Move the origin of pixel coordinates to top-left corner"
mapSize = self.tileSize << zoom
return px, mapSize - py
def MetersToTile(self, mx, my, zoom):
"Returns tile for given mercator coordinates"
px, py = self.MetersToPixels( mx, my, zoom)
return self.PixelsToTile( px, py)
def TileBounds(self, tx, ty, zoom):
"Returns bounds of the given tile in EPSG:900913 coordinates"
minx, miny = self.PixelsToMeters( tx*self.tileSize, ty*self.tileSize, zoom )
maxx, maxy = self.PixelsToMeters( (tx+1)*self.tileSize, (ty+1)*self.tileSize, zoom )
return ( minx, miny, maxx, maxy )
def TileLatLonBounds(self, tx, ty, zoom ):
"Returns bounds of the given tile in latutude/longitude using WGS84 datum"
bounds = self.TileBounds( tx, ty, zoom)
minLat, minLon = self.MetersToLatLon(bounds[0], bounds[1])
maxLat, maxLon = self.MetersToLatLon(bounds[2], bounds[3])
return ( minLat, minLon, maxLat, maxLon )
def Resolution(self, zoom ):
"Resolution (meters/pixel) for given zoom level (measured at Equator)"
# return (2 * math.pi * 6378137) / (self.tileSize * 2**zoom)
return self.initialResolution / (2**zoom)
def ZoomForPixelSize(self, pixelSize ):
"Maximal scaledown zoom of the pyramid closest to the pixelSize."
for i in range(30):
if pixelSize > self.Resolution(i):
return i-1 if i!=0 else 0 # We don't want to scale up
def GoogleTile(self, tx, ty, zoom):
"Converts TMS tile coordinates to Google Tile coordinates"
# coordinate origin is moved from bottom-left to top-left corner of the extent
return tx, (2**zoom - 1) - ty
def QuadTree(self, tx, ty, zoom ):
"Converts TMS tile coordinates to Microsoft QuadTree"
quadKey = ""
ty = (2**zoom - 1) - ty
for i in range(zoom, 0, -1):
digit = 0
mask = 1 << (i-1)
if (tx & mask) != 0:
digit += 1
if (ty & mask) != 0:
digit += 2
quadKey += str(digit)
return quadKey
Here is my C# port.
public class GlobalMercator {
private Int32 TileSize;
private Double InitialResolution;
private Double OriginShift;
private const Int32 EarthRadius = 6378137;
public GlobalMercator() {
TileSize = 256;
InitialResolution = 2 * Math.PI * EarthRadius / TileSize;
OriginShift = 2 * Math.PI * EarthRadius / 2;
}
public DPoint LatLonToMeters(Double lat, Double lon) {
var p = new DPoint();
p.X = lon * OriginShift / 180;
p.Y = Math.Log(Math.Tan((90 + lat) * Math.PI / 360)) / (Math.PI / 180);
p.Y = p.Y * OriginShift / 180;
return p;
}
public GeoPoint MetersToLatLon(DPoint m) {
var ll = new GeoPoint();
ll.Longitude = (m.X / OriginShift) * 180;
ll.Latitude = (m.Y / OriginShift) * 180;
ll.Latitude = 180 / Math.PI * (2 * Math.Atan(Math.Exp(ll.Latitude * Math.PI / 180)) - Math.PI / 2);
return ll;
}
public DPoint PixelsToMeters(DPoint p, Int32 zoom) {
var res = Resolution(zoom);
var met = new DPoint();
met.X = p.X * res - OriginShift;
met.Y = p.Y * res - OriginShift;
return met;
}
public DPoint MetersToPixels(DPoint m, Int32 zoom) {
var res = Resolution(zoom);
var pix = new DPoint();
pix.X = (m.X + OriginShift) / res;
pix.Y = (m.Y + OriginShift) / res;
return pix;
}
public Point PixelsToTile(DPoint p) {
var t = new Point();
t.X = (Int32)Math.Ceiling(p.X / (Double)TileSize) - 1;
t.Y = (Int32)Math.Ceiling(p.Y / (Double)TileSize) - 1;
return t;
}
public Point PixelsToRaster(Point p, Int32 zoom) {
var mapSize = TileSize << zoom;
return new Point(p.X, mapSize - p.Y);
}
public Point MetersToTile(Point m, Int32 zoom) {
var p = MetersToPixels(m, zoom);
return PixelsToTile(p);
}
public Pair<DPoint> TileBounds(Point t, Int32 zoom) {
var min = PixelsToMeters(new DPoint(t.X * TileSize, t.Y * TileSize), zoom);
var max = PixelsToMeters(new DPoint((t.X + 1) * TileSize, (t.Y + 1) * TileSize), zoom);
return new Pair<DPoint>(min, max);
}
public Pair<GeoPoint> TileLatLonBounds(Point t, Int32 zoom) {
var bound = TileBounds(t, zoom);
var min = MetersToLatLon(bound.Min);
var max = MetersToLatLon(bound.Max);
return new Pair<GeoPoint>(min, max);
}
public Double Resolution(Int32 zoom) {
return InitialResolution / (2 ^ zoom);
}
public Double ZoomForPixelSize(Double pixelSize) {
for (var i = 0; i < 30; i++)
if (pixelSize > Resolution(i))
return i != 0 ? i - 1 : 0;
throw new InvalidOperationException();
}
public Point ToGoogleTile(Point t, Int32 zoom) {
return new Point(t.X, ((Int32)Math.Pow(2, zoom) - 1) - t.Y);
}
public Point ToTmsTile(Point t, Int32 zoom) {
return new Point(t.X, ((Int32)Math.Pow(2, zoom) - 1) - t.Y);
}
}
public struct Point {
public Point(Int32 x, Int32 y)
: this() {
X = x;
Y = y;
}
public Int32 X { get; set; }
public Int32 Y { get; set; }
}
public struct DPoint {
public DPoint(Double x, Double y)
: this() {
this.X = x;
this.Y = y;
}
public Double X { get; set; }
public Double Y { get; set; }
public static implicit operator DPoint(Point p) {
return new DPoint(p.X, p.Y);
}
}
public class GeoPoint {
public Double Latitude { get; set; }
public Double Longitude { get; set; }
}
public class Pair<T> {
public Pair() {}
public Pair(T min, T max) {
Min = min;
Max = max;
}
public T Min { get; set; }
public T Max { get; set; }
}
I have two questions.
Did I port the code correctly? (I intentionally omitted one method as I don't use it and added one my own)
Here I have coordinates
WGS84 datum (longitude/latitude):
-123.75 36.59788913307022
-118.125 40.97989806962013
Spherical Mercator (meters):
-13775786.985667605 4383204.9499851465
-13149614.849955441 5009377.085697312
Pixels
2560 6144 2816 6400
Google
x:10, y:24, z:6
TMS
x:10, y:39, z:6
QuadTree
023010
How should I chain the methods so that I get from Google's tile coordinates (10, 24, 6) the spherical mercator meters?
Update
Finding answer for my second question is more important for me.
There's at least one bug in your class:
public Double Resolution(Int32 zoom) {
return InitialResolution / (2 ^ zoom); // BAD CODE, USE Math.Pow, not ^
}
Where you've mistaken the binary XOR operator for the exponent operator.
I've rewritten the code, made most functions static, and added a few more relevant functions:
/// <summary>
/// Conversion routines for Google, TMS, and Microsoft Quadtree tile representations, derived from
/// http://www.maptiler.org/google-maps-coordinates-tile-bounds-projection/
/// </summary>
public class WebMercator
{
private const int TileSize = 256;
private const int EarthRadius = 6378137;
private const double InitialResolution = 2 * Math.PI * EarthRadius / TileSize;
private const double OriginShift = 2 * Math.PI * EarthRadius / 2;
//Converts given lat/lon in WGS84 Datum to XY in Spherical Mercator EPSG:900913
public static Point LatLonToMeters(double lat, double lon)
{
var p = new Point();
p.X = lon * OriginShift / 180;
p.Y = Math.Log(Math.Tan((90 + lat) * Math.PI / 360)) / (Math.PI / 180);
p.Y = p.Y * OriginShift / 180;
return p;
}
//Converts XY point from (Spherical) Web Mercator EPSG:3785 (unofficially EPSG:900913) to lat/lon in WGS84 Datum
public static Point MetersToLatLon(Point m)
{
var ll = new Point();
ll.X = (m.X / OriginShift) * 180;
ll.Y = (m.Y / OriginShift) * 180;
ll.Y = 180 / Math.PI * (2 * Math.Atan(Math.Exp(ll.Y * Math.PI / 180)) - Math.PI / 2);
return ll;
}
//Converts pixel coordinates in given zoom level of pyramid to EPSG:900913
public static Point PixelsToMeters(Point p, int zoom)
{
var res = Resolution(zoom);
var met = new Point();
met.X = p.X * res - OriginShift;
met.Y = p.Y * res - OriginShift;
return met;
}
//Converts EPSG:900913 to pyramid pixel coordinates in given zoom level
public static Point MetersToPixels(Point m, int zoom)
{
var res = Resolution(zoom);
var pix = new Point();
pix.X = (m.X + OriginShift) / res;
pix.Y = (m.Y + OriginShift) / res;
return pix;
}
//Returns a TMS (NOT Google!) tile covering region in given pixel coordinates
public static Tile PixelsToTile(Point p)
{
var t = new Tile();
t.X = (int)Math.Ceiling(p.X / (double)TileSize) - 1;
t.Y = (int)Math.Ceiling(p.Y / (double)TileSize) - 1;
return t;
}
public static Point PixelsToRaster(Point p, int zoom)
{
var mapSize = TileSize << zoom;
return new Point(p.X, mapSize - p.Y);
}
//Returns tile for given mercator coordinates
public static Tile MetersToTile(Point m, int zoom)
{
var p = MetersToPixels(m, zoom);
return PixelsToTile(p);
}
//Returns bounds of the given tile in EPSG:900913 coordinates
public static Rect TileBounds(Tile t, int zoom)
{
var min = PixelsToMeters(new Point(t.X * TileSize, t.Y * TileSize), zoom);
var max = PixelsToMeters(new Point((t.X + 1) * TileSize, (t.Y + 1) * TileSize), zoom);
return new Rect(min, max);
}
//Returns bounds of the given tile in latutude/longitude using WGS84 datum
public static Rect TileLatLonBounds(Tile t, int zoom)
{
var bound = TileBounds(t, zoom);
var min = MetersToLatLon(new Point(bound.Left, bound.Top));
var max = MetersToLatLon(new Point(bound.Right, bound.Bottom));
return new Rect(min, max);
}
//Resolution (meters/pixel) for given zoom level (measured at Equator)
public static double Resolution(int zoom)
{
return InitialResolution / (Math.Pow(2, zoom));
}
public static double ZoomForPixelSize(double pixelSize)
{
for (var i = 0; i < 30; i++)
if (pixelSize > Resolution(i))
return i != 0 ? i - 1 : 0;
throw new InvalidOperationException();
}
// Switch to Google Tile representation from TMS
public static Tile ToGoogleTile(Tile t, int zoom)
{
return new Tile(t.X, ((int)Math.Pow(2, zoom) - 1) - t.Y);
}
// Switch to TMS Tile representation from Google
public static Tile ToTmsTile(Tile t, int zoom)
{
return new Tile(t.X, ((int)Math.Pow(2, zoom) - 1) - t.Y);
}
//Converts TMS tile coordinates to Microsoft QuadTree
public static string QuadTree(int tx, int ty, int zoom)
{
var quadtree = "";
ty = ((1 << zoom) - 1) - ty;
for (var i = zoom; i >= 1; i--)
{
var digit = 0;
var mask = 1 << (i - 1);
if ((tx & mask) != 0)
digit += 1;
if ((ty & mask) != 0)
digit += 2;
quadtree += digit;
}
return quadtree;
}
//Converts a quadtree to tile coordinates
public static Tile QuadTreeToTile(string quadtree, int zoom)
{
int tx= 0, ty = 0;
for (var i = zoom; i >= 1; i--)
{
var ch = quadtree[zoom - i];
var mask = 1 << (i - 1);
var digit = ch - '0';
if ((digit & 1) != 0)
tx += mask;
if ((digit & 2) != 0)
ty += mask;
}
ty = ((1 << zoom) - 1) - ty;
return new Tile(tx, ty);
}
//Converts a latitude and longitude to quadtree at the specified zoom level
public static string LatLonToQuadTree(Point latLon, int zoom)
{
Point m = LatLonToMeters(latLon.Y, latLon.X);
Tile t = MetersToTile(m, zoom);
return QuadTree(t.X, t.Y, zoom);
}
//Converts a quadtree location into a latitude/longitude bounding rectangle
public static Rect QuadTreeToLatLon(string quadtree)
{
int zoom = quadtree.Length;
Tile t = QuadTreeToTile(quadtree, zoom);
return TileLatLonBounds(t, zoom);
}
//Returns a list of all of the quadtree locations at a given zoom level within a latitude/longude box
public static List<string> GetQuadTreeList(int zoom, Point latLonMin, Point latLonMax)
{
if (latLonMax.Y< latLonMin.Y|| latLonMax.X< latLonMin.X)
return null;
Point mMin = LatLonToMeters(latLonMin.Y, latLonMin.X);
Tile tmin = MetersToTile(mMin, zoom);
Point mMax = LatLonToMeters(latLonMax.Y, latLonMax.X);
Tile tmax = MetersToTile(mMax, zoom);
var arr = new List<string>();
for (var ty = tmin.Y; ty <= tmax.Y; ty++)
{
for (var tx = tmin.X; tx <= tmax.X; tx++)
{
var quadtree = QuadTree(tx, ty, zoom);
arr.Add(quadtree);
}
}
return arr;
}
}
/// <summary>
/// Reference to a Tile X, Y index
/// </summary>
public class Tile
{
public Tile() { }
public Tile(int x, int y)
{
X = x;
Y = y;
}
public int X { get; set; }
public int Y { get; set; }
}
To solve your second question, use the following sequence:
int zoom = 6;
Tile googleTile = new Tile(10,24);
Tile tmsTile = GlobalMercator.ToTmsTile(googleTile, zoom);
Rect r3 = GlobalMercator.TileLatLonBounds(tmsTile, zoom);
var tl = GlobalMercator.LatLonToMeters(r3.Top, r3.Left);
var br = GlobalMercator.LatLonToMeters(r3.Bottom, r3.Right);
Debug.WriteLine("{0:0.000} {1:0.000}", tl.X, tl.Y);
Debug.WriteLine("{0:0.000} {1:0.000}", br.X, br.Y);
// -13775787.000 4383205.000
// -13149615.000 5009376.500
The best opensource solution for converting coordinates from one projection to another is Proj4 originally written in c but ported to numerous programming languages. The port to c# that I have tried and used is DotSpatial Projections found on CodePlex. It is easy to find out how to use it based on the examples. The only thing you need to know are conversion parameters for your case.
CoordinateSharp is available on NuGet. It's light weight and makes coordinate conversions really. It's not designed for mapping, but strait up conversions (and location based celestial information) if that is a factor.
Example
Coordinate c = new Coordinate(myLat,myLong);
c.UTM; //Outputs UTM string
c.UTM.Easting //UTM easting property
A couple of pointers for anyone reading that wants to use Oybek excellent code:
You need to add using System.Windows but also Add a Reference to the WindowsBase assembly, otherwise VS wont find Point and Rect.
Note that just must not use System.Drawing
And here's a new function that will convert Zoom lat/lng to a Google Tile:
public static Tile LatLonToGoogleTile(Point latLon, int zoom)
{
Point m = LatLonToMeters(latLon.Y, latLon.X);
Tile t = MetersToTile(m, zoom);
return ToGoogleTile(t, zoom);
}