Xamarin/C#/GPS: Weird distance measurement - c#

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.

Related

Windows Media Player Duration Not Working?

I am currently making a WPF App as a school project. I chose a Media player topic.
I have a slight problem with returning track length of a song and assigning it as a maximum for a slider (I used a slider for song seeking).
/*List<string> pathPesme = new List<string>();
path = od.FileNames;
for (int i = 0; i < path.Length; i++)
{
lista.Add(path[i]);
m.CreateControl();
media = this.m.newMedia(path[i]);*/
//When i want to instance a new class Pesma(song) and put the track length(duzina), it works and returns the value in a mm:ss format.
string ime = System.IO.Path.GetFileName(path[i]);
string duzina = media.durationString;
Pesma pesma = new Pesma(ime, path[i], duzina);
listaPesmi.Add(pesma);
}
However, when i try to do a similar thing and put it as Slider.Maximum, it doesn't work.
foreach (var pesma in dispesma)/**/
{
string aa=iseciString(lbxListaPesama.Items[lbxListaPesama.SelectedIndex].ToString(), ':');
//Method for cutting string to get song name
if (pesma.ImePesme == aa)
{
m.URL = pesma.Path;
premotajSlider.Maximum = Math.Ceiling(m.currentMedia.duration); //Slider: Here i try to put the max value. In the debugger, it returns a 0
m.Ctlcontrols.play();
jacinaZvukaSlider.Value = m.settings.volume;
}
}
It's my first time asking on StackOverflow, so I hope I didn't do something bad. If I did, sorry, still in the learning phase.

How do I get an album's cover art in Xamarin.Android?

Before anyone points it out, yes, I know there have been similar questions asked before. I've already tried solutions from them and they do not work, which is why I'm asking this question.
For reference, I'm using Android API 30.
So I'm performing a query on all audio files on the device using MediaStore. As of now I'm able to properly access artist/album/song IDs, names, etc. and use them elsewhere in my app. The one thing I'm unable to get though is the album art.
This is what my code looks like:
string[] columns = {
MediaStore.Audio.Media.InterfaceConsts.IsMusic,
MediaStore.Audio.Media.InterfaceConsts.Data,
MediaStore.Audio.Media.InterfaceConsts.Title,
MediaStore.Audio.Media.InterfaceConsts.CdTrackNumber,
MediaStore.Audio.Media.InterfaceConsts.Duration,
MediaStore.Audio.Media.InterfaceConsts.Id,
MediaStore.Audio.Media.InterfaceConsts.Album,
MediaStore.Audio.Media.InterfaceConsts.AlbumId,
MediaStore.Audio.Media.InterfaceConsts.Artist,
MediaStore.Audio.Media.InterfaceConsts.ArtistId,
};
ICursor? cursor = context.ContentResolver?.Query(MediaStore.Audio.Media.ExternalContentUri!, columns, null, null, null);
if (cursor is null)
{
return;
}
while (cursor.MoveToNext())
{
if (cursor.GetString(0) is not "1")
{
continue;
}
string trackPath = cursor.GetString(1)!;
string trackTitle = cursor.GetString(2)!;
int trackIndex = cursor.GetInt(3);
uint trackDuration = (uint)(cursor.GetFloat(4) / 1000);
long trackId = cursor.GetLong(5);
string albumTitle = cursor.GetString(6)!;
long albumId = cursor.GetLong(7);
string artistName = cursor.GetString(8)!;
long artistId = cursor.GetLong(9);
I initially tried the obvious method, which was adding MediaStore.Audio.Media.InterfaceConsts.AlbumArt to columns and getting the data from that column. But just adding that causes the application to freeze. Logging shows that adding it causes the method to freeze midway and not do anything. So this solution is out.
I then tried MediaMetadataRetriever, like this:
MediaMetadataRetriever retriever = new();
retriever.SetDataSource(trackPath);
byte[] artwork = retriever.GetEmbeddedPicture()!;
Bitmap albumArt = BitmapFactory.DecodeByteArray(artwork, 0, artwork.Length)!;
However, this also fails. I get an error message in the logs saying that MediaMetadataRetriever was unable to set the data source to that source.
So I figured maybe my data source was wrong. After doing some digging around I tried using a different path:
Uri contentUri = Uri.Parse("content://media/external/audio/albumart")!;
Uri albumArtUri = ContentUris.WithAppendedId(contentUri, albumId);
MediaMetadataRetriever retriever = new();
retriever.SetDataSource(albumArtUri.Path);
...
This also does not work. Neither does using MediaStore.Audio.Albums.ExternalContentUri.
I even tried opening a ParcelFileDescriptor from those URIs. Also doesn't work.
Does anyone know of a way that would definitely work? Most of the answers on StackOverflow seem to be quite dated, so they possibly don't apply to API 30 anymore. But I don't know what I'm doing wrong since the documentation isn't very detailed.

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
}
};
}
}
}

