EDIT: I have a problem with SKMaps. I am trying to have a menu with buttons and display map with a calculated route underneath a button when the it is clicked. When I click it I download a gpx file and call the calculation of route with it to be performed. The first time I run the calculation it works (both calculates it and focuses on it). However every next time I call the function the route is indeed calculated but the focus is off by far.
My code is iOS but the same issue is present on android as well.
Myrouting delegate is set to self.
I remove the mapView before I add it as subview again.
The route is displayed just not focused on.
Relevant parts of my class are:
IOS:
- (void)viewDidLoad
{
[super viewDidLoad];
[self requestFiles];
fileData = [[NSMutableData alloc] init];
self.mapView = [[SKMapView alloc]initWithFrame:CGRectMake(0.0f, 0.0f, CGRectGetWidth(self.view.frame), CGRectGetHeight(self.view.frame))];
self.mapView.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;
[SKRoutingService sharedInstance].mapView = self.mapView;
[SKRoutingService sharedInstance].routingDelegate = self;
}
-(void) generateContent{
self.container.contentSize = CGSizeMake(320, numberOfRoutes*65);
for (int i = 0; i<numberOfRoutes; i++) {
UIButton *button = [UIButton buttonWithType:UIButtonTypeCustom];
button.frame = CGRectMake(0,i*50, 320, 55);
[button addTarget:self action:#selector(displayRoute:) forControlEvents:UIControlEventTouchUpInside];
[button setTag:i+1];
[self.container addSubview:button];
}
}
-(void)displayRouteFile:(NSString *) fname{
if (downloadComplete) {
for (UIView *item in self.container.subviews) {
[item removeFromSuperview];
}
[self generateContent];
downloadComplete = NO;
self.mapView.frame = CGRectMake(0,(activeButtonNumber+1)*50+10, 320, 200);
self.container.contentSize = CGSizeMake(320, self.container.contentSize.height+210);
for (UIView *item in self.container.subviews) {
if ([item isKindOfClass:[UIButton class]]) {
UIButton *buttonToMove = (UIButton *) item;
if (buttonToMove.tag>activeButtonNumber+1) {
[buttonToMove setCenter:CGPointMake(item.center.x, item.center.y+220)];
}
}
}
[[SKRoutingService sharedInstance]clearCurrentRoutes];
[[SKRoutingService sharedInstance] clearAllRoutesFromCache];
[[SKRoutingService sharedInstance] clearRouteAlternatives];
[[SKRoutingService sharedInstance] startRouteFromGPXFile:fname];
[self.container addSubview:self.mapView];
}
}
-(IBAction)displayRoute:(UIButton *)sender{
UIButton *button = (UIButton *)sender;
[button setBackgroundImage:activeButtonBackground forState:UIControlStateNormal];
int route = button.tag-1;
activeButtonNumber = route;
receiveFile = YES;
//download route
...
fileData = [[NSMutableData alloc] init];
}
- (void)routingService:(SKRoutingService *)routingService didFinishRouteCalculationWithInfo:(SKRouteInformation*)routeInformation{
NSLog(#"Route is calculated with id: %u", routeInformation.routeID);
[routingService zoomToRouteWithInsets:UIEdgeInsetsZero];
// zoom to current route
^^^THIS PART DOES NOT WORK^^^
}
- (void)routingServiceDidFailRouteCalculation:(SKRoutingService *)routingService{
NSLog(#"Route calculation failed.");
}
-(void)viewWillDisappear:(BOOL)animated
{
[super viewWillDisappear:animated];
[[SKRoutingService sharedInstance]clearCurrentRoutes];
activeButtonNumber = 0;
}
ANDROID:
I have an Activity called TestThis which I open multiple times (every time I supply it with a different filepath leading to a GPX file). The whole process works fine the first time I open the Activity - whatever filepath I supply it with it displays the route and then zooms on it. If I go back and select another filepath to create the activity with the new route is displayed but the map zooms somewhere else.
That is the order things happen:
onCreate I get the path to the file from the Bundle.
onSurfaceCreated I clear the currentRoute and start the calculation
of the new one.
onRouteCalculationCompleted I set the new route to be the current route.
onAllRoutesCompleted I try to zoom.
public class TestThis extends Activity implements SKRouteListener, SKMapSurfaceListener {
private static SKMapSurfaceView mapView;
String path;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.prazen);
Bundle bundle = getIntent().getExtras();
path = bundle.getString("filepath");
RelativeLayout theArea = (RelativeLayout) findViewById(R.id.placeHere);
SKMapViewHolder myMapHolder = new SKMapViewHolder(TestThis.this);
mapView = myMapHolder.getMapSurfaceView();
mapView.setMapSurfaceListener(this);
mapView.getMapSettings().setCurrentPositionShown(false);
theArea.addView(myMapHolder);
SKRouteManager.getInstance().setRouteListener(TestThis.this);
}
#Override
public void onRouteCalculationCompleted(final int statusMessage, final int routeDistance, final int routeEta, final boolean thisRouteIsComplete, final int id) {
//SKRouteManager.getInstance().zoomToRoute((float)1.2, (float)1.2, 110, 8, 8, 8);
if(SKRouteManager.getInstance().setCurrentRouteByUniqueId(id)){
System.out.println("Current route selected!");
}
}
#Override
public void onAllRoutesCompleted() {
System.out.println("On all routes compl");
SKRouteManager.getInstance().zoomToRoute((float)1.2, (float)1.2, 110, 8, 8, 8);
//SKRouteManager.getInstance().zoomMapToCurrentRoute();
}
#Override
public void onSurfaceCreated() {
// TODO Auto-generated method stub
SKRouteManager.getInstance().clearCurrentRoute();
SKRouteManager.getInstance().setRouteFromGPXFile(path, SKRouteSettings.SKROUTE_CAR_SHORTEST, false, false, false);
}
}
IOS
The bug is on client side.
The problem is that when the route is calculated the previous frame of the map view is used and that's why the route is not centred. Moving
[self.container addSubview:self.mapView]; before the
[[SKRoutingService sharedInstance] clearCurrentRoutes];
[[SKRoutingService sharedInstance] clearAllRoutesFromCache];
[[SKRoutingService sharedInstance] clearRouteAlternatives];
[[SKRoutingService sharedInstance] startRouteFromGPXFile:fname];
should fix the problem.
Android:
The problem with incorrect zooming might occur when the call to zoomToRoute() is made soon after creating the activity. As a workaround for the issue the same approach as in the v2.2 open source demo may be followed: avoiding activity recreation and selecting the GPX tracks from the same activity as the one that displays the map - see the "Tracks" option in the demo's menu.
The original bug/cause will be addressed in a future update.
Related
I want to use the world map in Unity and have been looking at the API of various map services. I need to show a screenshot (a static map with markers) on the screen, and move to the full map view to navigate it after clicking on it.
MapBox managed to display the map with the selected coordinates and add test markers, but that's all I can do with a query like this: https://api.mapbox.com/styles/v1/mapbox/streets-v11/static/url-https%3A%2F%2Fwww.mapbox.com%2Fimg%2Frocket.png(-76.9,38.9)/-76.9,38.9,15/1000x1000?access_token=pk.eyJ1IjoiZGVuZGVhZCIsImEiOiJja2F1dha4egixnnfhmnvtc2u0y3bua2ntin0.GGOyhgN_fEqtPpPc5n6OLg because this request returns a jpg image.
They also have a plugin for Unity, but it's only used in 3d projects and does not allow me to configure the display in 2d.
In MapBox, the mapping I need is implemented using JavaScript for Web and Java for Android. On Android I can do what I need. I can connect to the API on Android, but will I be able to use it in Unity later?
It's the same with Google maps.
Actually, the question is, did someone work with map services in Unity? And how can this be implemented correctly?
I don't know if this is still relevant, but I used Mapbox in Unity (the Mapbox plugin) to create a AR Soundscape by "registering" GameObjects to coordinates and moving them in real-time when the map is moved.
Your problem sounds an awful lot like the one I solved with that.
Basically you provide the Lat/Lon values for your objects and convert them to Unity world space coordinates using the AbstractMap.GeoToWorldPosition() function.
I used a raycast to actually pull that off in-engine, which is quite convenient.
//Edit:
Unity is quite capable of handling 2D projects. You just have to configure it properly and build your project around it.
The following is the class that I use to handle all positioning-related calculations. Maybe it's of some help to you.
namespace TehMightyPotato.Positioning
{
[Serializable]
public class GeoPosition
{
[Tooltip(
"Update frequency of position polling. Update every n-th frame. 1 is every frame, 60 is every 60th frame.")]
[Range(1, 60)]
public int positionUpdateFrequency = 1;
[Tooltip("Should the object have a specified altitude?")]
public bool useYOffset = false;
[Tooltip("If useMeterConversion is activated the yOffsets unit is meters, otherwise its unity units.")]
public bool useMeterConversion = false;
[Tooltip("The actual y position of the object in m or unity units depending on useMeterConversion.")]
public float yOffset = 0;
[Tooltip("X is LAT, Y is LON")]public Vector2d geoVector;
[HideInInspector] public float worldRelativeScale;
// Apply the result of this function to your gameobjects transform.position on every frame to keep them on this position.
public Vector3 GetUnityWorldSpaceCoordinates(AbstractMap map)
{
UpdateWorldRelativeScale(map);
var worldSpaceCoordinates = map.GeoToWorldPosition(geoVector, false);
if (useYOffset)
{
worldSpaceCoordinates.y = yOffset;
}
return worldSpaceCoordinates;
}
public void UpdateWorldRelativeScale(AbstractMap map)
{
worldRelativeScale = map.WorldRelativeScale;
}
public void SetGeoVectorFromRaycast(Vector3 position, AbstractMap map, LayerMask layerMask)
{
var ray = new Ray(position, Vector3.down);
if (Physics.Raycast(ray, out var hitInfo, Mathf.Infinity, layerMask))
{
geoVector = map.WorldToGeoPosition(hitInfo.point);
}
else
{
throw new NullReferenceException("Raycast did not hit the map. Did you turn on map preview?");
}
}
public void SetYOffsetFromRaycast(AbstractMap map, Vector3 position, LayerMask layerMask)
{
UpdateWorldRelativeScale(map);
// using raycast because of possible y-non-zero maps/ terrain etc.
var ray = new Ray(position, Vector3.down);
if (Physics.Raycast(ray, out var hitInfo, Mathf.Infinity, layerMask))
{
var worldSpaceDistance = Vector3.Distance(position, hitInfo.point);
if (useMeterConversion)
{
yOffset = worldSpaceDistance * worldRelativeScale;
}
else
{
yOffset = worldSpaceDistance;
}
}
else
{
throw new NullReferenceException("Could not find map below. Is map preview turned on?");
}
}
}
}
I have been following the custom pin tutorial using Xamarin.Forms which I have linked below. I have finished implementing it and I have also moved onto adding pins to the map as well through tapping.
https://learn.microsoft.com/en-us/xamarin/xamarin-forms/app-fundamentals/custom-renderer/map/customized-pin
(side note: I am working almost exclusively with Xamarin.Forms and Android)
Currently, I am able to tap on the map and a new custom pin will be added at that location, which is great. What is not great is that I was unable to figure out how to get a tap and long hold gesture to work instead of just the normal tap. To try to combat this, and because I will eventually have to add these anyways, I am planning on adding a button that the user can press to initiate that they want to add a button to the map, and I will later add an undo button, and many others, etc.
The problem is, I have no idea how to get the result of what my toggle button state is from the custom render that I am using for the map. Where can I get the result of my toggle button and use it as a boolean towards whether to add a button or not?
Here is the toggle button code, which I took line by line from their example on this page:
https://learn.microsoft.com/en-us/xamarin/xamarin-forms/user-interface/button
Here is the code where I add a custom pin just by a single tap:
private void GoogleMap_MapClick(object sender, GoogleMap.MapClickEventArgs e)
{
((CustomMap)Element).OnTap(new Position(e.Point.Latitude, e.Point.Longitude));
var addingPin = new CustomPin
{
Type = PinType.Place,
Position = new Position(e.Point.Latitude, e.Point.Longitude),
Address = " - need to possibly implement - ",
Id = "shelter",
Label = "Pin from tap",
Url = "http://www.redcross.org"
};
Map.Pins.Add(addingPin);
this.customPins.Add(addingPin);
}
I thought about making a custom button render but by my knowledge I can only apply one Android render to a content page at a time, and when I tried to add a custom button render to the map render then this method was not happy, as it was taking in some sort of Android Map View and not a button view:
protected override void OnElementChanged(Xamarin.Forms.Platform.Android.ElementChangedEventArgs<Map> e)
{
base.OnElementChanged(e);
if (e.OldElement != null)
{
NativeMap.InfoWindowClick -= OnInfoWindowClick;
}
if (e.NewElement != null)
{
var formsMap = (CustomMap)e.NewElement;
customPins = formsMap.CustomPins;
Control.GetMapAsync(this);
}
}
Any help is greatly appreciated. Below I have included a pic of what my application looks like so far, along with the custom page that I am using as well.
using Hermes.Models;
using System;
using System.Collections.Generic;
using Xamarin.Forms;
using Xamarin.Forms.Maps;
namespace Hermes
{
public class MapPage : ContentPage
{
public MapPage()
{
CustomMap customMap = new CustomMap()
{
HeightRequest = 100,
WidthRequest = 960,
VerticalOptions = LayoutOptions.FillAndExpand,
MapType = MapType.Street,
};
var examplePinSupplies = new CustomPin
{
Type = PinType.Place,
Position = new Position(42.02525, -93.65087),
Address = " - need to possibly implement - ",
Id = "supplies",
Label = "supplies",
Url = "https://www.redcross.org/store"
};
var examplePinMedical = new CustomPin
{
Type = PinType.Place,
Position = new Position(42.02290, -93.63912),
Address = " - need to possibly implement - ",
Id = "medical",
Label = "medical",
Url = "http://www.redcross.org"
};
var examplePinShelter = new CustomPin
{
Type = PinType.Place,
Position = new Position(42.02045, -93.60968),
Address = " - need to possibly implement - ",
Id = "shelter",
Label = "shelter",
Url = "http://www.redcross.org"
};
customMap.CustomPins = new List<CustomPin> { examplePinSupplies, examplePinMedical, examplePinShelter };
customMap.Pins.Add(examplePinSupplies);
customMap.Pins.Add(examplePinMedical);
customMap.Pins.Add(examplePinShelter);
customMap.MoveToRegion(MapSpan.FromCenterAndRadius(new Position(42.025250, -93.650870), Distance.FromMiles(1.0)));
var addPin = new ToggleButton { Text = "Add pin" };
var buttons = new StackLayout
{
Orientation = StackOrientation.Horizontal,
Children = {
addPin
}
};
Content = new StackLayout
{
Spacing = 0,
Children = {
customMap,
buttons
}
};
}
}
}
What I am trying to do: I am trying to tap the 'add pin' toggle button, and then the map will allow me to only add one pin on the map by tapping, and then any other consecutive taps that are not on another pin will not add another pin to the map.
I have a custom animation that expands a Cell inside a TableView. I also refresh the tableView size before/after the animation as appropriate. This works on UWP, and I don't have a Mac yet, so I can't test it on IOS.
This doesn't work at all on Android. It does not refresh the tableRoot to update the size of it, nor does it seem to play the expanding animations.
I say the second part, because when I lock the tableview to the full size it doesn't play.
The animation itself is pretty straight forward:
private void AnimateLinearRowAppear(VisualElement visual, double targetHeight = 40, TableRoot _tableRoot, bool visible)
{
visual.IsVisible = visible;
TableRootRefresher(_tableRoot);
Animation _animation;
_animation = new Animation(
(d) => visual.HeightRequest = d, 0, targetHeight);
_animation.Commit(visual, "The Animation", 16, 250, Easing.Linear, (target, b) =>
{
_animation = null;
});
}
Any one have any ideas as to why it doesn't work on Android?
Unless I'm mistaken this is for cross platform development.
Do I have to write an interface and call each of my animations in a platform specific way?
Oh, this is the latest version of Xamarin 2.3.4.270.
Thanks for any help in advance!
I'm calling the animation from an event such as a specific picker selection or switch.
Switch.toggled += (sender, e) =>
{
AnimationChooser(celltochange,switch.toggle, tableRootCellIsPartOf);
}
AnimationChooser decides if we're playing the appearing or disappearing animation and is working correctly.
private void AnimationChooser(Cell celltoChange, bool stateOfSwitch, TableRoot _tableRoot)
{
var visual = (celltoChange as ViewCell).View
if(visual.IsVisible && stateOfSwitch)
{ AnimateLinearRowDissappear(visual, 40 ,_tableRoot, stateOfSwitch);}
else AnimateLinearRowAppear(visual,40,_tableRoot,stateOfSwitch);
}
TableRootRefresher(TableRoot _tableRoot)
{
Var tempRoot = _tableRoot;
_tableRoot = null;
_tableRoot = tempRoot:
}
The XAML:
<TableView>
<TableRoot x:Name="root">
<TableSection>
<SwitchCell x:Name="switch"/>
<EntryCell x:Name="toAppear" HieghtRequest="0" IsVisible="False"/>
</TableSection>
</TableRoot>
</TableView>
Here is a sample project.
I have used the below codesnippet animation working fine for me.
//animate changed the opacity from 1 to 0.
//XFtabbody is my UI elements
var animation = new Animation(v => XFTabBody.Opacity = v, 1, 0);
Action<double, bool> finished = animate;
animation.Commit(XFTabBody, "aa", 0, 100, Easing.Linear, finished);
void animate(double d, bool b)
{
XFTabBody.Children.Clear();
}
Right now i am implementing a custom Renderer for my Xamarin.Forms Label with which i can make use of the marquee scrolling features of the TextView.
What i want to do now is change the scrolling mode of the text from just going to the left endlessly to "bouncing" from side to side. (Kind of like this question i found regarding this subject: How to make a label scroll a word back and forth?) I have not found any resources online that talk about this. I would be very happy if you could give me a general idea of where to look or what to do.
Thank you for your time
Here is my marquee Custom-Renderer:
protected override void OnElementChanged(ElementChangedEventArgs<Label> e)
{
MarqueeLabel marqLabel = (MarqueeLabel)this.Element;
base.OnElementChanged(e);
if (marqLabel.shouldScroll)
{
Control.SetSingleLine(true);
Control.SetHorizontallyScrolling(true);
Control.Selected = true;
Control.Ellipsize = Android.Text.TextUtils.TruncateAt.Marquee;
Control.SetMarqueeRepeatLimit(-1);
}
}
If you can do the animations on the Xamarin.Forms level then you can save yourself quite a bit of work rather than implementing on each platform-specific level.
Try the following example that demonstrates some bouncing of a Label to and from the edges of the screen, as it may give you some ideas? Note it doesn't do it continuously in the example below.
StackLayout objStackLayout = new StackLayout()
{
Orientation = StackOrientation.Vertical,
};
Label objLabel = new Label();
objLabel.Text="Hello";
objLabel.HorizontalOptions = LayoutOptions.Start;
objStackLayout.Children.Add(objLabel);
Button objButton1 = new Button()
{
Text = "Animate"
};
objButton1.Clicked += (async (o2, e2) =>
{
double dblWidth = objStackLayout.Width - objLabel.Width;
//
await objLabel.TranslateTo(dblWidth, 0,1000, Easing.BounceIn);
await objLabel.TranslateTo(0,0,1000, Easing.BounceIn);
});
objStackLayout.Children.Add(objButton1);
This is not the good way to do that but I am sure this will solve your problem.
StackLayout objStackLayout = new StackLayout()
{
Orientation = StackOrientation.Vertical,
};
Label lblMarquee = new Label();
lblMarquee.Text = "This is marquee animation!!!";
lblMarquee.HorizontalOptions = LayoutOptions.Start;
objStackLayout.Children.Add(lblMarquee);
Button btnAnimate = new Button()
{
Text = "Stare Marquee Animation"
};
btnAnimate.Clicked += (async (o2, e2) =>
{
double dblWidth = objStackLayout.Width + lblMarquee.Width;
// Infinite loop
while (true)
{
lblMarquee.IsVisible = false;
await lblMarquee.TranslateTo(dblWidth / 2, 0, 500, Easing.SinIn);
lblMarquee.IsVisible = true;
await lblMarquee.TranslateTo(-(dblWidth / 2), 0, 10000, Easing.SinIn);
}
});
objStackLayout.Children.Add(btnAnimate);
Content = objStackLayout;
}
NOTE: You can remove the infinite loop and try the same with TPL that will be good way to handle never ending task instead of using infinite loop.
I have integrated sygic in my android application using a surface view. I want to navigate in that sygic application . I have used this code :
SWayPoint wp = new SWayPoint();
wp.Location = new LONGPOSITION(34, 35);
ApplicationAPI.StartNavigation(err, wp, 0, true, false, MAX);
But it is not working. Any ideas ?
I have once implemented Sygic in an app and this is basically how my code looks like (after hours of debug because the documentation was very poor...):
// surfaceView for displaying the "map"
SurfaceView mSygicSurface = (SurfaceView) findViewById(R.id.sygic_surface); // surface
// api status
int mSygicAPIStatus = -2;
// start the drive
ApplicationAPI.startDrive(new ApiCallback() {
public void onRunDrive() {
mSygicSurface.post(new Runnable() {
public void run() {
runDrive(mSygicSurface, getPackageName());
}
});
}
public void onInitApi() // gets called after runDrive();
{
mSygicAPIStatus = ApplicationAPI.InitApi(getPackageName(), true, new ApplicationHandler() { /* nothing relevant here */ }); // api initialization
if (mSygicAPIStatus != 1) {
// error
return;
}
}
});
Once you want to navigate somewhere:
GeoPoint point = new GeoPoint(/* ... */, /* ... */);
final SWayPoint wayPoint = new SWayPoint("", point.getLongitudeE6(), point.getLatitudeE6());
SError error = new SError();
final int returnCode = ApplicationAPI.StartNavigation(error, point, NavigationParams.NpMessageAvoidTollRoadsUnable, true, true, 0);
Carefully note that Sygic uses E6 coordinates.
This is not an answer on the question, but for whose who searching for weird sygic exmaples in 2017 I put it here
ApiMaps.showCoordinatesOnMap(new Position((int)(-84.41949*100000.0),(int)(33.7455*100000.0)),1000,0);
//LONGITUDE first!!
//and multiply on 100 000
//https://developers.sygic.com/reference/java3d/html/classcom_1_1sygic_1_1sdk_1_1remoteapi_1_1_api_maps.html
p.s. this is for standalone apk