public bool IsThereNewPoints(string kmlFeedresult, KMLInfo track) { string LastPointTime = track.LastPointTimestamp; XDocument xmlTrack = XDocument.Parse(kmlFeedresult); XmlNamespaceManager ns = new XmlNamespaceManager(new NameTable()); ns.AddNamespace("kml", "http://www.opengis.net/kml/2.2"); //set placemark as a root element var placemarks = xmlTrack.XPathSelectElements("//kml:Placemark", ns); var NewLastPointTime = NewLastTimestamp(placemarks, ns); DateTime dTLast = new DateTime(); DateTime dTNew = DateTime.UtcNow.ToUniversalTime(); if (!string.IsNullOrEmpty(NewLastPointTime)) { dTNew = DateTime.Parse(NewLastPointTime).ToUniversalTime(); } if (!string.IsNullOrEmpty(LastPointTime)) { dTLast = DateTime.SpecifyKind(DateTime.Parse(LastPointTime, CultureInfo.CreateSpecificCulture("en-US")), DateTimeKind.Utc); } if (dTNew > dTLast) { return(true); } return(false); }
public static async Task <IActionResult> Run( [HttpTrigger(AuthorizationLevel.Anonymous, "get", Route = "DeleteInReachFeedFromCosmos/{userWebId}/{trackId}")] HttpRequest req, [CosmosDB( databaseName: "FreeCosmosDB", collectionName: "TrackMe", ConnectionStringSetting = "CosmosDBForFree", PartitionKey = "{userWebId}", Id = "{trackId}" )] KMLInfo kMLInfo, [CosmosDB( databaseName: "FreeCosmosDB", collectionName: "TrackMe", ConnectionStringSetting = "CosmosDBForFree", SqlQuery = "SELECT * FROM c WHERE c.groupid = 'user'" )] IEnumerable <InReachUser> inReachUsers, [CosmosDB( databaseName: "FreeCosmosDB", collectionName: "TrackMe", ConnectionStringSetting = "CosmosDBForFree" )] DocumentClient documentClient, ExecutionContext context) { var config = new ConfigurationBuilder() .SetBasePath(context.FunctionAppDirectory) .AddJsonFile("local.settings.json", optional: true, reloadOnChange: true) .AddEnvironmentVariables() .Build(); var StorageContainerConnectionString = config["StorageContainerConnectionString"]; CloudStorageAccount storageAccount = CloudStorageAccount.Parse(StorageContainerConnectionString); CloudBlobClient blobClient = storageAccount.CreateCloudBlobClient(); HelperKMLParse helperKMLParse = new HelperKMLParse(); ClaimsPrincipal Identities = req.HttpContext.User; var checkUser = new HelperCheckUser(); var LoggedInUser = checkUser.LoggedInUser(inReachUsers, Identities); var IsAuthenticated = false; if (LoggedInUser.status == Status.ExistingUser) { IsAuthenticated = true; } if (IsAuthenticated) { //selfLink is like this: "dbs/auo6AA==/colls/auo6AOdfluE=/docs/auo6AOdfluEnFwIAAAAAAA==/"; await documentClient.DeleteDocumentAsync(kMLInfo._self, new RequestOptions { PartitionKey = new PartitionKey(LoggedInUser.userWebId) }); //delete blobs foreach (var blob in helperKMLParse.Blobs) { var blobName = $"{kMLInfo.groupid}/{kMLInfo.id}/{blob.BlobName}.kml"; await helperKMLParse.RemoveBlobAsync(blobName, blobClient); } } return(new OkObjectResult(IsAuthenticated)); }
public async Task <string> GetKMLAsync(KMLInfo kMLInfo) { string userUrl = $"{kMLInfo.InReachWebAddress}{CreateDateParameter(kMLInfo.d1, "d1")}{CreateDateParameter(kMLInfo.d2, "d2")}"; string garminUrl = $"https://share.garmin.com/Feed/Share/{userUrl}"; //get the data from garmin based on start date in every 5 minutes var http = new HttpClient(); var base64EncodedAuthenticationString = Convert.ToBase64String(Encoding.ASCII.GetBytes($":{kMLInfo.InReachWebPassword}")); http.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Basic", base64EncodedAuthenticationString); HttpResponseMessage response = http.GetAsync(garminUrl).Result; return(await response.Content.ReadAsStringAsync()); }
public static async Task <IActionResult> Run( [HttpTrigger(AuthorizationLevel.Anonymous, "post", Route = null)] HttpRequest req, [CosmosDB( databaseName: "FreeCosmosDB", collectionName: "TrackMe", ConnectionStringSetting = "CosmosDBForFree" )] IAsyncCollector <KMLInfo> asyncCollectorKMLInfo, [CosmosDB( databaseName: "FreeCosmosDB", collectionName: "TrackMe", ConnectionStringSetting = "CosmosDBForFree", SqlQuery = "SELECT * FROM c WHERE c.groupid = 'user'" )] IEnumerable <InReachUser> inReachUsers, ExecutionContext context ) { var config = new ConfigurationBuilder() .SetBasePath(context.FunctionAppDirectory) .AddJsonFile("local.settings.json", optional: true, reloadOnChange: true) .AddEnvironmentVariables() .Build(); var StorageContainerConnectionString = config["StorageContainerConnectionString"]; CloudStorageAccount storageAccount = CloudStorageAccount.Parse(StorageContainerConnectionString); CloudBlobClient blobClient = storageAccount.CreateCloudBlobClient(); ClaimsPrincipal Identities = req.HttpContext.User; var checkUser = new HelperCheckUser(); var LoggedInUser = checkUser.LoggedInUser(inReachUsers, Identities); var IsAuthenticated = false; if (LoggedInUser.status == Status.ExistingUser) { IsAuthenticated = true; } if (IsAuthenticated) { string requestBody = await new StreamReader(req.Body).ReadToEndAsync(); if (requestBody != "") { var kMLFull = JsonConvert.DeserializeObject <KMLFull>(requestBody); KMLInfo kMLInfo = new KMLInfo() { id = kMLFull.id, Title = kMLFull.Title, d1 = kMLFull.d1, d2 = kMLFull.d2, InReachWebAddress = kMLFull.InReachWebAddress, InReachWebPassword = kMLFull.InReachWebPassword, UserTimezone = kMLFull.UserTimezone }; var blobs = helperKMLParse.Blobs; blobs.ForEach(x => x.BlobValue = ""); blobs.First(x => x.BlobName == "plannedtrack").BlobValue = kMLFull.PlannedTrack; //1. replace ö->o ä->a etc //2. first: UrlEncode is removing all weird charactes and spaces //3. second: HttpUtility.UrlEncode is removing some not named weird characters, just in case //4. third: UrlEncode again is removing possible %-marks //setting id field only on initial track creation if (string.IsNullOrEmpty(kMLInfo.id)) { string id = RemoveDiacritics(kMLInfo.Title); id = UrlEncode(HttpUtility.UrlEncode(UrlEncode(id))); kMLInfo.id = id; } kMLInfo.LastPointTimestamp = ""; kMLInfo.groupid = LoggedInUser.userWebId; kMLInfo.IsLongTrack = false; var dateD1 = DateTime.Parse(kMLInfo.d1).AddHours(-kMLInfo.UserTimezone); var dateD2 = DateTime.Parse(kMLInfo.d2).AddDays(1).AddHours(-kMLInfo.UserTimezone); TimeSpan timeSpan = dateD2 - dateD1; //this setting affects of parsing all points (slow) or not. Depending on the duration of the track if (timeSpan.TotalDays > 2) { kMLInfo.IsLongTrack = true; } kMLInfo.d1 = dateD1.ToString("yyyy-MM-ddTHH:mm:ssZ"); kMLInfo.d2 = dateD2.ToString("yyyy-MM-ddTHH:mm:ssZ"); HelperGetKMLFromGarmin helperGetKMLFromGarmin = new HelperGetKMLFromGarmin(); //get feed grom garmin var kmlFeedresult = await helperGetKMLFromGarmin.GetKMLAsync(kMLInfo); //parse and transform the feed and save to database helperKMLParse.ParseKMLFile(kmlFeedresult, kMLInfo, blobs, new List <Emails>(), LoggedInUser); await asyncCollectorKMLInfo.AddAsync(kMLInfo); //save blobs foreach (var blob in blobs) { var blobName = $"{kMLInfo.groupid}/{kMLInfo.id}/{blob.BlobName}.kml"; await helperKMLParse.AddToBlobAsync(blobName, blob.BlobValue, blobClient); } } } return(new OkObjectResult(IsAuthenticated)); }
static async Task ManageTodayTrack(InReachUser LoggedInUser, IAsyncCollector <KMLInfo> addDocuments, DocumentClient client, Uri collectionUri, string TodayTrackId, string SendEmailFunctionUrl, string SendEmailFunctionKey, string WebSiteUrl, CloudBlobClient blobClient) { var dated1 = DateTime.UtcNow.ToUniversalTime().ToString("yyyy-MM-dd"); var dated2 = DateTime.UtcNow.ToUniversalTime().ToString("yyyy-MM-dd"); var dateTimed1 = DateTime.Parse(dated1).AddHours(-LoggedInUser.UserTimezone).ToString("yyyy-MM-ddTHH:mm:ssZ"); var dateTimed2 = DateTime.Parse(dated2).AddDays(1).AddHours(-LoggedInUser.UserTimezone).ToString("yyyy-MM-ddTHH:mm:ssZ"); KMLInfo kMLInfo = new KMLInfo() { id = TodayTrackId, Title = "Today's Live Track", d1 = dateTimed1, d2 = dateTimed2, groupid = LoggedInUser.userWebId, InReachWebAddress = LoggedInUser.InReachWebAddress, InReachWebPassword = LoggedInUser.InReachWebPassword, UserTimezone = LoggedInUser.UserTimezone, IsLongTrack = false }; //create Today's track if (LoggedInUser.Active) { HelperGetKMLFromGarmin helperGetKMLFromGarmin = new HelperGetKMLFromGarmin(); var emails = new List <Emails>(); //get feed grom garmin var kmlFeedresult = await helperGetKMLFromGarmin.GetKMLAsync(kMLInfo); var blobs = helperKMLParse.Blobs; //parse and transform the feed and save to database helperKMLParse.ParseKMLFile(kmlFeedresult, kMLInfo, blobs, emails, LoggedInUser, WebSiteUrl); await addDocuments.AddAsync(kMLInfo); //save blobs foreach (var blob in blobs) { var blobName = $"{kMLInfo.groupid}/{kMLInfo.id}/{blob.BlobName}.kml"; await helperKMLParse.AddToBlobAsync(blobName, blob.BlobValue, blobClient); } //sending out emails if (emails.Any()) { HttpClient httpClient = new HttpClient(); Uri SendEmailFunctionUri = new Uri($"{SendEmailFunctionUrl}?code={SendEmailFunctionKey}"); var returnMessage = await httpClient.PostAsJsonAsync(SendEmailFunctionUri, emails); } } //delete Today's track if (!LoggedInUser.Active) { //select and delete document var queryOne = new SqlQuerySpec("SELECT c._self, c.groupid, c.id FROM c WHERE c.id = @id", new SqlParameterCollection(new SqlParameter[] { new SqlParameter { Name = "@id", Value = kMLInfo.id } })); KMLInfo kML = client.CreateDocumentQuery(collectionUri, queryOne, new FeedOptions { PartitionKey = new PartitionKey(kMLInfo.groupid) }).AsEnumerable().FirstOrDefault(); if (!(kML is null)) { //delete metadata await client.DeleteDocumentAsync(kML._self, new RequestOptions { PartitionKey = new PartitionKey(kML.groupid) }); //delete blobs foreach (var blob in helperKMLParse.Blobs) { var blobName = $"{kML.groupid}/{kML.id}/{blob.BlobName}.kml"; await helperKMLParse.RemoveBlobAsync(blobName, blobClient); } } } }
public static async Task <IActionResult> Run( [HttpTrigger(AuthorizationLevel.Anonymous, "get", Route = "GetKMLFeedMetadata/{GroupId}/{id}")] HttpRequest req, string GroupId, [CosmosDB( databaseName: "FreeCosmosDB", collectionName: "TrackMe", ConnectionStringSetting = "CosmosDBForFree", PartitionKey = "{GroupId}", Id = "{id}" )] KMLInfo kMLInfo, [CosmosDB( databaseName: "FreeCosmosDB", collectionName: "TrackMe", ConnectionStringSetting = "CosmosDBForFree", SqlQuery = "SELECT * FROM c WHERE c.groupid = 'user'" )] IEnumerable <InReachUser> inReachUsers, ExecutionContext context) { var config = new ConfigurationBuilder() .SetBasePath(context.FunctionAppDirectory) .AddJsonFile("local.settings.json", optional: true, reloadOnChange: true) .AddEnvironmentVariables() .Build(); var StorageContainerConnectionString = config["StorageContainerConnectionString"]; CloudStorageAccount storageAccount = CloudStorageAccount.Parse(StorageContainerConnectionString); CloudBlobClient blobClient = storageAccount.CreateCloudBlobClient(); ClaimsPrincipal Identities = req.HttpContext.User; var checkUser = new HelperCheckUser(); var LoggedInUser = checkUser.LoggedInUser(inReachUsers, Identities); var IsAuthenticated = false; if (LoggedInUser.status == Status.ExistingUser) { IsAuthenticated = true; } KMLData kMLData = new KMLData() { id = kMLInfo.id, d1 = kMLInfo.d1, d2 = kMLInfo.d2, Title = kMLInfo.Title, InReachWebAddress = kMLInfo.InReachWebAddress, InReachWebPassword = kMLInfo.InReachWebPassword }; var blobName = $"{kMLInfo.groupid}/{kMLInfo.id}/plannedtrack.kml"; kMLData.PlannedTrack = await helperKMLParse.GetFromBlobAsync(blobName, blobClient); if (IsAuthenticated) { if (GroupId == LoggedInUser.userWebId) { return(new OkObjectResult(kMLData)); } else { return(new OkObjectResult("Not your track")); } } return(new OkObjectResult(IsAuthenticated)); }
public bool ParseKMLFile(string kmlFeedresult, KMLInfo kMLInfo, List <Blob> blobs, List <Emails> emails, InReachUser user = null, string webSiteUrl = "") { //open and parse KMLfeed XDocument xmlTrack = XDocument.Parse(kmlFeedresult); XmlNamespaceManager ns = new XmlNamespaceManager(new NameTable()); ns.AddNamespace("kml", "http://www.opengis.net/kml/2.2"); var defaultns = xmlTrack.Root.GetDefaultNamespace(); XDocument xmlLineString; XDocument xmlPlacemarks; XDocument xmlPlacemarksWithMessages; XDocument xmlLastPlacemark; XElement NewPlacemark = new XElement(defaultns + "Folder", new XElement(defaultns + "Placemark", new XElement(defaultns + "ExtendedData", new XElement(defaultns + "Data", new XAttribute("name", "Time"), new XElement(defaultns + "value")), new XElement(defaultns + "Data", new XAttribute("name", "Velocity"), new XElement(defaultns + "value")), new XElement(defaultns + "Data", new XAttribute("name", "Distance"), new XElement(defaultns + "value")), new XElement(defaultns + "Data", new XAttribute("name", "TimeElapsed"), new XElement(defaultns + "value")), new XElement(defaultns + "Data", new XAttribute("name", "Elevation"), new XElement(defaultns + "value")), new XElement(defaultns + "Data", new XAttribute("name", "Course"), new XElement(defaultns + "value")), new XElement(defaultns + "Data", new XAttribute("name", "Text"), new XElement(defaultns + "value")), new XElement(defaultns + "Data", new XAttribute("name", "Latitude"), new XElement(defaultns + "value")), new XElement(defaultns + "Data", new XAttribute("name", "Longitude"), new XElement(defaultns + "value")) ), new XElement(defaultns + "styleUrl"), new XElement(defaultns + "Point", new XElement(defaultns + "coordinates") ))); XElement newDoc = new XElement(defaultns + "kml", new XElement(defaultns + "Document")); XElement newLineString = new XElement(defaultns + "Folder", new XElement(defaultns + "Placemark", new XElement(defaultns + "name"), new XElement(defaultns + "description"), new XElement(defaultns + "LineString", new XElement(defaultns + "coordinates" )))); var PlacemarksAll = blobs.First(x => x.BlobName == "placemarksall").BlobValue; var PlacemarksMsg = blobs.First(x => x.BlobName == "placemarksmsg").BlobValue; var TrackLine = blobs.First(x => x.BlobName == "trackline").BlobValue; var LastPlacemark = string.Empty; //create Xdocuments if they are empty if (!string.IsNullOrEmpty(PlacemarksAll)) { xmlPlacemarks = XDocument.Parse(PlacemarksAll); } else { xmlPlacemarks = new XDocument(new XElement(newDoc)); AddStyles(xmlPlacemarks.XPathSelectElement("//kml:Document", ns), defaultns); } if (!string.IsNullOrEmpty(PlacemarksMsg)) { xmlPlacemarksWithMessages = XDocument.Parse(PlacemarksMsg); } else { xmlPlacemarksWithMessages = new XDocument(new XElement(newDoc)); AddStyles(xmlPlacemarksWithMessages.XPathSelectElement("//kml:Document", ns), defaultns); } if (!string.IsNullOrEmpty(TrackLine)) { xmlLineString = XDocument.Parse(TrackLine); } else { xmlLineString = new XDocument(new XElement(newDoc)); var documentLineString = xmlLineString.XPathSelectElement("//kml:Document", ns); documentLineString.Add(newLineString); } xmlLastPlacemark = new XDocument(new XElement(newDoc)); AddStyles(xmlLastPlacemark.XPathSelectElement("//kml:Document", ns), defaultns); var documentLastPlacemark = xmlLastPlacemark.XPathSelectElement("//kml:Document", ns); var documentPlacemarkMessages = xmlPlacemarksWithMessages.XPathSelectElement("//kml:Document", ns); var documentPlacemark = xmlPlacemarks.XPathSelectElement("//kml:Document", ns); //set placemark as a root element var placemarks = xmlTrack.XPathSelectElements("//kml:Placemark", ns); double lastLatitude = kMLInfo.LastLatitude; double lastLongitude = kMLInfo.LastLongitude; double totalDistance = kMLInfo.LastTotalDistance; TimeSpan totalTime = new TimeSpan(); TimeSpan.TryParse(kMLInfo.LastTotalTime, out totalTime); var lastpointTs = kMLInfo.LastPointTimestamp; DateTime lastDate = new DateTime(); if (!string.IsNullOrEmpty(lastpointTs)) { lastDate = DateTime.Parse(lastpointTs, CultureInfo.InvariantCulture).AddHours(kMLInfo.UserTimezone); } var lineStringMessage = string.Empty; DateTime trackStarted = new DateTime(); var LastPointTimestamp = string.Empty; bool isTheLastPointIsZero = true; //iterate through each Placemark foreach (var placemark in placemarks) { //Placemarks without ExtendedData not proceed var check = placemark.XPathSelectElement("./kml:ExtendedData", ns); if (check != null) { //copy coordinates NewPlacemark.XPathSelectElement("//kml:Point/kml:coordinates", ns).Value = placemark.XPathSelectElement("./kml:Point/kml:coordinates", ns).Value; //copy LatLon var thisLatitude = NewPlacemark.XPathSelectElement("//kml:ExtendedData/kml:Data[@name = 'Latitude']/kml:value", ns).Value = placemark.XPathSelectElement("./kml:ExtendedData/kml:Data[@name = 'Latitude']/kml:value", ns).Value; var thisLongitude = NewPlacemark.XPathSelectElement("//kml:ExtendedData/kml:Data[@name = 'Longitude']/kml:value", ns).Value = placemark.XPathSelectElement("./kml:ExtendedData/kml:Data[@name = 'Longitude']/kml:value", ns).Value; //calculate distance double.TryParse(thisLatitude, NumberStyles.Any, CultureInfo.InvariantCulture, out double thisLatitudeDouble); double.TryParse(thisLongitude, NumberStyles.Any, CultureInfo.InvariantCulture, out double thisLongitudeDouble); GeoCoordinate pin1 = new GeoCoordinate(thisLatitudeDouble, thisLongitudeDouble); GeoCoordinate pin2 = new GeoCoordinate(lastLatitude, lastLongitude); if (lastLatitude != 0 && lastLongitude != 0) { totalDistance += pin1.GetDistanceTo(pin2) / 1000; isTheLastPointIsZero = false; } lastLatitude = thisLatitudeDouble; lastLongitude = thisLongitudeDouble; var distance = totalDistance.ToString("0") + " km"; NewPlacemark.XPathSelectElement("//kml:ExtendedData/kml:Data[@name = 'Distance']/kml:value", ns).Value = distance; //select Speed element end remove fraction after comma: 12 km/m string speed = placemark.XPathSelectElement("./kml:ExtendedData/kml:Data[@name = 'Velocity']/kml:value", ns).Value; speed = speed.Substring(0, speed.IndexOf(".")) + " km/h"; NewPlacemark.XPathSelectElement("//kml:ExtendedData/kml:Data[@name = 'Velocity']/kml:value", ns).Value = speed; //select Course element and transform it compass style heading and remove fractions string heading = placemark.XPathSelectElement("./kml:ExtendedData/kml:Data[@name = 'Course']/kml:value", ns).Value; heading = heading.Substring(0, heading.IndexOf(".")); Int32.TryParse(heading, out int h); //setting placemark to use style according to heading: NE 45° heading = GetHeading(h, out int idx).ToUpper() + " " + heading + "°"; NewPlacemark.XPathSelectElement("//kml:ExtendedData/kml:Data[@name = 'Course']/kml:value", ns).Value = heading; NewPlacemark.XPathSelectElement("//kml:styleUrl", ns).Value = $"#{idx}"; //select Elevation element end remove fraction after comma: 124 m string elevation = placemark.XPathSelectElement("./kml:ExtendedData/kml:Data[@name = 'Elevation']/kml:value", ns).Value; elevation = elevation.Substring(0, elevation.IndexOf(".")) + " m"; NewPlacemark.XPathSelectElement("//kml:ExtendedData/kml:Data[@name = 'Elevation']/kml:value", ns).Value = elevation; //select Time element and covert var dateTimeString = placemark.XPathSelectElement("./kml:ExtendedData/kml:Data[@name = 'Time']/kml:value", ns).Value; DateTime dT = DateTime.Parse(dateTimeString, CultureInfo.CreateSpecificCulture("en-US")); var dateTimeToPlacemark = $"{dT:HH:mm dd.MM.yyyy}"; NewPlacemark.XPathSelectElement("//kml:ExtendedData/kml:Data[@name = 'Time']/kml:value", ns).Value = dateTimeToPlacemark; //select lastpoint timestamp. The nevest placemark is always the last placemark. LastPointTimestamp = placemark.XPathSelectElement("./kml:TimeStamp/kml:when", ns).Value; //calculate total time if (lastDate != DateTime.MinValue) { totalTime += dT.Subtract(lastDate); } lastDate = dT; var totalTimeStr = $"{totalTime:%d} day(s) {totalTime:%h\\:mm}"; NewPlacemark.XPathSelectElement("//kml:ExtendedData/kml:Data[@name = 'TimeElapsed']/kml:value", ns).Value = totalTimeStr; //select events and messages string textValue = placemark.XPathSelectElement("./kml:ExtendedData/kml:Data[@name = 'Text']/kml:value", ns).Value; string eventType = placemark.XPathSelectElement("./kml:ExtendedData/kml:Data[@name = 'Event']/kml:value", ns).Value; NewPlacemark.XPathSelectElement("//kml:ExtendedData/kml:Data[@name = 'Text']/kml:value", ns).Value = textValue; //check if the eventType or receivedText contains keywords, if yes, then attach the style to the placemark foreach (var styleKeyword in StyleKeywords) { //setting style for inReach turned on or any message received if (styleKeyword.Keywords.Any(eventType.Contains)) { NewPlacemark.XPathSelectElement("//kml:styleUrl", ns).Value = $"#{styleKeyword.StyleName}"; } //setting style for the keywords found in message body if (styleKeyword.Keywords.Any(textValue.ToLower().Contains)) { NewPlacemark.XPathSelectElement("//kml:styleUrl", ns).Value = $"#{styleKeyword.StyleName}"; } } //if tracking turned on on device then copy Event into Text field if (eventType == InReachEvents[0]) { NewPlacemark.XPathSelectElement("//kml:ExtendedData/kml:Data[@name = 'Text']/kml:value", ns).Value = InReachEvents[0]; trackStarted = lastDate; if (string.IsNullOrEmpty(kMLInfo.TrackStartTime)) { kMLInfo.TrackStartTime = trackStarted.ToString("HH:mm dd.MM.yyyy"); } } //get the sender name as a Garmin Map Display Name. Not used. //var senderName = placemark.XPathSelectElement("./kml:ExtendedData/kml:Data[@name = 'Map Display Name']/kml:value", ns).Value; var inReachMessage = NewPlacemark.XPathSelectElement("//kml:ExtendedData/kml:Data[@name = 'Text']/kml:value", ns).Value; lineStringMessage = $"Track started on {kMLInfo.TrackStartTime}.<br>Total distance traveled { distance} in { totalTimeStr}."; var eMailMessage = $"Hello, <br><br><h3>{user.name} is on {kMLInfo.Title}.</h3>" + $"What just happened? <b>{inReachMessage}</b>.<br>" + $"{lineStringMessage}<br>" + $"Follow me on the map <a href='{webSiteUrl}/{kMLInfo.groupid}?id={kMLInfo.id}'></a>{webSiteUrl}/{kMLInfo.groupid}<br><br>" + $"This message was sent in {lastDate:HH:mm dd.MM.yyyy}, at location LatLon: {lastLatitude}, {lastLongitude}. " + $"<a href='https://www.google.com/maps/search/?api=1&query={lastLatitude},{lastLongitude}'>Open in google maps</a>.<br><br>" + $"Best regards,<br>Whoever is carrying this device.<br><br>" + $"<small>Disclaimer<br>" + $"You are getting this e-mail because you subscribed to receive {user.name} inReach messages.<br>" + $"Click here to unsubscribe:<a href='{webSiteUrl}/unsubscribe?userWebId={kMLInfo.groupid}'>Remove me from {user.name} inReach notifications</a>.<br>" + $"Sorry! It's not working yet. You cannot unsubscribe. You have to follow me forever.</small>"; var eMailSubject = $"{user.name} at {lastDate:HH:mm}: {inReachMessage}"; //if inReach has sent out a message then add a Placemark if (InReachEvents.Any(eventType.Contains)) { documentPlacemarkMessages.Add(new XElement(NewPlacemark)); emails.Add(new Emails { EmailBody = eMailMessage, EmailSubject = eMailSubject, UserWebId = kMLInfo.groupid, DateTime = dateTimeString, EmailFrom = user.email, Name = user.name, EmailTo = user.subscibers }); } //add full placemarks only for short less than 2 days tracks if (!kMLInfo.IsLongTrack) { documentPlacemark.Add(new XElement(NewPlacemark)); } } else { //build a linestring, add new coordinates into end of existing list string NewCoordinates = ReturnValue(xmlTrack.XPathSelectElement("//kml:Placemark/kml:LineString/kml:coordinates", ns)); string Coordinates = ReturnValue(xmlLineString.XPathSelectElement("//kml:Placemark/kml:LineString/kml:coordinates", ns)); Coordinates = Coordinates + "\r\n" + NewCoordinates; xmlLineString.XPathSelectElement("//kml:Placemark/kml:LineString/kml:coordinates", ns).Value = Coordinates; xmlLineString.XPathSelectElement("//kml:Placemark/kml:name", ns).Value = kMLInfo.Title; xmlLineString.XPathSelectElement("//kml:Placemark/kml:description", ns).Value = lineStringMessage; } } kMLInfo.LastTotalDistance = totalDistance; kMLInfo.LastLatitude = lastLatitude; kMLInfo.LastLongitude = lastLongitude; kMLInfo.LastTotalTime = totalTime.ToString(); kMLInfo.LastPointTimestamp = LastPointTimestamp; //create a layer just with a last placemark if it is not zero if (!isTheLastPointIsZero) { documentLastPlacemark.Add(new XElement(NewPlacemark)); } LastPlacemark = xmlString + xmlLastPlacemark.ToString(); PlacemarksMsg = xmlString + xmlPlacemarksWithMessages.ToString(); TrackLine = xmlString + xmlLineString.ToString(); //add full placemarks only for track with duration less than 2 day if (!kMLInfo.IsLongTrack) { PlacemarksAll = xmlString + xmlPlacemarks.ToString(); } blobs.First(x => x.BlobName == "trackline").BlobValue = TrackLine; blobs.First(x => x.BlobName == "placemarksall").BlobValue = PlacemarksAll; blobs.First(x => x.BlobName == "placemarksmsg").BlobValue = PlacemarksMsg; blobs.First(x => x.BlobName == "lastplacemark").BlobValue = LastPlacemark; return(true); }