UWP MapControl highlight countries - c#

I’m writing a little app which will receive a country code (2 ISO letters) and a state code (also 2 letters ISO code).
I would like to highlight (And color) the region specified by these 2 information (So let’s say “CA, QC”, will highlight Quebec state in Canada)
I don’t need Anything else (Well, maybe FadeIn, FadeOut animation, but I’ll figure this one out later)
All zoom/tap/click/other actions are blocked.
The MapControl declaration is really easy :
<maps:MapControl Grid.Row="1" x:Name="myMap" ZoomLevel="0"/>
Thanks in advance
Edit: After a lot of research, the help from following answer, I’m astonished that a BASIC action is NOT a part of Microsoft’s platform. That’s insane. All back end was coded in less than 30 minutes (Including authentication, listing properties, checking access level, setting up SignalR callbacks), but on the visual side, welp, we have NOTHING from UWP platform. That’s just sad.
/bye UWP, I’ve tried. multiple times.
Edit 2 : Made it work with some adjustements :
if (feature != null && (feature.Geometry.Type == GeoJSONObjectType.Polygon) || (feature.Geometry.Type == GeoJSONObjectType.MultiPolygon))
{
myMap.MapElements.Clear();
MapPolygon polygon = null;
if (feature.Geometry.Type == GeoJSONObjectType.Polygon)
{
var polygonGeometry = feature.Geometry as Polygon;
polygon = new MapPolygon
{
Path = new Geopath(polygonGeometry.Coordinates[0].Coordinates.Select(coord => new BasicGeoposition() { Latitude = coord.Latitude, Longitude = coord.Longitude })),
FillColor = Colors.DarkRed
};
myMap.MapElements.Add(polygon);
}
else
{
var ploy = (feature.Geometry as MultiPolygon);
foreach (var item in ploy.Coordinates)
{
var polygon1 = new MapPolygon
{
Path = new Geopath(item.Coordinates[0].Coordinates.Select(coord => new BasicGeoposition() { Latitude = coord.Latitude, Longitude = coord.Longitude })),
FillColor = Colors.DarkRed
};
myMap.MapElements.Add(polygon1);
}
}
}

There is no built-in way to achieve this, so you will have to do some additional steps to make this work.
First you need to download a geojson-based dataset with polygonial definitions of all countries. One lightweight and functional can be found here on GitHub.
Now you need to install the GeoJSON.NET package from NuGet into your project and include the downloaded .geojson file in your project, for example in the Assets folder. Make sure that its Build action is set to Content.
Now you can use code like this to highlight a country by creating a MapPolygon and placing it on the map:
private FeatureCollection _countryPolygons = null;
private async void HighlightClick(string country)
{
if (_countryPolygons == null)
{
_countryPolygons = JsonConvert.DeserializeObject<FeatureCollection>(
await FileIO.ReadTextAsync(
await StorageFile.GetFileFromApplicationUriAsync(new Uri("ms-appx:///Assets/countries.geojson",
UriKind.Absolute))));
}
var feature = _countryPolygons.Features.FirstOrDefault(f =>
f.Id.Equals(country, StringComparison.CurrentCultureIgnoreCase));
if (feature != null && feature.Geometry.Type == GeoJSONObjectType.Polygon)
{
var polygonGeometry = feature.Geometry as Polygon;
MapPolygon polygon = new MapPolygon();
polygon.Path = new Geopath(polygonGeometry.Coordinates[0].Coordinates.Select(coord => new BasicGeoposition() { Latitude = coord.Latitude, Longitude = coord.Longitude }));
polygon.FillColor = Colors.DeepSkyBlue;
Map.MapElements.Clear();
Map.MapElements.Add(polygon);
}
}

Related

How to vertically format embedded fields

