Emixam23 Emixam23 - 2 months ago 10
C# Question

Determine the zoom level to cover all marker about lat/lng

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

Answer

I then find the solution, there is the code for it, it has been code 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 !