/// <summary> /// Calculates destination point given distance and bearing from start point. /// </summary> /// <param name="Bearing">Initial GPS bearing in degrees to the destination point. The valid range of values is [0, 360). See Bearing* constants.</param> /// <param name="Distance">Distance in metres between the start point and the destination point.</param> /// <returns>GPS location of the destination point.</returns> /// <remarks>Formula source: http://www.movable-type.co.uk/scripts/latlong.html .</remarks> public GpsLocation GoVector(double Bearing, double Distance) { double lat1 = (double)Latitude; double lon1 = (double)Longitude; double lat1Rad = lat1.ToRadians(); double lon1Rad = lon1.ToRadians(); double brnRad = Bearing.ToRadians(); // Formula: // φ2 = asin(sin φ1 ⋅ cos δ + cos φ1 ⋅ sin δ ⋅ cos θ) // λ2 = λ1 + atan2(sin θ ⋅ sin δ ⋅ cos φ1, cos δ − sin φ1 ⋅ sin φ2) // where φ is latitude, λ is longitude, θ is the bearing(clockwise from north), δ is the angular distance d / R; d being the distance travelled, R the earth’s radius double lat2Rad = Math.Asin(Math.Sin(lat1Rad) * Math.Cos(Distance / EarthRadius) + Math.Cos(lat1Rad) * Math.Sin(Distance / EarthRadius) * Math.Cos(brnRad)); double lon2Rad = lon1Rad + Math.Atan2(Math.Sin(brnRad) * Math.Sin(Distance / EarthRadius) * Math.Cos(lat1Rad), Math.Cos(Distance / EarthRadius) - Math.Sin(lat1Rad) * Math.Sin(lat2Rad)); double lat2 = lat2Rad.ToDegrees(); double lon2 = lon2Rad.ToDegrees(); // Normalize longitude. lon2 = (lon2 + 540.0) % 360.0 - 180.0; if (lon2 == -180.0) { lon2 = 180.0; } GpsLocation res = new GpsLocation((decimal)lat2, (decimal)lon2); return(res); }
/// <summary> /// Calculates distance to another location in metres using Haversine formula. /// </summary> /// <param name="TargetLocation">Target location to which the distance is calculated.</param> /// <returns>Distance from this location to the target location in metres.</returns> /// <remarks>Formula source: http://www.movable-type.co.uk/scripts/latlong.html .</remarks> public double DistanceTo(GpsLocation TargetLocation) { double lat1 = (double)Latitude; double lon1 = (double)Longitude; double lat2 = (double)TargetLocation.Latitude; double lon2 = (double)TargetLocation.Longitude; // Haversine formula: // a = sin²(Δφ/2) + cos φ1 ⋅ cos φ2 ⋅ sin²(Δλ/2) // c = 2 ⋅ atan2(√a, √(1−a)) // d = R ⋅ c // where φ is latitude, λ is longitude, R is earth’s radius (mean radius = 6,371 km). double lat1Rad = lat1.ToRadians(); double lat2Rad = lat2.ToRadians(); double latDiffRad = (lat2 - lat1).ToRadians(); double lonDiffRad = (lon2 - lon1).ToRadians(); double a = Math.Sin(latDiffRad / 2) * Math.Sin(latDiffRad / 2) + Math.Cos(lat1Rad) * Math.Cos(lat2Rad) * Math.Sin(lonDiffRad / 2) * Math.Sin(lonDiffRad / 2); double c = 2 * Math.Atan2(Math.Sqrt(a), Math.Sqrt(1 - a)); double res = EarthRadius * c; return(res); }
/// <summary> /// Calculates final bearing from the start point to the destination point. /// </summary> /// <param name="Destination">Destination location.</param> /// <returns>Final bearing from the start point to the destination point.</returns> /// <remarks>Formula source: http://www.movable-type.co.uk/scripts/latlong.html .</remarks> public double FinalBearingTo(GpsLocation Destination) { // For final bearing, simply take the initial bearing from the end point to the start point and reverse it (using θ = (θ+180) % 360). double reverseBrng = Destination.InitialBearingTo(this); double res = (reverseBrng + 180.0) % 360.0; return(res); }
public override bool Equals(object obj) { if (!(obj is GpsLocation)) { return(false); } GpsLocation val = (GpsLocation)obj; return(Latitude.Equals(val.Latitude) && Longitude.Equals(val.Longitude)); }
/// <summary> /// Calculates GPS square from the given centre location using a radius. /// </summary> /// <param name="Radius">Half of a distance between oposite sides.</param> public GpsSquare(GpsLocation Centre, double Radius) { // We calculate positions of square mid points of top and bottom sides - i.e. points in the center of square sides. MidTop = Centre.GoVector(GpsLocation.BearingNorth, Radius); MidBottom = Centre.GoVector(GpsLocation.BearingSouth, Radius); // From these mid points, we navigate use West and East bearing to go to the square corners. LeftTop = MidTop.GoVector(GpsLocation.BearingWest, Radius); RightTop = MidTop.GoVector(GpsLocation.BearingEast, Radius); LeftBottom = MidBottom.GoVector(GpsLocation.BearingWest, Radius); RightBottom = MidBottom.GoVector(GpsLocation.BearingEast, Radius); }
/// <summary> /// Basic square constructor. /// </summary> /// <param name="LeftTop">Location of the left-top corner of the square.</param> /// <param name="RightTop">Location of the right-top corner of the square.</param> /// <param name="LeftBottom">Location of the left-bottom corner of the square.</param> /// <param name="RightBottom">Location of the right-bottom corner of the square.</param> public GpsSquare(GpsLocation LeftTop, GpsLocation RightTop, GpsLocation LeftBottom, GpsLocation RightBottom) { this.LeftTop = LeftTop; this.RightTop = RightTop; this.LeftBottom = LeftBottom; this.RightBottom = RightBottom; double bearing = LeftTop.InitialBearingTo(RightTop); double distance = LeftTop.DistanceTo(RightTop) / 2; MidTop = LeftTop.GoVector(bearing, distance); bearing = LeftBottom.InitialBearingTo(RightBottom); distance = LeftBottom.DistanceTo(RightBottom) / 2; MidBottom = LeftBottom.GoVector(bearing, distance); }
/// <summary> /// Calculates GPS square from the given centre location using a radius. /// </summary> /// <param name="Radius">Half of a distance between oposite sides.</param> /// <returns></returns> public GpsSquare GetSquare(double Radius) { // We calculate positions of square mid points of top and bottom sides - i.e. points in the center of square sides. GpsLocation midTop = GoVector(BearingNorth, Radius); GpsLocation midBottom = GoVector(BearingSouth, Radius); // From these mid points, we navigate use West and East bearing to go to the square corners. GpsLocation leftTop = midTop.GoVector(BearingWest, Radius); GpsLocation rightTop = midTop.GoVector(BearingEast, Radius); GpsLocation leftBottom = midBottom.GoVector(BearingWest, Radius); GpsLocation rightBottom = midBottom.GoVector(BearingEast, Radius); GpsSquare res = new GpsSquare(leftTop, rightTop, leftBottom, rightBottom); return(res); }
/// <summary> /// Calculates initial bearing from the start point to the destination point. /// </summary> /// <param name="Destination">Destination location.</param> /// <returns>Initial bearing from the start point to the destination point.</returns> /// <remarks>Formula source: http://www.movable-type.co.uk/scripts/latlong.html .</remarks> public double InitialBearingTo(GpsLocation Destination) { double lat1 = (double)Latitude; double lon1 = (double)Longitude; double lat2 = (double)Destination.Latitude; double lon2 = (double)Destination.Longitude; double lat1Rad = lat1.ToRadians(); double lon1Rad = lon1.ToRadians(); double lat2Rad = lat2.ToRadians(); double lon2Rad = lon2.ToRadians(); // Formula: θ = atan2(sin Δλ ⋅ cos φ2 , cos φ1 ⋅ sin φ2 − sin φ1 ⋅ cos φ2 ⋅ cos Δλ) // where φ1, λ1 is the start point, φ2, λ2 the end point(Δλ is the difference in longitude) double y = Math.Sin(lon2Rad - lon1Rad) * Math.Cos(lat2Rad); double x = Math.Cos(lat1Rad) * Math.Sin(lat2Rad) - Math.Sin(lat1Rad) * Math.Cos(lat2Rad) * Math.Cos(lon2Rad - lon1Rad); double res = Math.Atan2(y, x).ToDegrees(); // Normalize. res = (res + 360.0) % 360.0; return(res); }
/// <summary> /// Processes update received from LOC server that informs the proximity server about a new neighbor server or a change in existing neighbor server contact information. /// </summary> /// <param name="UnitOfWork">Unit of work instance.</param> /// <param name="ServerId">Network identifier of the neighbor server.</param> /// <param name="IpAddress">IP address of the neighbor server.</param> /// <param name="Port">Primary interface port of the neighbor server.</param> /// <param name="Latitude">GPS location latitude of the neighbor server.</param> /// <param name="Longitude">GPS location longitude of the neighbor server.</param> /// <param name="NeighborhoodSize">Size of the proximity server's neighborhood at the moment the function is called.</param> /// <returns>Information related to how should the caller proceed further, described in AddOrChangeNeighborResult structure.</returns> /// <remarks>The caller is responsible for calling this function within a database transaction with NeighborLock and NeighborhoodActionLock locks.</remarks> public async Task <AddOrChangeNeighborResult> AddOrChangeNeighborAsync(UnitOfWork UnitOfWork, byte[] ServerId, IPAddress IpAddress, int Port, int Latitude, int Longitude, int NeighborhoodSize) { log.Trace("(ServerId:'{0}',IpAddress:{1},Port:{2},Latitude:{3},Longitude:{4},NeighborhoodSize:{5})", ServerId.ToHex(), IpAddress, Port, Latitude, Longitude, NeighborhoodSize); AddOrChangeNeighborResult res = new AddOrChangeNeighborResult(); res.NeighborhoodSize = NeighborhoodSize; // Data validation. bool serverIdValid = ServerId.Length == ProtocolHelper.NetworkIdentifierLength; if (!serverIdValid) { log.Error("Received invalid neighbor server ID '{0}' from LOC server.", ServerId.ToHex()); res.Error = true; log.Trace("(-):*.Error={0},*.SaveDb={1},*.SignalActionProcessor={2},*.NeighborhoodSize={3}", res.Error, res.SaveDb, res.SignalActionProcessor, res.NeighborhoodSize); return(res); } bool portValid = (0 < Port) && (Port <= 65535); if (!portValid) { log.Error("Received invalid neighbor server port '{0}' from LOC server.", Port); res.Error = true; log.Trace("(-):*.Error={0},*.SaveDb={1},*.SignalActionProcessor={2},*.NeighborhoodSize={3}", res.Error, res.SaveDb, res.SignalActionProcessor, res.NeighborhoodSize); return(res); } IopProtocol.GpsLocation location = new IopProtocol.GpsLocation(Latitude, Longitude); if (!location.IsValid()) { log.Error("Received invalid neighbor server location '{0}' from LOC server.", location); res.Error = true; log.Trace("(-):*.Error={0},*.SaveDb={1},*.SignalActionProcessor={2},*.NeighborhoodSize={3}", res.Error, res.SaveDb, res.SignalActionProcessor, res.NeighborhoodSize); return(res); } // Data processing. Neighbor existingNeighbor = (await UnitOfWork.NeighborRepository.GetAsync(n => n.NetworkId == ServerId)).FirstOrDefault(); if (existingNeighbor == null) { // New neighbor server. if (NeighborhoodSize < Config.Configuration.MaxNeighborhoodSize) { // We have not reached the maximal size of the neighborhood yet, the server can be added. log.Trace("New neighbor ID '{0}' detected, IP address {1}, port {2}, latitude {3}, longitude {4}.", ServerId.ToHex(), IpAddress, Port, Latitude, Longitude); // Add neighbor to the database of neighbors. // The neighbor is not initialized, so we will not allow it to send us // any updates. First, we need to contact it and start the neighborhood initialization process. Neighbor neighbor = new Neighbor() { NetworkId = ServerId, IpAddress = IpAddress.GetAddressBytes(), PrimaryPort = Port, NeighborPort = null, LocationLatitude = location.Latitude, LocationLongitude = location.Longitude, LastRefreshTime = DateTime.UtcNow, Initialized = false, SharedActivities = 0 }; await UnitOfWork.NeighborRepository.InsertAsync(neighbor); res.NeighborhoodSize++; // This action will cause our proximity server to contact the new neighbor server and ask it to share its activity database, // i.e. the neighborhood initialization process will be started. // We set a delay depending on the number of neighbors, so that a new server joining a neighborhood is not overwhelmed with requests. int delay = RandomSource.Generator.Next(0, 3 * res.NeighborhoodSize); NeighborhoodAction action = new NeighborhoodAction() { ServerId = ServerId, Timestamp = DateTime.UtcNow, Type = NeighborhoodActionType.AddNeighbor, ExecuteAfter = DateTime.UtcNow.AddSeconds(delay), TargetActivityId = 0, TargetActivityOwnerId = null, AdditionalData = null, }; await UnitOfWork.NeighborhoodActionRepository.InsertAsync(action); res.SignalActionProcessor = true; res.SaveDb = true; } else { log.Error("Unable to add new neighbor ID '{0}', the proximity server reached its neighborhood size limit {1}.", ServerId.ToHex(), Config.Configuration.MaxNeighborhoodSize); } } else { // This is a neighbor we already know about. Just check that its information is up to date and if not, update it. IPAddress existingNeighborIpAddress = new IPAddress(existingNeighbor.IpAddress); if (!existingNeighborIpAddress.Equals(IpAddress)) { log.Trace("Existing neighbor ID '{0}' changed its IP address from {1} to {2}.", ServerId.ToHex(), existingNeighborIpAddress, IpAddress); existingNeighbor.IpAddress = IpAddress.GetAddressBytes(); } if (existingNeighbor.PrimaryPort != Port) { // Primary port was change, so we also expect that the neighbors interface port was changed as well. log.Trace("Existing neighbor ID '{0}' changed its primary port from {1} to {2}, invalidating neighbors interface port as well.", ServerId.ToHex(), existingNeighbor.PrimaryPort, Port); existingNeighbor.PrimaryPort = Port; existingNeighbor.NeighborPort = null; } if (existingNeighbor.LocationLatitude != location.Latitude) { log.Trace("Existing neighbor ID '{0}' changed its latitude from {1} to {2}.", ServerId.ToHex(), existingNeighbor.LocationLatitude, location.Latitude); existingNeighbor.LocationLatitude = Latitude; } if (existingNeighbor.LocationLongitude != location.Longitude) { log.Trace("Existing neighbor ID '{0}' changed its longitude from {1} to {2}.", ServerId.ToHex(), existingNeighbor.LocationLongitude, location.Longitude); existingNeighbor.LocationLongitude = Longitude; } // We consider a fresh LOC info to be accurate, so we do not want to delete the neighbors received here // and hence we update their refresh time. existingNeighbor.LastRefreshTime = DateTime.UtcNow; UnitOfWork.NeighborRepository.Update(existingNeighbor); res.SaveDb = true; } log.Trace("(-):*.Error={0},*.SaveDb={1},*.SignalActionProcessor={2},*.NeighborhoodSize={3}", res.Error, res.SaveDb, res.SignalActionProcessor, res.NeighborhoodSize); return(res); }
/// <summary> /// Announces server's primary server role interface to the LOC server. /// </summary> /// <param name="PrimaryPort">Primary port of the server.</param> /// <param name="Type">Type of the server.</param> /// <param name="Location">Optionally, an empty GpsLocation instance that will be filled with location information received from the LOC server if the function succeeds.</param> /// <returns>true if the function succeeds, false otherwise.</returns> public async Task <bool> RegisterPrimaryServerRoleAsync(int PrimaryPort, ServiceType Type, IopProtocol.GpsLocation Location = null) { log.Info("(PrimaryPort:{0})", PrimaryPort); bool res = false; ServiceInfo serviceInfo = new ServiceInfo() { Port = (uint)PrimaryPort, Type = Type, ServiceData = ProtocolHelper.ByteArrayToByteString(Crypto.Sha256(((KeysEd25519)config.Settings["Keys"]).PublicKey)) }; LocProtocolMessage request = MessageBuilder.CreateRegisterServiceRequest(serviceInfo); if (await SendMessageAsync(request)) { LocProtocolMessage response = await ReceiveMessageAsync(shutdownSignaling.ShutdownCancellationTokenSource.Token); if (response != null) { res = (response.Id == request.Id) && (response.MessageTypeCase == Message.MessageTypeOneofCase.Response) && (response.Response.Status == Status.Ok) && (response.Response.ResponseTypeCase == Response.ResponseTypeOneofCase.LocalService) && (response.Response.LocalService.LocalServiceResponseTypeCase == LocalServiceResponse.LocalServiceResponseTypeOneofCase.RegisterService); if (res) { if (Location != null) { IopProtocol.GpsLocation location = new IopProtocol.GpsLocation(response.Response.LocalService.RegisterService.Location.Latitude, response.Response.LocalService.RegisterService.Location.Longitude); Location.Latitude = location.Latitude; Location.Longitude = location.Longitude; if (Location.IsValid()) { res = true; } else { log.Error("Registration failed, LOC server provided invalid location information [{0}].", location); } } else { res = true; } if (res) { log.Debug("Primary interface has been registered successfully on LOC server{0}.", Location != null ? string.Format(", server location set is [{0}]", Location) : ""); } } else { log.Error("Registration failed, response status is {0}.", response.Response != null ? response.Response.Status.ToString() : "n/a"); } } else { log.Error("Invalid message received from LOC server."); } } else { log.Error("Unable to send register server request to LOC server."); } log.Info("(-):{0}", res); return(res); }
/// <summary> /// Calculates distance between two locations. /// </summary> /// <param name="LocationA">First location.</param> /// <param name="LocationB">Second location.</param> /// <returns>Distance between the two locations in metres.</returns> public static double DistanceBetween(GpsLocation LocationA, GpsLocation LocationB) { return(LocationA.DistanceTo(LocationB)); }
/// <summary> /// Creates a response message to a RegisterServiceRequest message. /// </summary> /// <param name="Request">RegisterServiceRequest message for which the response is created.</param> /// <returns>RegisterServiceResponse message that is ready to be sent.</returns> public LocProtocolMessage CreateRegisterServiceResponse(LocProtocolMessage Request, GpsLocation Location) { RegisterServiceResponse registerServiceResponse = new RegisterServiceResponse(); registerServiceResponse.Location = new Iop.Locnet.GpsLocation() { Latitude = Location.GetLocationTypeLatitude(), Longitude = Location.GetLocationTypeLongitude() }; LocProtocolMessage res = CreateLocalServiceOkResponse(Request); res.Response.LocalService.RegisterService = registerServiceResponse; return(res); }