Current Formatting For Embed Fields
Here is an embed I currently use for my semi-public Ark Servers.
First field is the Map name,
Second field is the direct connect IP Address,
Third field is if/where there is a community base on that map.
As you can see it works as intended but if there's to much info on a single line in the field the formatting is screwed up. Is there a way to fix this?
I'm using 3 separate stream builders to build the different fields and then adding them to the embed. If code is needed I can post a "dumbed down version" so it doesn't take up the whole page.
var linkHeading = "steam://connect/";
var sb = new StringBuilder();
var sb2 = new StringBuilder();
var sb3 = new StringBuilder();
var embed = new EmbedBuilder();
embed.WithColor(new Color(0, 255, 0));
embed.Title = "List of Server Ips";
JObject o1;
using (StreamReader file = File.OpenText("serverips.json"))
using (JsonTextReader reader = new JsonTextReader(file))
{
o1 = (JObject)JToken.ReadFrom(reader);
}
var ipsObject = JsonConvert.DeserializeObject<Rootobject>(o1.ToString());
sb.AppendLine("The Island: ");
sb2.AppendLine($"{linkHeading}{ipsObject.TheIsland.ip}:{ipsObject.TheIsland.port}/");
if(ipsObject.TheIsland.comm != "")
{
sb3.AppendLine($"Comm: {ipsObject.TheIsland.comm}");
} else { sb3.AppendLine($"No Comm Info Available"); };
sb.AppendLine("Aberration: ");
sb2.AppendLine($"{linkHeading}{ipsObject.Aberration.ip}:{ipsObject.Aberration.port}/");
if (ipsObject.Aberration.comm != "")
{
sb3.AppendLine($"Comm: {ipsObject.Aberration.comm}");
} else { sb3.AppendLine($"No Comm Info Available"); };
embed.WithDescription($"Cluster Ip and Comm Information");
embed.AddField(x =>
{
x.Name = "Map";
x.Value = sb.ToString();
x.IsInline = true;
});
embed.AddField(x =>
{
x.Name = "IP";
x.Value = sb2.ToString();
x.IsInline = true;
});
embed.AddField(x =>
{
x.Name = "Comm?";
x.Value = sb3.ToString();
x.IsInline = true;
});
await Context.User.SendMessageAsync(null, false, embed.Build());
await ReplyAsync("Server Ip List was sent directly to your inbox! :)");
You don't have that much control over how embed fields are displayed. The only thing you control in regards to fields are if they inline or not. The rendering is completely up to Discord and the end users screen size. For example, your current output on mobile will ignore the inline setting and list the fields one on top of the other instead of side by side.
Unless your fields consistently contain a small amount of text each you can't guarantee how the end use will see the output. If you need to guarantee some sort of consistent structured display across all devices, your best bet is to use an image.

How to load pins just around your current location and load more as you zoom out in Xamarin.Forms.Maps

