Skip to content

Commit

Permalink
Adding Legacy, UTM Basic and UTM Ready display (ArduPilot#3166)
Browse files Browse the repository at this point in the history
* How to spot each utm status

* Initial badges in header

* CdnUrl for environment specific CDN content

* Map info panel now matching

* Switch to Markdig

* Move MarkdownPipeline to service locator

---------

Co-authored-by: rupertbenbrook <[email protected]>
  • Loading branch information
rupertbenbrook and rupertbenbrook-aa authored Sep 12, 2023
1 parent 05efac1 commit 4203d27
Show file tree
Hide file tree
Showing 7 changed files with 208 additions and 73 deletions.
40 changes: 33 additions & 7 deletions ExtLibs/AltitudeAngelWings.Plugin/Resources/MapInfoDockPanel.html
Original file line number Diff line number Diff line change
Expand Up @@ -72,17 +72,34 @@
text-transform: capitalize;
}

div.utmStatus {
div.utmReadyStatus {
border: 1px solid #3dbb4e;
margin-top: 10px;
padding: 0 10px;
}

div.utmStatus div.section div.title {
display: block;
text-align: center;
font-size: 18px;
padding: 5px;
div.utmBasicStatus {
border: 1px solid darkorange;
margin-top: 10px;
padding: 0 10px;
}

div.utmReadyTitle {
background-color: #edf8ed;
margin: 8px 0;
padding: 8px;
}

div.utmBasicTitle {
background-color: #fcebb4;
margin: 8px 0;
padding: 8px;
}

div.utmLegacyTitle {
background-color: #fde5ee;
margin: 8px 0;
padding: 8px;
}

div.noUtm {
Expand All @@ -91,7 +108,7 @@
padding: 0 10px;
}

div.utmStatus div.button {
div.utmReadyStatus div.button {
background-color: #3dbb4e;
text-align: center;
margin: 0 50px 10px;
Expand All @@ -100,6 +117,15 @@
font-size: 14px;
}

div.utmBasicStatus div.button {
background-color: #fb0;
text-align: center;
margin: 0 50px 10px;
padding: 10px 10px;
font-weight: bold;
font-size: 14px;
}

div.phoneNumber {
margin: 5px 0;
padding-top: 10px;
Expand Down
2 changes: 1 addition & 1 deletion ExtLibs/AltitudeAngelWings/AltitudeAngelWings.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@
<TreatWarningsAsErrors>True</TreatWarningsAsErrors>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="MarkdownSharp" Version="2.0.5" />
<PackageReference Include="Markdig" Version="0.32.0" />
<PackageReference Include="NetTopologySuite" Version="2.4.0" />
<PackageReference Include="NodaTime.Serialization.JsonNet" Version="3.0.1" />
<PackageReference Include="Flurl" Version="3.0.2" />
Expand Down
168 changes: 104 additions & 64 deletions ExtLibs/AltitudeAngelWings/Clients/Api/Model/DisplayInfoExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
using System.Globalization;
using System.Linq;
using System.Text;
using MarkdownSharp;
using Markdig;

namespace AltitudeAngelWings.Clients.Api.Model
{
Expand All @@ -29,92 +29,132 @@ public static class DisplayInfoExtensions
public static string FormatAsHtml(this FeatureProperties featureProperties,
IDictionary<string, RateCardDetail> rateCardDetails)
{
var settings = ServiceLocator.GetService<ISettings>();
var pipeline = ServiceLocator.GetService<MarkdownPipeline>();
var builder = new StringBuilder();
var markdown = new Markdown(new MarkdownOptions
{
AutoNewlines = false,
AutoHyperlink = false,
LinkEmails = false,
EmptyElementSuffix = "/>"
});
builder.Append("<div class=\"feature\">");
builder.Append($"<div class=\"highlight\" style=\"background-color: {featureProperties.FillColor}\"></div>");
builder.Append($"<div class=\"header\">");
builder.Append("<div class=\"header\">");
builder.Append($"<div class=\"category\">{featureProperties.DisplayInfo.Category}</div>");
builder.Append($"<div class=\"detailedCategory\"> : {featureProperties.DisplayInfo.DetailedCategory}</div>");
builder.Append($"<div class=\"displayTitle\">{featureProperties.DisplayInfo.Title.ToUpper()}</div>");
if (featureProperties.HasUtmStatus())
{
builder.Append($"<div class=\"utmBadge\"><img src=\"{settings.CdnUrl}/images/icons/");
if (featureProperties.IsUtmReady())
{
builder.Append("utm-ready.png");
}
else if (featureProperties.IsUtmBasic())
{
builder.Append("utm-basic.png");
}
else
{
builder.Append("utm-legacy.png");
}
builder.Append("\"/></div>");
}
builder.Append("</div>");
if (featureProperties.UtmStatus?.UtmDetails != null && featureProperties.UtmStatus.Enabled)

if (featureProperties.HasUtmStatus())
{
builder.Append("<div class=\"utmStatus\">");
builder.Append("<div class=\"section\">");
builder.Append("<div class=\"displayTitle\">APPROVAL REQUIRED</div>");

if (!string.IsNullOrWhiteSpace(featureProperties.UtmStatus.Title))
if (featureProperties.IsUtmReady())
{
builder.Append($"<div class=\"title\">{markdown.Transform(featureProperties.UtmStatus.Title)}</div>");
}

builder.Append("<div class=\"displayTitle\">FACILITY IS UTM READY</div>");
builder.Append("<div class=\"utmReadyTitle\">Online Approval</div>");

if (!string.IsNullOrWhiteSpace(featureProperties.UtmStatus.Description))
}
else if (featureProperties.IsUtmBasic())
{
builder.Append("<div class=\"utmBasicTitle\">Notification Available</div>");
}
else
{
builder.Append($"<div class=\"text\">{markdown.Transform(featureProperties.UtmStatus.Description)}</div>");
builder.Append("<div class=\"utmLegacyTitle\">Not Connected</div>");
}
builder.Append("</div>");

foreach (var rateType in featureProperties.UtmStatus.RateTypes.Keys)
if (featureProperties.IsUtmEnabled())
{
var rateCard = featureProperties.UtmStatus.RateTypes[rateType]
.Where(c =>
{
if (c.AppliesFrom == null) return false;
var now = DateTimeOffset.UtcNow;
if (c.AppliesFrom > now) return false;
if (c.AppliesTo == null) return true;
return c.AppliesTo >= now;
})
.OrderBy(c => c.AppliesFrom)
.Select(c => rateCardDetails[c.Id])
.FirstOrDefault();
if (rateCard == null) continue;
builder.Append("<div class=\"rateType\">");
var isReady = featureProperties.IsUtmReady();
builder.Append($"<div class=\"{(isReady ? "utmReadyStatus" : "utmBasicStatus")}\">");
builder.Append("<div class=\"section\">");
builder.Append($"<div class=\"displayTitle\">{MapRateTypeToText(rateType)}</div>");
builder.Append("<div class=\"rateCard\">");
builder.Append($"<p>{featureProperties.DisplayInfo.Title.ToUpper()}</p>");
builder.Append($"<p>{rateCard.ExplanatoryText}</p>");
builder.Append("<ul>");
foreach (var rate in rateCard.Rates.OrderBy(r => r.Ordinal))

if (!string.IsNullOrWhiteSpace(featureProperties.UtmStatus.Title))
{
builder.Append("<li>");
builder.Append(rate.Name);
builder.Append(" (");
var total = rate.Rate + rateCard.StandingCharge;
total += rateCard.TaxRate / 100 * total;
builder.Append(CurrencyLookup.ContainsKey(rateCard.Currency)
? total.ToString("C", CurrencyLookup[rateCard.Currency])
: $"{total} {rateCard.Currency}");

builder.Append(")</li>");
builder.Append($"<div class=\"title\">{Markdown.ToHtml(featureProperties.UtmStatus.Title, pipeline)}</div>");
}
builder.Append("</ul>");
builder.Append("<p>If you wish to operate here, depending on your flight plan, you may require their prior approval.</p>");
builder.Append($"<p><a href=\"{rateCard.RateCardTerms}\">Terms and conditions</a></p>");
builder.Append("</div>");

builder.Append($"<div class=\"displayTitle\">{(isReady ? "FACILITY IS UTM CONNECTED" : "NOTIFY FACILITY")}</div>");

if (!string.IsNullOrWhiteSpace(featureProperties.UtmStatus.Description))
{
builder.Append($"<div class=\"text\">{Markdown.ToHtml(featureProperties.UtmStatus.Description, pipeline)}</div>");
}
else if (!isReady)
{
builder.Append("<div class=\"text\"><p>This facility supports prior notification. Submitting your flight plan will begin the approval process with the facility.<ul><li>Prior Notification</li></ul></p></div>");
}

foreach (var rateType in featureProperties.UtmStatus.RateTypes.Keys)
{
var rateCard = featureProperties.UtmStatus.RateTypes[rateType]
.Where(c =>
{
if (c.AppliesFrom == null) return false;
var now = DateTimeOffset.UtcNow;
if (c.AppliesFrom > now) return false;
if (c.AppliesTo == null) return true;
return c.AppliesTo >= now;
})
.OrderBy(c => c.AppliesFrom)
.Select(c => rateCardDetails[c.Id])
.FirstOrDefault();
if (rateCard == null) continue;
builder.Append("<div class=\"rateType\">");
builder.Append("<div class=\"section\">");
builder.Append($"<div class=\"displayTitle\">{MapRateTypeToText(rateType)}</div>");
builder.Append("<div class=\"rateCard\">");
builder.Append($"<p>{featureProperties.DisplayInfo.Title.ToUpper()}</p>");
builder.Append($"<p>{Markdown.ToHtml(rateCard.ExplanatoryText, pipeline)}</p>");
builder.Append("<ul>");
foreach (var rate in rateCard.Rates.OrderBy(r => r.Ordinal))
{
builder.Append("<li>");
builder.Append(rate.Name);
builder.Append(" (");
var total = rate.Rate + rateCard.StandingCharge;
total += rateCard.TaxRate / 100 * total;
builder.Append(CurrencyLookup.TryGetValue(rateCard.Currency, out var value)
? total.ToString("C", value)
: $"{total} {rateCard.Currency}");

builder.Append(")</li>");
}
builder.Append("</ul>");
builder.Append("<p>If you wish to operate here, depending on your flight plan, you may require their prior approval.</p>");
builder.Append($"<p><a href=\"{rateCard.RateCardTerms}\">Terms and conditions</a></p>");
builder.Append("</div>");
builder.Append("</div>");
builder.Append("</div>");
}

if (!string.IsNullOrWhiteSpace(featureProperties.UtmStatus.UtmDetails.ExternalUrl))
{
builder.Append($"<div class=\"button\"><a href=\"{featureProperties.UtmStatus.UtmDetails.ExternalUrl}\">{(isReady ? "Request To Fly Here" : "Notify Facility")}</a></div>");
}

builder.Append("</div>");
builder.Append("</div>");
}

if (!string.IsNullOrWhiteSpace(featureProperties.UtmStatus.UtmDetails.ExternalUrl))
{
builder.Append($"<div class=\"button\"><a href=\"{featureProperties.UtmStatus.UtmDetails.ExternalUrl}\">Request To Fly Here</a></div>");
}

builder.Append("</div>");
builder.Append("</div>");
}

if (featureProperties.Contact?.PhoneNumbers != null && featureProperties.Contact.PhoneNumbers.Count > 0)
{
if (featureProperties.UtmStatus?.UtmDetails != null && featureProperties.UtmStatus.Enabled)
if (featureProperties.IsUtmEnabled())
{
builder.Append("<div class=\"contact\">");
builder.Append("<div class=\"section\">");
Expand Down Expand Up @@ -153,12 +193,12 @@ public static string FormatAsHtml(this FeatureProperties featureProperties,

if (!string.IsNullOrWhiteSpace(section.Text))
{
builder.Append($"<div class=\"text\">{markdown.Transform(section.Text)}</div>");
builder.Append($"<div class=\"text\">{Markdown.ToHtml(section.Text, pipeline)}</div>");
}

if (!string.IsNullOrWhiteSpace(section.Disclaimer))
{
builder.Append($"<div class=\"disclaimer\">{markdown.Transform(section.Disclaimer)}</div>");
builder.Append($"<div class=\"disclaimer\">{Markdown.ToHtml(section.Disclaimer, pipeline)}</div>");
}
builder.Append("</div>");
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,52 @@ namespace AltitudeAngelWings.Clients.Api.Model
{
public static class FeaturePropertiesExtensions
{
public static bool HasUtmStatus(this FeatureProperties properties)
=> properties?.UtmStatus != null;

public static bool IsUtmEnabled(this FeatureProperties properties)
=> properties?.UtmStatus != null && properties.UtmStatus.Enabled;

public static bool IsUtmLegacy(this FeatureProperties properties)
{
if (!properties.HasUtmStatus())
{
return false;
}

return !properties.IsUtmEnabled();
}

public static bool IsUtmBasic(this FeatureProperties properties)
{
if (!properties.IsUtmEnabled())
{
return false;
}

if (string.IsNullOrEmpty(properties.UtmStatus.UtmDetails?.Id))
{
return false;
}

return properties.UtmStatus.UtmDetails.Id == "AA_UTM_READY";
}

public static bool IsUtmReady(this FeatureProperties properties)
{
if (!properties.IsUtmEnabled())
{
return false;
}

if (string.IsNullOrEmpty(properties.UtmStatus.UtmDetails?.Id))
{
return false;
}

return properties.UtmStatus.UtmDetails.Id == "AA_GUARDIAN_UTM";
}

public static ColorInfo ToColorInfo(this FeatureProperties properties, float opacityAdjust = 1f)
{
var fillColor = ToARGB(properties.FillColor, properties.FillOpacity, opacityAdjust);
Expand Down
1 change: 1 addition & 0 deletions ExtLibs/AltitudeAngelWings/ISettings.cs
Original file line number Diff line number Diff line change
Expand Up @@ -56,5 +56,6 @@ public interface ISettings
string FlightIdentifierSerialNumber { get; set; }
int AltitudeFilter { get; set; }
string SurveillanceUrl { get; }
string CdnUrl { get; }
}
}
16 changes: 16 additions & 0 deletions ExtLibs/AltitudeAngelWings/ServiceLocatorConfiguration.cs
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
using AltitudeAngelWings.Service.OutboundNotifications;
using Flurl.Http;
using Flurl.Http.Configuration;
using Markdig;
using Newtonsoft.Json;
using Newtonsoft.Json.Serialization;
using NodaTime;
Expand Down Expand Up @@ -144,6 +145,21 @@ public void Configure()
l.Resolve<IApiClient>(),
l.Resolve<ITelemetryService>(),
l.Resolve<IFlightService>()));
ServiceLocator.Register(l => new MarkdownPipelineBuilder()
.UseAbbreviations()
.UseAutoIdentifiers()
.UseCitations()
.UseDefinitionLists()
.UseEmphasisExtras()
.UseFooters()
.UseFootnotes()
.UseGridTables()
.UsePipeTables()
.UseListExtras()
.UseTaskLists()
.DisableHtml()
.UseSmartyPants()
.Build());
}

private static void ResetAccessToken(ISettings settings)
Expand Down
8 changes: 7 additions & 1 deletion ExtLibs/AltitudeAngelWings/Settings.cs
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,13 @@ public bool UseFlights

public string SurveillanceUrl => $"https://surveillance-api.{UrlDomainSuffix}";

public string UrlDomainSuffix => OverrideClientUrlSettings ? OverrideUrlDomainSuffix : "altitudeangel.com";
public string CdnUrl => UrlDomainSuffix == DefaultDomainSuffix
? "https://dronesafetymap.com"
: $"https://map.{UrlDomainSuffix}";

private const string DefaultDomainSuffix = "altitudeangel.com";

public string UrlDomainSuffix => OverrideClientUrlSettings ? OverrideUrlDomainSuffix : DefaultDomainSuffix;

public bool OverrideClientUrlSettings
{
Expand Down

0 comments on commit 4203d27

Please sign in to comment.