UWP MapControl highlight countries

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);
}
}

Bing Maps GeoCodeService and Addresses

I have some problems with my bing maps.
The first one happens when I click on My Location - from almost all locations I were it worked fine, but there are some locations that returns null, why? (It happened me in a new building that hasn't address yet and also happened in a building with no internet connections).
The method:
private async void MyLocation_Click(object sender, RoutedEventArgs e)
{
Bing.Maps.Location location = await GeoLocation.GetCurrentLocationAsync();
MapLayer.SetPosition(_flagPin, location);
map.SetView(location, 15);
}
The first line calls to my static function:
public static async Task<Bing.Maps.Location> GetCurrentLocationAsync()
{
Geolocator geo = new Geolocator();
geo.DesiredAccuracy = PositionAccuracy.Default;
Geoposition currentPosition = null;
currentPosition = await geo.GetGeopositionAsync();
return new Bing.Maps.Location()
{
Latitude = currentPosition.Coordinate.Latitude,
Longitude = currentPosition.Coordinate.Longitude
};
}
What is the problem? How to fix it?
And the second question is about addresses.
When I get an Address object, there are many formats I can select such as FormattedAddress, CountryRegion, PostalTown, I selected The FormattedAddress and there is a problem with it.
My code:
GeocodeResponse GP = await GeoLocation.ReverseGeocodeAsync(location.Latitude, location.Longitude);
EventContext.Address = GP.Results[0].Address.FormattedAddress;
The problem is when I want to send an Address and get the Location.
Sometimes this code returns null, why?
GeocodeResponse GP = await GeoLocation.GeocodeAsync(EventContext.Address);
I thought that maybe the problem is that sometimes the Address (Formatted) is not good, sometimes it gives weird addresses, such as, "Street, st. Canada", which is not found and therefore, it returns null. But what can I do to send a correctly Address? Does FomattedAddress is good?
Here are the two GeoCodeAsync and ReverseGeocodeAsync functions:
public static async Task<GeocodeResponse> GeocodeAsync(string address)
{
GeocodeService.GeocodeRequest geocodeRequest = new GeocodeService.GeocodeRequest();
// Set credentials using a Bing Maps key
geocodeRequest.Credentials = new GeocodeService.Credentials();
geocodeRequest.Credentials.ApplicationId = Application.Current.Resources["BingCredentials"] as string;
// Set the address
geocodeRequest.Query = address;
// Make the geocode request
GeocodeService.GeocodeServiceClient geocodeService = new GeocodeServiceClient(GeocodeServiceClient.EndpointConfiguration.BasicHttpBinding_IGeocodeService);
GeocodeResponse geocodeResponse = await geocodeService.GeocodeAsync(geocodeRequest);
return geocodeResponse;
}
public static async Task<GeocodeResponse> ReverseGeocodeAsync(double latitude, double longitude)
{
ReverseGeocodeRequest reverseGeocodeRequest = new ReverseGeocodeRequest();
// Set credentials using a Bing Maps key
reverseGeocodeRequest.Credentials = new GeocodeService.Credentials();
reverseGeocodeRequest.Credentials.ApplicationId = Application.Current.Resources["BingCredentials"] as string;
// Set the coordinates
reverseGeocodeRequest.Location = new GeocodeService.GeocodeLocation() { Latitude = latitude, Longitude = longitude };
// Make the reverse geocode request
GeocodeServiceClient geocodeService = new GeocodeServiceClient(GeocodeServiceClient.EndpointConfiguration.BasicHttpBinding_IGeocodeService);
GeocodeResponse geocodeResponse = await geocodeService.ReverseGeocodeAsync(reverseGeocodeRequest);
return geocodeResponse;
}
To clarify your first issue, are you getting null from the GPS or is it the address information in the result that is null the issue? If the GPS is returning null then it's possible that your GPS isn't able to get the current position where you are. This has nothing to do with Bing Maps and more so just an issue which your mobile device getting a clear view to the GPS satellites. If the issue is that the address information in the result is null then this is to be expected with new buildings that might not yet be known in the Bing Maps data set. It usually takes several months for new buildings to be found and added to the map data set. If you have no internet connection then the mobile device won't be able to connect to Bing Maps to get the address information. Note that Bing Maps is over 9 Petabytes in size so there is no local copy of the data on your mobile device.
If you already have the coordinates and the address information you shouldn't be geocoding it again. This is a waste of time and will cause issues. The geocoder will sometimes return "street" or "ramp" if the coordinate in which you pass to the reverse geocoder is on an unnamed street. Note geocoders are not designed to clean/validate addresses. They are designed to take an address and return a coordinate. Reverse geocoders are designed to take a coordinate and find the nearest address. Mixing the results from one with the other can result in odd results as the coordinates for each could be significantly different. It rare cases it's possible to loop between both services and get different results each time as the results will slightly be different and end up "walking" along a street.

Categories

Resources