-
-
Save jamesmontemagno/44aa14cc3cac8caf9d48b73b705954c6 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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