From 7fa39674a5f1661ca6b522cf32999aa57233b372 Mon Sep 17 00:00:00 2001 From: Emmanuel Mathot Date: Mon, 5 Jul 2021 18:18:43 +0200 Subject: [PATCH] PropertyObservableCollection --- src/DotNetStac.Test/Item/ItemTests.cs | 20 + .../Item/ItemTests_ItemProvidersIn.json | 662 ++++++++++++++++++ .../Common/PropertyObservableCollection.cs | 27 + src/DotNetStac/IStacPropertiesContainer.cs | 1 + src/DotNetStac/StacAccessorsHelpers.cs | 18 + src/DotNetStac/StacCatalog.CommonMetadata.cs | 6 +- .../StacCollection.CommonMetadata.cs | 6 +- src/DotNetStac/StacCollection.cs | 2 - src/DotNetStac/StacItem.CommonMetadata.cs | 6 +- 9 files changed, 731 insertions(+), 17 deletions(-) create mode 100644 src/DotNetStac.Test/Resources/Item/ItemTests_ItemProvidersIn.json create mode 100644 src/DotNetStac/Common/PropertyObservableCollection.cs diff --git a/src/DotNetStac.Test/Item/ItemTests.cs b/src/DotNetStac.Test/Item/ItemTests.cs index 621a1a18..6b3143b5 100644 --- a/src/DotNetStac.Test/Item/ItemTests.cs +++ b/src/DotNetStac.Test/Item/ItemTests.cs @@ -321,5 +321,25 @@ public void ItemClone() JsonAssert.AreEqual(simpleJson, expectedJson); } + + [Fact] + public void ItemProviders() + { + var simpleJson = GetJson("Item", "ItemProvidersIn"); + ValidateJson(simpleJson); + StacItem simpleItem = StacConvert.Deserialize(simpleJson); + + simpleItem.Providers.Add(new StacProvider("ESA", new StacProviderRole[] { StacProviderRole.licensor })); + + Assert.Contains("providers", simpleItem.Properties); + + simpleItem.Providers.RemoveAt(0); + + Assert.DoesNotContain("providers", simpleItem.Properties); + + var newJson = StacConvert.Serialize(simpleItem); + ValidateJson(newJson); + + } } } diff --git a/src/DotNetStac.Test/Resources/Item/ItemTests_ItemProvidersIn.json b/src/DotNetStac.Test/Resources/Item/ItemTests_ItemProvidersIn.json new file mode 100644 index 00000000..5ce267bb --- /dev/null +++ b/src/DotNetStac.Test/Resources/Item/ItemTests_ItemProvidersIn.json @@ -0,0 +1,662 @@ +{ + "type": "Feature", + "stac_version": "1.0.0", + "stac_extensions": [ + "https://stac-extensions.github.io/eo/v1.0.0/schema.json", + "https://stac-extensions.github.io/projection/v1.0.0/schema.json", + "https://stac-extensions.github.io/sat/v1.0.0/schema.json" + ], + "id": "S2A_30VWN_20200830_0_L2A", + "bbox": [ + -3.000355032202007, + 60.33378967715628, + -0.9494552273687494, + 61.33443350563737 + ], + "geometry": { + "type": "Polygon", + "coordinates": [ + [ + [ + -1.0115813842949837, + 60.33378967715628 + ], + [ + -3.000344266875852, + 60.34865379793714 + ], + [ + -3.000355032202007, + 61.33443350563737 + ], + [ + -0.9494552273687494, + 61.31895930227699 + ], + [ + -1.0115813842949837, + 60.33378967715628 + ] + ] + ] + }, + "properties": { + "datetime": "2020-08-30T11:34:28Z", + "platform": "sentinel-2a", + "constellation": "sentinel-2", + "instruments": [ + "msi" + ], + "gsd": 10, + "view:off_nadir": 0, + "proj:epsg": 32630, + "sat:relative_orbit": 80, + "sentinel:utm_zone": 30, + "sentinel:latitude_band": "V", + "sentinel:grid_square": "WN", + "sentinel:sequence": "0", + "sentinel:product_id": "S2A_MSIL2A_20200830T113321_N0214_R080_T30VWN_20200830T121958", + "sentinel:data_coverage": 100, + "eo:cloud_cover": 0, + "sentinel:valid_cloud_cover": false, + "created": "2020-08-31T09:57:42.772Z", + "updated": "2020-08-31T09:57:42.772Z" + }, + "collection": "sentinel-s2-l2a-cogs", + "assets": { + "thumbnail": { + "title": "Thumbnail", + "type": "image/png", + "roles": [ + "thumbnail" + ], + "href": "https://roda.sentinel-hub.com/sentinel-s2-l1c/tiles/30/V/WN/2020/8/30/0/preview.jpg" + }, + "overview": { + "title": "True color image", + "type": "image/tiff; application=geotiff; profile=cloud-optimized", + "roles": [ + "overview" + ], + "gsd": 10, + "eo:bands": [ + { + "name": "B04", + "common_name": "red", + "center_wavelength": 0.6645, + "full_width_half_max": 0.038 + }, + { + "name": "B03", + "common_name": "green", + "center_wavelength": 0.56, + "full_width_half_max": 0.045 + }, + { + "name": "B02", + "common_name": "blue", + "center_wavelength": 0.4966, + "full_width_half_max": 0.098 + } + ], + "href": "https://sentinel-cogs.s3.us-west-2.amazonaws.com/sentinel-s2-l2a-cogs/30/V/WN/2020/8/S2A_30VWN_20200830_0_L2A/L2A_PVI.tif", + "proj:shape": [ + 343, + 343 + ], + "proj:transform": [ + 320, + 0, + 499980, + 0, + -320, + 6800040, + 0, + 0, + 1 + ] + }, + "info": { + "title": "Original JSON metadata", + "type": "application/json", + "roles": [ + "metadata" + ], + "href": "https://roda.sentinel-hub.com/sentinel-s2-l2a/tiles/30/V/WN/2020/8/30/0/tileInfo.json" + }, + "metadata": { + "title": "Original XML metadata", + "type": "application/xml", + "roles": [ + "metadata" + ], + "href": "https://roda.sentinel-hub.com/sentinel-s2-l2a/tiles/30/V/WN/2020/8/30/0/metadata.xml" + }, + "visual": { + "title": "True color image", + "type": "image/tiff; application=geotiff; profile=cloud-optimized", + "roles": [ + "overview" + ], + "gsd": 10, + "eo:bands": [ + { + "name": "B04", + "common_name": "red", + "center_wavelength": 0.6645, + "full_width_half_max": 0.038 + }, + { + "name": "B03", + "common_name": "green", + "center_wavelength": 0.56, + "full_width_half_max": 0.045 + }, + { + "name": "B02", + "common_name": "blue", + "center_wavelength": 0.4966, + "full_width_half_max": 0.098 + } + ], + "href": "https://sentinel-cogs.s3.us-west-2.amazonaws.com/sentinel-s2-l2a-cogs/30/V/WN/2020/8/S2A_30VWN_20200830_0_L2A/TCI.tif", + "proj:shape": [ + 10980, + 10980 + ], + "proj:transform": [ + 10, + 0, + 499980, + 0, + -10, + 6800040, + 0, + 0, + 1 + ] + }, + "B01": { + "title": "Band 1 (coastal)", + "type": "image/tiff; application=geotiff; profile=cloud-optimized", + "roles": [ + "data" + ], + "gsd": 60, + "eo:bands": [ + { + "name": "B01", + "common_name": "coastal", + "center_wavelength": 0.4439, + "full_width_half_max": 0.027 + } + ], + "href": "https://sentinel-cogs.s3.us-west-2.amazonaws.com/sentinel-s2-l2a-cogs/30/V/WN/2020/8/S2A_30VWN_20200830_0_L2A/B01.tif", + "proj:shape": [ + 1830, + 1830 + ], + "proj:transform": [ + 60, + 0, + 499980, + 0, + -60, + 6800040, + 0, + 0, + 1 + ] + }, + "B02": { + "title": "Band 2 (blue)", + "type": "image/tiff; application=geotiff; profile=cloud-optimized", + "roles": [ + "data" + ], + "gsd": 10, + "eo:bands": [ + { + "name": "B02", + "common_name": "blue", + "center_wavelength": 0.4966, + "full_width_half_max": 0.098 + } + ], + "href": "https://sentinel-cogs.s3.us-west-2.amazonaws.com/sentinel-s2-l2a-cogs/30/V/WN/2020/8/S2A_30VWN_20200830_0_L2A/B02.tif", + "proj:shape": [ + 10980, + 10980 + ], + "proj:transform": [ + 10, + 0, + 499980, + 0, + -10, + 6800040, + 0, + 0, + 1 + ] + }, + "B03": { + "title": "Band 3 (green)", + "type": "image/tiff; application=geotiff; profile=cloud-optimized", + "roles": [ + "data" + ], + "gsd": 10, + "eo:bands": [ + { + "name": "B03", + "common_name": "green", + "center_wavelength": 0.56, + "full_width_half_max": 0.045 + } + ], + "href": "https://sentinel-cogs.s3.us-west-2.amazonaws.com/sentinel-s2-l2a-cogs/30/V/WN/2020/8/S2A_30VWN_20200830_0_L2A/B03.tif", + "proj:shape": [ + 10980, + 10980 + ], + "proj:transform": [ + 10, + 0, + 499980, + 0, + -10, + 6800040, + 0, + 0, + 1 + ] + }, + "B04": { + "title": "Band 4 (red)", + "type": "image/tiff; application=geotiff; profile=cloud-optimized", + "roles": [ + "data" + ], + "gsd": 10, + "eo:bands": [ + { + "name": "B04", + "common_name": "red", + "center_wavelength": 0.6645, + "full_width_half_max": 0.038 + } + ], + "href": "https://sentinel-cogs.s3.us-west-2.amazonaws.com/sentinel-s2-l2a-cogs/30/V/WN/2020/8/S2A_30VWN_20200830_0_L2A/B04.tif", + "proj:shape": [ + 10980, + 10980 + ], + "proj:transform": [ + 10, + 0, + 499980, + 0, + -10, + 6800040, + 0, + 0, + 1 + ] + }, + "B05": { + "title": "Band 5", + "type": "image/tiff; application=geotiff; profile=cloud-optimized", + "roles": [ + "data" + ], + "gsd": 20, + "eo:bands": [ + { + "name": "B05", + "center_wavelength": 0.7039, + "full_width_half_max": 0.019 + } + ], + "href": "https://sentinel-cogs.s3.us-west-2.amazonaws.com/sentinel-s2-l2a-cogs/30/V/WN/2020/8/S2A_30VWN_20200830_0_L2A/B05.tif", + "proj:shape": [ + 5490, + 5490 + ], + "proj:transform": [ + 20, + 0, + 499980, + 0, + -20, + 6800040, + 0, + 0, + 1 + ] + }, + "B06": { + "title": "Band 6", + "type": "image/tiff; application=geotiff; profile=cloud-optimized", + "roles": [ + "data" + ], + "gsd": 20, + "eo:bands": [ + { + "name": "B06", + "center_wavelength": 0.7402, + "full_width_half_max": 0.018 + } + ], + "href": "https://sentinel-cogs.s3.us-west-2.amazonaws.com/sentinel-s2-l2a-cogs/30/V/WN/2020/8/S2A_30VWN_20200830_0_L2A/B06.tif", + "proj:shape": [ + 5490, + 5490 + ], + "proj:transform": [ + 20, + 0, + 499980, + 0, + -20, + 6800040, + 0, + 0, + 1 + ] + }, + "B07": { + "title": "Band 7", + "type": "image/tiff; application=geotiff; profile=cloud-optimized", + "roles": [ + "data" + ], + "gsd": 20, + "eo:bands": [ + { + "name": "B07", + "center_wavelength": 0.7825, + "full_width_half_max": 0.028 + } + ], + "href": "https://sentinel-cogs.s3.us-west-2.amazonaws.com/sentinel-s2-l2a-cogs/30/V/WN/2020/8/S2A_30VWN_20200830_0_L2A/B07.tif", + "proj:shape": [ + 5490, + 5490 + ], + "proj:transform": [ + 20, + 0, + 499980, + 0, + -20, + 6800040, + 0, + 0, + 1 + ] + }, + "B08": { + "title": "Band 8 (nir)", + "type": "image/tiff; application=geotiff; profile=cloud-optimized", + "roles": [ + "data" + ], + "gsd": 10, + "eo:bands": [ + { + "name": "B08", + "common_name": "nir", + "center_wavelength": 0.8351, + "full_width_half_max": 0.145 + } + ], + "href": "https://sentinel-cogs.s3.us-west-2.amazonaws.com/sentinel-s2-l2a-cogs/30/V/WN/2020/8/S2A_30VWN_20200830_0_L2A/B08.tif", + "proj:shape": [ + 10980, + 10980 + ], + "proj:transform": [ + 10, + 0, + 499980, + 0, + -10, + 6800040, + 0, + 0, + 1 + ] + }, + "B8A": { + "title": "Band 8A", + "type": "image/tiff; application=geotiff; profile=cloud-optimized", + "roles": [ + "data" + ], + "gsd": 20, + "eo:bands": [ + { + "name": "B8A", + "center_wavelength": 0.8648, + "full_width_half_max": 0.033 + } + ], + "href": "https://sentinel-cogs.s3.us-west-2.amazonaws.com/sentinel-s2-l2a-cogs/30/V/WN/2020/8/S2A_30VWN_20200830_0_L2A/B8A.tif", + "proj:shape": [ + 5490, + 5490 + ], + "proj:transform": [ + 20, + 0, + 499980, + 0, + -20, + 6800040, + 0, + 0, + 1 + ] + }, + "B09": { + "title": "Band 9", + "type": "image/tiff; application=geotiff; profile=cloud-optimized", + "roles": [ + "data" + ], + "gsd": 60, + "eo:bands": [ + { + "name": "B09", + "center_wavelength": 0.945, + "full_width_half_max": 0.026 + } + ], + "href": "https://sentinel-cogs.s3.us-west-2.amazonaws.com/sentinel-s2-l2a-cogs/30/V/WN/2020/8/S2A_30VWN_20200830_0_L2A/B09.tif", + "proj:shape": [ + 1830, + 1830 + ], + "proj:transform": [ + 60, + 0, + 499980, + 0, + -60, + 6800040, + 0, + 0, + 1 + ] + }, + "B11": { + "title": "Band 11 (swir16)", + "type": "image/tiff; application=geotiff; profile=cloud-optimized", + "roles": [ + "data" + ], + "gsd": 20, + "eo:bands": [ + { + "name": "B11", + "common_name": "swir16", + "center_wavelength": 1.6137, + "full_width_half_max": 0.143 + } + ], + "href": "https://sentinel-cogs.s3.us-west-2.amazonaws.com/sentinel-s2-l2a-cogs/30/V/WN/2020/8/S2A_30VWN_20200830_0_L2A/B11.tif", + "proj:shape": [ + 5490, + 5490 + ], + "proj:transform": [ + 20, + 0, + 499980, + 0, + -20, + 6800040, + 0, + 0, + 1 + ] + }, + "B12": { + "title": "Band 12 (swir22)", + "type": "image/tiff; application=geotiff; profile=cloud-optimized", + "roles": [ + "data" + ], + "gsd": 20, + "eo:bands": [ + { + "name": "B12", + "common_name": "swir22", + "center_wavelength": 2.22024, + "full_width_half_max": 0.242 + } + ], + "href": "https://sentinel-cogs.s3.us-west-2.amazonaws.com/sentinel-s2-l2a-cogs/30/V/WN/2020/8/S2A_30VWN_20200830_0_L2A/B12.tif", + "proj:shape": [ + 5490, + 5490 + ], + "proj:transform": [ + 20, + 0, + 499980, + 0, + -20, + 6800040, + 0, + 0, + 1 + ] + }, + "AOT": { + "title": "Aerosol Optical Thickness (AOT)", + "type": "image/tiff; application=geotiff; profile=cloud-optimized", + "roles": [ + "data" + ], + "href": "https://sentinel-cogs.s3.us-west-2.amazonaws.com/sentinel-s2-l2a-cogs/30/V/WN/2020/8/S2A_30VWN_20200830_0_L2A/AOT.tif", + "proj:shape": [ + 1830, + 1830 + ], + "proj:transform": [ + 60, + 0, + 499980, + 0, + -60, + 6800040, + 0, + 0, + 1 + ] + }, + "WVP": { + "title": "Water Vapour (WVP)", + "type": "image/tiff; application=geotiff; profile=cloud-optimized", + "roles": [ + "data" + ], + "href": "https://sentinel-cogs.s3.us-west-2.amazonaws.com/sentinel-s2-l2a-cogs/30/V/WN/2020/8/S2A_30VWN_20200830_0_L2A/WVP.tif", + "proj:shape": [ + 10980, + 10980 + ], + "proj:transform": [ + 10, + 0, + 499980, + 0, + -10, + 6800040, + 0, + 0, + 1 + ] + }, + "SCL": { + "title": "Scene Classification Map (SCL)", + "type": "image/tiff; application=geotiff; profile=cloud-optimized", + "roles": [ + "data" + ], + "href": "https://sentinel-cogs.s3.us-west-2.amazonaws.com/sentinel-s2-l2a-cogs/30/V/WN/2020/8/S2A_30VWN_20200830_0_L2A/SCL.tif", + "proj:shape": [ + 5490, + 5490 + ], + "proj:transform": [ + 20, + 0, + 499980, + 0, + -20, + 6800040, + 0, + 0, + 1 + ] + } + }, + "links": [ + { + "rel": "self", + "href": "https://earth-search.aws.element84.com/v0/collections/sentinel-s2-l2a-cogs/items/S2A_30VWN_20200830_0_L2A" + }, + { + "rel": "canonical", + "href": "https://sentinel-cogs.s3.us-west-2.amazonaws.com/sentinel-s2-l2a-cogs/30/V/WN/2020/8/S2A_30VWN_20200830_0_L2A/S2A_30VWN_20200830_0_L2A.json", + "type": "application/json" + }, + { + "rel": "canonical", + "href": "https://cirrus-v0-data-1qm7gekzjucbq.s3.us-west-2.amazonaws.com/sentinel-s2-l2a/30/V/WN/2020/8/S2A_30VWN_20200830_0_L2A/S2A_30VWN_20200830_0_L2A.json", + "type": "application/json" + }, + { + "title": "Source STAC Item", + "rel": "derived_from", + "href": "https://cirrus-v0-data-1qm7gekzjucbq.s3.us-west-2.amazonaws.com/sentinel-s2-l2a/30/V/WN/2020/8/S2A_30VWN_20200830_0_L2A/S2A_30VWN_20200830_0_L2A.json", + "type": "application/json" + }, + { + "rel": "parent", + "href": "https://earth-search.aws.element84.com/v0/collections/sentinel-s2-l2a-cogs" + }, + { + "rel": "collection", + "href": "https://earth-search.aws.element84.com/v0/collections/sentinel-s2-l2a-cogs" + }, + { + "rel": "root", + "href": "https://earth-search.aws.element84.com/v0/" + } + ] +} \ No newline at end of file diff --git a/src/DotNetStac/Common/PropertyObservableCollection.cs b/src/DotNetStac/Common/PropertyObservableCollection.cs new file mode 100644 index 00000000..bdca977c --- /dev/null +++ b/src/DotNetStac/Common/PropertyObservableCollection.cs @@ -0,0 +1,27 @@ +using System.Collections.Generic; +using System.Collections.ObjectModel; +using System.Collections.Specialized; +using System.Linq; + +namespace Stac.Common +{ + public class PropertyObservableCollection : ObservableCollection + { + public PropertyObservableCollection(IStacPropertiesContainer propertiesContainer, string key) : base() + { + PropertiesContainer = propertiesContainer; + Key = key; + this.CollectionChanged += ObservableCollectionInPropertiesChanged; + } + + public IStacPropertiesContainer PropertiesContainer { get; } + public string Key { get; } + + private void ObservableCollectionInPropertiesChanged(object sender, NotifyCollectionChangedEventArgs e) + { + PropertiesContainer.RemoveProperty(Key); + if ( this.Count == 0 ) return; + PropertiesContainer.SetProperty(Key, this.ToList()); + } + } +} diff --git a/src/DotNetStac/IStacPropertiesContainer.cs b/src/DotNetStac/IStacPropertiesContainer.cs index 06cae17b..6b355299 100644 --- a/src/DotNetStac/IStacPropertiesContainer.cs +++ b/src/DotNetStac/IStacPropertiesContainer.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Generic; using System.Collections.ObjectModel; +using System.Collections.Specialized; using Newtonsoft.Json.Linq; using Stac.Extensions; diff --git a/src/DotNetStac/StacAccessorsHelpers.cs b/src/DotNetStac/StacAccessorsHelpers.cs index 80c09c5c..3a5a191d 100644 --- a/src/DotNetStac/StacAccessorsHelpers.cs +++ b/src/DotNetStac/StacAccessorsHelpers.cs @@ -1,8 +1,11 @@ using System; using System.Collections.Generic; +using System.Collections.ObjectModel; +using System.Collections.Specialized; using System.Linq; using System.Reflection; using Newtonsoft.Json.Linq; +using Stac.Common; namespace Stac { @@ -47,12 +50,27 @@ public static T GetProperty(this IDictionary properties, stri var @object = GetProperty(properties, key); if (@object == null) return default(T); if (@object is JToken) + { return (@object as JToken).ToObject(); + } if (typeof(T).GetTypeInfo().IsEnum) return (T)Enum.Parse(typeof(T), @object.ToString()); return ChangeType(@object); } + public static PropertyObservableCollection GetObservableCollectionProperty(this IStacPropertiesContainer propertiesContainer, string key) + { + List array = propertiesContainer.GetProperty>(key); + PropertyObservableCollection observableCollection = new PropertyObservableCollection(propertiesContainer, key); + if (array != null && array.Count > 0) + { + observableCollection.AddRange(array); + } + return observableCollection; + } + + + public static T ChangeType(object value) { var t = typeof(T); diff --git a/src/DotNetStac/StacCatalog.CommonMetadata.cs b/src/DotNetStac/StacCatalog.CommonMetadata.cs index efe5e003..1ae0c902 100644 --- a/src/DotNetStac/StacCatalog.CommonMetadata.cs +++ b/src/DotNetStac/StacCatalog.CommonMetadata.cs @@ -36,11 +36,7 @@ public string License /// Providers should be listed in chronological order with the most recent provider being the last element of the list. /// /// - public Collection Providers - { - get => this.GetProperty>("providers"); - set => this.SetProperty("providers", value); - } + public Collection Providers => this.GetObservableCollectionProperty("providers"); public string Platform { diff --git a/src/DotNetStac/StacCollection.CommonMetadata.cs b/src/DotNetStac/StacCollection.CommonMetadata.cs index 717f84d8..c44220e9 100644 --- a/src/DotNetStac/StacCollection.CommonMetadata.cs +++ b/src/DotNetStac/StacCollection.CommonMetadata.cs @@ -48,11 +48,7 @@ public string License /// Providers should be listed in chronological order with the most recent provider being the last element of the list. /// /// - public Collection Providers - { - get => this.GetProperty>("providers"); - set => this.SetProperty("providers", value); - } + public Collection Providers => this.GetObservableCollectionProperty("providers"); public string Platform { diff --git a/src/DotNetStac/StacCollection.cs b/src/DotNetStac/StacCollection.cs index 120f6402..973979b4 100644 --- a/src/DotNetStac/StacCollection.cs +++ b/src/DotNetStac/StacCollection.cs @@ -44,7 +44,6 @@ public StacCollection(string id, this.Assets = new Dictionary(assets); this.Summaries = new Dictionary(); this.StacExtensions = new SortedSet(); - this.Providers = new Collection(); this.License = license; this.Keywords = new Collection(); this.Extent = extent; @@ -63,7 +62,6 @@ public StacCollection(StacCollection stacCollection) this.Summaries = new Dictionary(stacCollection.Summaries); this.Properties = new Dictionary(stacCollection.Properties); this.Assets = new Dictionary(stacCollection.Assets); - this.Providers = new Collection(stacCollection.Providers); this.License = stacCollection.License; this.Keywords = new Collection(stacCollection.Keywords); this.Extent = new StacExtent(stacCollection.Extent); diff --git a/src/DotNetStac/StacItem.CommonMetadata.cs b/src/DotNetStac/StacItem.CommonMetadata.cs index 8aae97a8..b31d6331 100644 --- a/src/DotNetStac/StacItem.CommonMetadata.cs +++ b/src/DotNetStac/StacItem.CommonMetadata.cs @@ -40,11 +40,7 @@ public string License /// Providers should be listed in chronological order with the most recent provider being the last element of the list. /// /// - public Collection Providers - { - get => this.GetProperty>("providers"); - set => this.SetProperty("providers", value); - } + public Collection Providers => this.GetObservableCollectionProperty("providers"); public string Platform {