I’m trying to show pins on the map but only the ones that can fit the screen around your current location depending on the zoom level I’ve set on when the map appears, because I have 10’s of pins and when I open the map it loads every pin and takes a long time to load.
Any idea on how to do it ?
Method to load pins:
async Task ExecuteLoadPinsCommand()
{
IsBusy = true;
try
{
Map.Pins.Clear();
Map.MapElements.Clear();
Map.CustomPins.Clear();
var contents = await placeRepository.GetAllPlacesWithoutRelatedDataAsync();
if (contents == null || contents.Count < 1)
{
await App.Current.MainPage.DisplayAlert("No places found", "No places have been found for that category, please try again later", "Ok");
await ExecuteLoadPinsCommand();
}
if (contents != null)
{
places.Clear();
var customPins = this.Map.CustomPins;
places = contents;
foreach (var item in places)
{
CustomPin devicePin = new CustomPin
{
Type = PinType.Place,
PlaceId = item.PlaceId.ToString(),
Position = new Position(item.Latitude, item.Longitude),
Label = $"{item.Name}",
Address = $"{item.Name}"
};
Map.CustomPins.Add(devicePin);
Map.Pins.Add(devicePin);
}
}
}
catch (Exception ex)
{
Debug.WriteLine(ex);
}
finally
{
IsBusy = false;
}
}
CustomMapRenderer:
protected override MarkerOptions CreateMarker(Pin pin)
{
CustomPin pin = (CustomPin)pin;
var thePlace = Task.Run(async () => await placeRepository.GetPlaceByIdWithMoodAndEventsAsync(Guid.Parse(pin.PlaceId)));
var place = thePlace.ConfigureAwait(true)
.GetAwaiter()
.GetResult();
var marker = new MarkerOptions();
marker.SetPosition(new LatLng(place.Position.Latitude, place.Position.Longitude));
if (place.Category == "" && place.SomethingCount == 0)
{
marker.SetIcon(BitmapDescriptorFactory.FromResource(Resource.Drawable.icon));
}
//else if ...
return marker;
}
A major part of programming is learning to debug well.
When facing a performance problem, it is important to isolate the time delay to the smallest bit of code that you can.
Here's the thought process I would go through:
YOUR OBSERVATION: When there is one pin, it takes less than a second. When there are 20 pins, it takes maybe 10 seconds. (9 second difference.)
YOUR HYPOTHESIS (Given the question you posted): Maybe adding 20 pins to map takes much or most of the 9 seconds.
TESTS: How can we test EXACTLY the code that "adds pins to map"?
A: "Divide and conquer":
Let all the other code run 20 times. That is, have 20 pins as data. BUT suppress the code that adds those pins.
Test #1: Have 20 pins returned by GetAllPlacesWithoutRelatedDataAsync. So all that work is done 20 times.
Comment out JUST the code that ADDS the pins. Make this change:
//Map.CustomPins.Add(devicePin);
//Map.Pins.Add(devicePin);
Result #1: _____ seconds
Its possible that having NO pins allows the map to skip loading some pin-related code. Lets find out how quick it is when we only ADD ONE of the 20 pins.
Test #2: Have 20 pins in the data. BUT only ADD one of them. Make this ONE-LINE change:
foreach (var item in places)
{
CustomPin devicePin = new CustomPin
{
Type = PinType.Place,
PlaceId = item.PlaceId.ToString(),
Position = new Position(item.Latitude, item.Longitude),
Label = $"{item.Name}",
Address = $"{item.Name}"
};
Map.CustomPins.Add(devicePin);
Map.Pins.Add(devicePin);
break; // <--- ADD THIS LINE.
}
Result #2: _____ seconds
(Test #2 is what I was trying to ask you to do, in one of my comments on the question. I've deleted those comments.)
Now we have enough information to determine how much of the ~9 extra seconds are due to going through that foreach loop 20 times, to ADD 20 pins.
This will determine whether there is any point in trying to speed up the ADDS, or whether there is a problem elsewhere.
If most time is spent elsewhere, then you need to add the suspect code to the question. Then do similar tests there. Until you can report exactly what code takes most of the time.
IF 20x map.Pins.Add(..) takes a significant amount of time, THEN here are two techniques, either of which should be faster, imho.
FASTER ADD #1:
Use Map.ItemsSource.
FASTER ADD #2:
Create the map WITH its pins, BEFORE displaying it.
using Xamarin.Forms;
using Xamarin.Forms.Maps;
namespace XFSOAnswers
{
// Based on https://github.com/xamarin/xamarin-forms-samples/blob/main/WorkingWithMaps/WorkingWithMaps/WorkingWithMaps/PinPageCode.cs
public class PinPageCode : ContentPage
{
public PinPageCode()
{
Title = "Pins demo";
Position position = new Position(36.9628066, -122.0194722);
MapSpan mapSpan = new MapSpan(position, 0.01, 0.01);
Map map = new Map(mapSpan);
Pin pin = new Pin
{
Label = "Santa Cruz",
Address = "The city with a boardwalk",
Type = PinType.Place,
Position = position
};
map.Pins.Add(pin);
// ... more pins.
Content = new StackLayout
{
Margin = new Thickness(10),
Children =
{
map
}
};
}
}
}

Xamarin/C#/GPS: Weird distance measurement

I'm having a difficult time figuring out how to properly get distances between two Locations in Xamarin. At least I'm consistently getting the wrong results, according to almighty Google.
This code explains the problem.
public void OnLocationChanged(Location location)
{
// Longitude/Latitude of Tower Bridge.
location.Longitude = 51.5053446;
location.Latitude = -0.0765396;
foreach (var store in this.stores)
{
if (store.DistanceView != null)
{
Location store_loc = new Location(location.Provider);
//store_loc.Longitude = double.Parse(store.GPSN);
//store_loc.Latitude = double.Parse(store.GPSW);
// Longitude/Latitude of Big Ben.
store_loc.Longitude = 51.5005747;
store_loc.Latitude = -0.1247025;
var distance = location.DistanceTo(store_loc);
// Google Maps ("measure distance") says 3.44km.
// Xamarin (variable "distance") says 5351.983 meters.
store.DistanceView.Text = distance.ToString();
}
}
}
The actual locations I'm working with are different (for reasons of privacy), but the measurement error is similar, in that I'm getting a result that's not quite twice as high as measured by Google Maps, but somewhere in the vicinity. At any rate, the above measurements should match, and they don't.

Removing Slide Layouts from Presentation

I have a following code for removing slide layout parts:
var presentation = output.PresentationPart;
var usedSlideLayouts = presentation.SlideParts.Select(it => it.SlideLayoutPart).ToList();
foreach (var sm in presentation.SlideMasterParts)
{
var slideLayoutIds = sm.SlideMaster.SlideLayoutIdList.Elements<SlideLayoutId>().ToList();
var slideLayoutParts = sm.SlideLayoutParts.ToList();
var i = 0;
foreach (var slp in slideLayoutParts)
{
var isUsed = usedSlideLayouts.Any(usl => usl == slp);
if (!isUsed)
{
var slideLayoutId = slideLayoutIds[i];
slideLayoutId.Remove();
sm.DeletePart(slp);
}
i++;
}
}
When i save the presentation after running it my presentation is corrupted (needs to be restored before opening in power point).
From what I saw (by unzipping the pptx) this could be because of the fact that there is only one slide layout but it's internal URI is like 'slideLayout2.xml'. Is there a way to refresh that internal URI?
For anyone wondering I attached a powerpoint in this github issue:
https://github.com/OfficeDev/Open-XML-SDK/issues/218
Your slideLayoutId is the wrong one. The SlideLayoutParts are not necessarily returned in the same order as the SlideLayoutIds.
You can solve this by changing the line
var slideLayoutId = slideLayoutIds[i];
to
var slideLayoutId = slideLayoutIds.Where(sid => sid.RelationshipId == sm.GetIdOfPart(slp)).FirstOrDefault();
This picks the correct slideLayoutId based on the RelationshipId.

Programatically created Sitecore items have no version, no content; "Current item does not have a version in English" C#

I'm creating an import tool that programatically creates items in Sitecore. The item gets created, but when I view it, it says 'The current item does not have a version in "English : English."' I put in using (new LanguageSwitcher("en-gb")) but that didn't fix it. The way my code works is that it looks for the folder that the item is supposed to be put in (all folders are based on year, e.g. 2016, 2017); if the folder doesn't exist, I create that folder before creating the item. This is my code:
protected void PublishRelease(PressReleaseItem release)
{
using (new LanguageSwitcher("en-gb"))
{
var year = release.ReleaseDate.Year;
// create year folder if it doesn't exist
var folderQuery = String.Format(PressReleaseYearFolderFastQuery, year);
Item folder = _db.SelectItems(folderQuery).ToList().FirstOrDefault();
if (folder == null)
{
var templateId = _templateFactory.GetTemplateId<IPressReleaseYearFolderItem>();
TemplateID pressReleaseFolderTemplateId = new TemplateID(templateId.ToID());
folder = _pressReleaseFolder.Add(year.ToString(), pressReleaseFolderTemplateId);
}
if (folder == null) return;
// add item to folder
var itemTemplateId = _templateFactory.GetTemplateId<IPRNewswirePressReleaseItem>();
TemplateID pressReleaseTemplateId = new TemplateID(itemTemplateId.ToID());
item = folder.Add(SanitizeHeadline(release.Headline), pressReleaseTemplateId);
if (item == null) return;
item.Fields.ReadAll();
item.Editing.BeginEdit();
try
{
item.Fields["External ID"].Value = release.ExternalId;
item.Fields["Active"].Value = release.Active.ToString();
item.Fields["Image Url"].Value = release.ImageUrl;
item.Fields["PDF Url"].Value = release.PdfUrl;
item.Fields["Description"].Value = release.SubHeadline;
item.Fields["Headline"].Value = release.Headline;
item.Fields["Date"].Value = release.ReleaseDate.ToString("d");
item.Fields["Longtext"].Value = release.Body;
item.Fields["Category"].Value = SetReleaseCategories(release.Category);
}
catch (Exception ex)
{
item.Editing.EndEdit();
}
item.Editing.EndEdit();
}
}
When I view the new item in Sitecore, it says it has no version in English; when I click to add a new version, all of the fields are blank.
I would have expected the code you have above to default to creating a new version in the en-GB language. Like Richard mentions, validate if your 'english' is set to 'en' or 'en-gb'. If your default English has a different code, you might have to update your language switcher.
Alternatively, have you tried doing something like below to force a version?
var result = item.Versions.AddVersion();
This would at least allow you to test if the version creation is working at all, though you shouldn't need it for a new item.

Categories

Resources