Skip to content

Instantly share code, notes, and snippets.

@jamesmontemagno
Created December 17, 2022 17:37
Show Gist options
  • Save jamesmontemagno/44aa14cc3cac8caf9d48b73b705954c6 to your computer and use it in GitHub Desktop.
Save jamesmontemagno/44aa14cc3cac8caf9d48b73b705954c6 to your computer and use it in GitHub Desktop.
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Mapsui;
using Mapsui.Extensions;
using Mapsui.Layers;
using Mapsui.Nts;
using Mapsui.Nts.Extensions;
using Mapsui.Projections;
using Mapsui.Providers;
using Mapsui.Styles;
using Mapsui.Tiling;
using Mapsui.UI;
using Mapsui.Widgets.ScaleBar;
using Meissner.Helpers;
using Meissner.ViewModels;
using NetTopologySuite.Geometries;
using NetTopologySuite.IO;
using NetTopologySuite.Operation;
using Newtonsoft.Json;
using Xamarin.Essentials;
using Xamarin.Forms;
namespace Meissner.Views
{
public partial class InteractiveMapsPage : ContentPage
{
double lat = 43.984949;
double lng = -121.526921;
MPoint homePoint;
VectorStyle prevStyle;
ScaleBarWidget widget;
InteractiveMapViewModel vm;
ILayer theLayer;
public InteractiveMapsPage()
{
InitializeComponent();
BindingContext = vm = new InteractiveMapViewModel();
var map = new Mapsui.Map
{
CRS = "EPSG:3857",
};
map.Layers.Add(OpenStreetMap.CreateTileLayer());
widget = new ScaleBarWidget(map) { ScaleBarMode = ScaleBarMode.Single, MarginX = 10, MarginY = 10 };
map.Widgets.Add(widget);
map.Limiter = new ViewportLimiterKeepWithin
{
PanLimits = GetLimits()
};
MRect GetLimits()
{
var (minX, minY) = SphericalMercator.FromLonLat(-121.644022, 43.903806);
var (maxX, maxY) = SphericalMercator.FromLonLat(-121.357699, 44.085267);
return new MRect(minX, minY, maxX, maxY);
}
mapView.Map = map;
//mapView.UniqueCallout = true;
mapView.Info += MapView_Info;
//mapView.MapClicked += MapView_MapClicked;
homePoint = SphericalMercator.FromLonLat(lng, lat).ToMPoint();
mapView.Map.Home = n => n.NavigateTo(homePoint, mapView.Map.Resolutions[15]);
}
private async void MapView_Info(object sender, Mapsui.UI.MapInfoEventArgs e)
{
if (e?.MapInfo?.Feature == null)
return;
var getInfo = false;
foreach (var style in e.MapInfo.Feature.Styles)
{
if(style is LabelStyle)
{
if (prevStyle != null)
{
prevStyle.Line.Width = 4;
}
getInfo = true;
}
else if (style is VectorStyle)
{
var current = style as VectorStyle;
if(prevStyle == current && prevStyle.Line.Width == 7)
{
//close it
TapGestureRecognizer_Tapped(null, null);
return;
}
else if(prevStyle != null)
{
prevStyle.Line.Width = 4;
}
prevStyle = current;
prevStyle.Line.Width = 7;
getInfo = true;
}
}
if(getInfo)
{
vm.SheetTitle = e.MapInfo.Feature["name"] as string;
vm.SheetDescription = e.MapInfo.Feature["desc"] as string;
vm.SheetSubtitle = e.MapInfo.Feature["subtitle"] as string;
theLayer = e.MapInfo?.Layer;
theLayer?.DataHasChanged(); // To trigger a refresh of graphics.
e.Handled = true;
if (!vm.IsOpen)
{
vm.IsOpen = true;
await BottomSheetInfo.FadeTo(1,350).ConfigureAwait(false);
}
}
}
bool loaded = false;
protected override async void OnAppearing()
{
base.OnAppearing();
widget.UnitConverter = Settings.UseMetric ? (IUnitConverter)MetricUnitConverter.Instance : (IUnitConverter)ImperialUnitConverter.Instance;
if (loaded)
return;
loaded = true;
var status = await CheckAndRequestInitialLocationPermission();
using var data = await FileSystem.OpenAppPackageFileAsync("traildata.geojson");
if (data == null)
return;
var geoData = Deserialize(data);
var rgb = ColorConverters.FromHex("#8a8a8a");
var gray = new Mapsui.Styles.Brush(new Mapsui.Styles.Color(rgb.R, rgb.G, rgb.B));
rgb = ColorConverters.FromHex("#e60000");
var red = new Mapsui.Styles.Brush(new Mapsui.Styles.Color(rgb.R, rgb.G, rgb.B));
var features = new List<IFeature>();
var features2 = new List<IFeature>();
foreach (var feature in geoData)
{
if (feature.Geometry is NetTopologySuite.Geometries.Point)
{
var p = feature.Geometry as NetTopologySuite.Geometries.Point;
var color = feature.Attributes["color"] as string;
var parking = feature.Attributes.GetOptionalValue("parking");
var desc = feature.Attributes.GetOptionalValue("desc");
var subtitle = feature.Attributes.GetOptionalValue("subtitle");
var f = CreatePointFeature(feature.Attributes["name"] as string, color, parking as string, p.X, p.Y);
f["desc"] = desc as string;
f["subtitle"] = subtitle as string;
features.Add(f);
}
else if (feature.Geometry is NetTopologySuite.Geometries.MultiLineString)
{
var g = feature.Geometry as NetTopologySuite.Geometries.MultiLineString;
for (var i = 0; i < g.Count(); i++)
{
if (i == 0)
rgb = ColorConverters.FromHex(feature.Attributes["color"] as string);
else
rgb = ColorConverters.FromHex(feature.Attributes["color2"] as string);
var c = new Mapsui.Styles.Color(rgb.R, rgb.G, rgb.B);
var cords = g.Geometries[i];
var lineString = new LineString(cords.Coordinates.Select(v => SphericalMercator.FromLonLat(v.X, v.Y).ToCoordinate()).ToArray());
var f = new GeometryFeature { Geometry = lineString };
//var calloutStyle = CreateCalloutStyle(feature.Attributes["name"] as string, desc as string);
var desc = feature.Attributes.GetOptionalValue("desc");
var subtitle = feature.Attributes.GetOptionalValue("subtitle");
f["name"] = feature.Attributes["name"] as string;
f["desc"] = desc as string;
f["subtitle"] = subtitle as string;
var v = new VectorStyle
{
Fill = null,
Outline = null,
Line = { Color = c, Width = 4 }
};
f.Styles.Add(v);
features2.Add(f);
}
}
}
mapView.Map.Layers.Add(new MemoryLayer { IsMapInfoLayer = true, Name = "LineStringLayer", Features = features2, Style = null });
mapView.Map.Layers.Add(new MemoryLayer { IsMapInfoLayer = true, Name = "Points", Features = features, Style = null });
await SetupLocationOnMap(status);
IFeature CreatePointFeature(string name, string color, string parking, double lng, double lat)
{
if (color == "#e60000")
{
var feature = new PointFeature(SphericalMercator.FromLonLat(lng, lat).ToMPoint());
feature["name"] = name;
if (string.IsNullOrWhiteSpace(parking))
{
feature.Styles.Add(new SymbolStyle { SymbolScale = 0.4, SymbolOffset = new Offset(0, 0), SymbolType = SymbolType.Ellipse, Fill = red });
}
else
{
feature.Styles.Add(new LabelStyle { Text = "P", Font = { FontFamily = null, Size = 12, Italic = false, Bold = true }, BackColor = red, ForeColor = Mapsui.Styles.Color.White });
}
return feature;
}
else
{
var rgb = ColorConverters.FromHex(color);
var feature = new PointFeature(SphericalMercator.FromLonLat(lng, lat).ToMPoint());
feature.Styles.Add(new LabelStyle { Text = name, Font = { FontFamily = null, Size = 12, Italic = false, Bold = true }, BackColor = gray, ForeColor = Mapsui.Styles.Color.White });
return feature;
}
}
NetTopologySuite.Features.FeatureCollection Deserialize(Stream stream)
{
var serializer = GeoJsonSerializer.Create();
var streamReader = new StreamReader(stream, new UTF8Encoding());
using var reader = new JsonTextReader(streamReader);
return serializer.Deserialize<NetTopologySuite.Features.FeatureCollection>(reader);
}
}
private async Task SetupLocationOnMap(PermissionStatus status)
{
if (status == PermissionStatus.Granted)
{
try
{
var location = await Geolocation.GetLastKnownLocationAsync();
if (location == null)
{
location = await Geolocation.GetLocationAsync(new GeolocationRequest
{
DesiredAccuracy = GeolocationAccuracy.Low,
Timeout = TimeSpan.FromSeconds(20)
});
}
mapView.MyLocationEnabled = true;
mapView.MyLocationFollow = false;
mapView.IsMyLocationButtonVisible = true;
mapView.MyLocationLayer.UpdateMyLocation(new Mapsui.UI.Forms.Position(location.Latitude, location.Longitude));
}
catch (Exception)
{
}
}
else
{
mapView.MyLocationEnabled = false;
mapView.MyLocationFollow = false;
mapView.IsMyLocationButtonVisible = false;
}
}
private static async Task<PermissionStatus> CheckAndRequestInitialLocationPermission()
{
var status = await Permissions.CheckStatusAsync<Permissions.LocationWhenInUse>();
if (!Settings.AskedLocation)
{
var prompt = (DeviceInfo.Platform == DevicePlatform.iOS && status == PermissionStatus.Unknown) || (DeviceInfo.Platform == DevicePlatform.Android && status == PermissionStatus.Denied);
if (prompt)
{
Settings.AskedLocation = true;
var r = await App.Current.MainPage.DisplayAlert("Location Permission", "You can optionally grant location to show your location on the interactive map. Do you want to enable this permission now?", "Yes", "No");
if (r)
{
status = await Permissions.RequestAsync<Permissions.LocationWhenInUse>();
if (status == PermissionStatus.Granted)
{
await App.Current.MainPage.DisplayAlert("Location Permission", "Location permission granted. You can now request to see your location on the interactive map in the maps settings.", "OK");
}
}
}
}
return status;
}
async Task<bool> CheckAndRequestLocation()
{
var status = await Permissions.CheckStatusAsync<Permissions.LocationWhenInUse>();
if (status != PermissionStatus.Granted)
{
if (DeviceInfo.Platform == DevicePlatform.iOS && status == PermissionStatus.Denied)
{
var openSettings = await App.Current.MainPage.DisplayAlert("Location Permission", "You have denied location permission. Do you want to open the settings to enable location?", "Yes", "No");
if (openSettings)
{
AppInfo.ShowSettingsUI();
}
return false;
}
var showRationale = Permissions.ShouldShowRationale<Permissions.LocationWhenInUse>();
var request = true;
if (showRationale)
{
request = await App.Current.MainPage.DisplayAlert("Location Permission", "You have denied location permission. Do you want to enable location?", "Yes", "No");
}
if (request)
{
status = await Permissions.RequestAsync<Permissions.LocationWhenInUse>();
}
if (status != PermissionStatus.Granted)
return false;
}
return true;
}
private async void UpdateLocationButton_Clicked(object sender, EventArgs e)
{
if (vm.IsBusy)
return;
vm.IsBusy = true;
try
{
if (await CheckAndRequestLocation())
{
var location = await Geolocation.GetLocationAsync(new GeolocationRequest
{
DesiredAccuracy = GeolocationAccuracy.Medium,
Timeout = TimeSpan.FromSeconds(20)
});
mapView.MyLocationEnabled = true;
mapView.MyLocationFollow = false;
mapView.IsMyLocationButtonVisible = true;
mapView.MyLocationLayer.UpdateMyLocation(new Mapsui.UI.Forms.Position(location.Latitude, location.Longitude));
}
}
catch (Exception)
{
}
finally
{
vm.IsBusy = false;
}
}
private void CenterMeissnerButton_Clicked(object sender, EventArgs e)
{
mapView.Navigator.NavigateTo(homePoint, mapView.Map.Resolutions[15]);
}
private async void TapGestureRecognizer_Tapped(object sender, EventArgs e)
{
if (vm.IsOpen)
{
if (prevStyle != null)
{
prevStyle.Line.Width = 4;
theLayer?.DataHasChanged(); // To trigger a refresh of graphics.
}
await BottomSheetInfo.FadeTo(0, 350);
vm.IsOpen = false;
}
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment