summaryrefslogtreecommitdiff
path: root/Xamarin.Forms.Maps/MapSpan.cs
blob: 883e630f733a21e28205984e3a15b439547ba3df (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
using System;

namespace Xamarin.Forms.Maps
{
	public sealed class MapSpan
	{
		const double EarthRadiusKm = 6371;
		const double EarthCircumferenceKm = EarthRadiusKm * 2 * Math.PI;
		const double MinimumRangeDegrees = 0.001 / EarthCircumferenceKm * 360; // 1 meter

		public MapSpan(Position center, double latitudeDegrees, double longitudeDegrees)
		{
			Center = center;
			LatitudeDegrees = Math.Min(Math.Max(latitudeDegrees, MinimumRangeDegrees), 90.0);
			LongitudeDegrees = Math.Min(Math.Max(longitudeDegrees, MinimumRangeDegrees), 180.0);
		}

		public Position Center { get; }

		public double LatitudeDegrees { get; }

		public double LongitudeDegrees { get; }

		public Distance Radius
		{
			get
			{
				double latKm = LatitudeDegreesToKm(LatitudeDegrees);
				double longKm = LongitudeDegreesToKm(Center, LongitudeDegrees);
				return new Distance(1000 * Math.Min(latKm, longKm) / 2);
			}
		}

		public MapSpan ClampLatitude(double north, double south)
		{
			north = Math.Min(Math.Max(north, 0), 90);
			south = Math.Max(Math.Min(south, 0), -90);
			double lat = Math.Max(Math.Min(Center.Latitude, north), south);
			double maxDLat = Math.Min(north - lat, -south + lat) * 2;
			return new MapSpan(new Position(lat, Center.Longitude), Math.Min(LatitudeDegrees, maxDLat), LongitudeDegrees);
		}

		public override bool Equals(object obj)
		{
			if (ReferenceEquals(null, obj))
				return false;
			if (ReferenceEquals(this, obj))
				return true;
			return obj is MapSpan && Equals((MapSpan)obj);
		}

		public static MapSpan FromCenterAndRadius(Position center, Distance radius)
		{
			return new MapSpan(center, 2 * DistanceToLatitudeDegrees(radius), 2 * DistanceToLongitudeDegrees(center, radius));
		}

		public override int GetHashCode()
		{
			unchecked
			{
				int hashCode = Center.GetHashCode();
				hashCode = (hashCode * 397) ^ LongitudeDegrees.GetHashCode();
				hashCode = (hashCode * 397) ^ LatitudeDegrees.GetHashCode();
				return hashCode;
			}
		}

		public static bool operator ==(MapSpan left, MapSpan right)
		{
			return Equals(left, right);
		}

		public static bool operator !=(MapSpan left, MapSpan right)
		{
			return !Equals(left, right);
		}

		public MapSpan WithZoom(double zoomFactor)
		{
			double maxDLat = Math.Min(90 - Center.Latitude, 90 + Center.Latitude) * 2;
			return new MapSpan(Center, Math.Min(LatitudeDegrees / zoomFactor, maxDLat), LongitudeDegrees / zoomFactor);
		}

		static double DistanceToLatitudeDegrees(Distance distance)
		{
			return distance.Kilometers / EarthCircumferenceKm * 360;
		}

		static double DistanceToLongitudeDegrees(Position position, Distance distance)
		{
			double latCircumference = LatitudeCircumferenceKm(position);
			return distance.Kilometers / latCircumference * 360;
		}

		bool Equals(MapSpan other)
		{
			return Center.Equals(other.Center) && LongitudeDegrees.Equals(other.LongitudeDegrees) && LatitudeDegrees.Equals(other.LatitudeDegrees);
		}

		static double LatitudeCircumferenceKm(Position position)
		{
			return EarthCircumferenceKm * Math.Cos(position.Latitude * Math.PI / 180.0);
		}

		static double LatitudeDegreesToKm(double latitudeDegrees)
		{
			return EarthCircumferenceKm * latitudeDegrees / 360;
		}

		static double LongitudeDegreesToKm(Position position, double longitudeDegrees)
		{
			double latCircumference = LatitudeCircumferenceKm(position);
			return latCircumference * longitudeDegrees / 360;
		}
	}
}