Xamarin add Scale/Rotate to ContentView - c#

I have a question.
I created a CustomView where I can drag/drop the view inside, but now I also want to add a scale/rotate function to it. Now here is the OnTouchEvent that created to move the view inside:
public override bool OnTouchEvent(MotionEvent e)
{
float x = e.RawX;
float y = e.RawY;
var dragView = Element as DraggableView.DraggableView;
switch (e.Action)
{
case MotionEventActions.Down:
if (dragView.DragMode == DragMode.Touch)
{
if (!touchedDown)
{
if (firstTime)
{
originalX = GetX();
originalY = GetY();
firstTime = false;
}
dragView.DragStarted();
}
TextMoved = false;
touchedDown = true;
stopwatch.Start();
}
dX = x - this.GetX();
dY = y - this.GetY();
break;
case MotionEventActions.Move:
if (touchedDown)
{
if (dragView.DragDirection == DragDirectionType.All || dragView.DragDirection == DragDirectionType.Horizontal)
{
SetX(x - dX);
}
if (dragView.DragDirection == DragDirectionType.All || dragView.DragDirection == DragDirectionType.Vertical)
{
SetY(y - dY);
}
TextMoved = true;
}
break;
case MotionEventActions.Up:
touchedDown = false;
if(TextMoved == true)
{
dragView.DragEnded();
}
else
{
MessagingCenter.Send<object, DraggableView.DraggableView>(this, "EditSelectedText", dragView);
}
break;
case MotionEventActions.Cancel:
touchedDown = false;
break;
}
return base.OnTouchEvent(e);
}
But now I also need the Scale/Rotate function.
The problem is that I already created it for my skiasharp bitmaps, but this isn't skiasharp, so I can't use that.
How can I implement this function in a OnTouchEvent without skiasharp?

There is a xamarin-forms-samples about ScaleAndRotate , however it is not related to TouchEvent .I found a way by using PanGestureRecognizer and PinchGestureRecognizer to implement it .
Create a ScaleAndRotateContainer ContentView which contains PanGestureRecognizer and PinchGestureRecognizer :
public class ScaleAndRotateContainer : ContentView
{
double currentScale = 1;
double startScale = 1;
double xOffset = 0;
double yOffset = 0;
double rotateNum = 1;
public ScaleAndRotateContainer()
{
var pinchGesture = new PinchGestureRecognizer ();
pinchGesture.PinchUpdated += OnPinchUpdated;
var panGesture = new PanGestureRecognizer();
panGesture.PanUpdated += PanGesture_PanUpdated;
GestureRecognizers.Add (pinchGesture);
GestureRecognizers.Add(panGesture);
}
private void PanGesture_PanUpdated(object sender, PanUpdatedEventArgs e)
{
rotateNum++;
this.RotateTo(rotateNum);
this.AnchorX = 0.5;
this.AnchorY = 0.5;
}
void OnPinchUpdated (object sender, PinchGestureUpdatedEventArgs e)
{
if (e.Status == GestureStatus.Started) {
// Store the current scale factor applied to the wrapped user interface element,
// and zero the components for the center point of the translate transform.
startScale = Content.Scale;
Content.AnchorX = 0;
Content.AnchorY = 0;
}
if (e.Status == GestureStatus.Running) {
// Calculate the scale factor to be applied.
currentScale += (e.Scale - 1) * startScale;
currentScale = Math.Max (1, currentScale);
// The ScaleOrigin is in relative coordinates to the wrapped user interface element,
// so get the X pixel coordinate.
double renderedX = Content.X + xOffset;
double deltaX = renderedX / Width;
double deltaWidth = Width / (Content.Width * startScale);
double originX = (e.ScaleOrigin.X - deltaX) * deltaWidth;
// The ScaleOrigin is in relative coordinates to the wrapped user interface element,
// so get the Y pixel coordinate.
double renderedY = Content.Y + yOffset;
double deltaY = renderedY / Height;
double deltaHeight = Height / (Content.Height * startScale);
double originY = (e.ScaleOrigin.Y - deltaY) * deltaHeight;
// Calculate the transformed element pixel coordinates.
double targetX = xOffset - (originX * Content.Width) * (currentScale - startScale);
double targetY = yOffset - (originY * Content.Height) * (currentScale - startScale);
// Apply translation based on the change in origin.
Content.TranslationX = targetX.Clamp (-Content.Width * (currentScale - 1), 0);
Content.TranslationY = targetY.Clamp (-Content.Height * (currentScale - 1), 0);
// Apply scale factor
Content.Scale = currentScale;
}
if (e.Status == GestureStatus.Completed) {
// Store the translation delta's of the wrapped user interface element.
xOffset = Content.TranslationX;
yOffset = Content.TranslationY;
}
}
}
Then use it in Xaml for ContentPage :
xmlns:local="clr-namespace:PinchGesture;assembly=YourNameSpace"
<local:ScaleAndRotateContainer>
<local:ScaleAndRotateContainer.Content>
<StackLayout HorizontalOptions="Center" VerticalOptions="Center" >
<Label Text="Hello Xamarin.Forms!" />
<Image Source="waterfront.jpg" />
</StackLayout>
</local:ScaleAndRotateContainer.Content>
</local:ScaleAndRotateContainer>
The effect :

Related

Image flickering while zooming while using PinchGestureRecognizer

I use the below code to place an <Image> and <Image.GestureRecognizers> to make the zoom functionality.
The code behind for the event PinchUpdated="PinchGestureRecognizer_PinchUpdated" is also provided.
The zoom function works, but too much flickering while pinching.
XAML :
<Image
Grid.Column="0"
Grid.Row="0"
x:Name="pageIterator"
Grid.ColumnSpan="3">
<Image.GestureRecognizers>
<PinchGestureRecognizer PinchUpdated="PinchGestureRecognizer_PinchUpdated"></PinchGestureRecognizer>
</Image.GestureRecognizers>
</Image>
Code behind :
private void PinchGestureRecognizer_PinchUpdated(object sender, PinchGestureUpdatedEventArgs e)
{
double currentScale = 1;
double startScale = 1;
double xOffset = 0;
double yOffset = 0;
if (e.Status == GestureStatus.Started)
{
// Store the current scale factor applied to the wrapped user interface element,
// and zero the components for the center point of the translate transform.
startScale = Content.Scale;
Content.AnchorX = 0;
Content.AnchorY = 0;
}
if (e.Status == GestureStatus.Running)
{
// Calculate the scale factor to be applied.
currentScale += (e.Scale - 1) * startScale;
currentScale = Math.Max(1, currentScale);
// The ScaleOrigin is in relative coordinates to the wrapped user interface element,
// so get the X pixel coordinate.
double renderedX = Content.X + xOffset;
double deltaX = renderedX / Width;
double deltaWidth = Width / (Content.Width * startScale);
double originX = (e.ScaleOrigin.X - deltaX) * deltaWidth;
// The ScaleOrigin is in relative coordinates to the wrapped user interface element,
// so get the Y pixel coordinate.
double renderedY = Content.Y + yOffset;
double deltaY = renderedY / Height;
double deltaHeight = Height / (Content.Height * startScale);
double originY = (e.ScaleOrigin.Y - deltaY) * deltaHeight;
// Calculate the transformed element pixel coordinates.
double targetX = xOffset - (originX * Content.Width) * (currentScale - startScale);
double targetY = yOffset - (originY * Content.Height) * (currentScale - startScale);
// Apply translation based on the change in origin.
Content.TranslationX = targetX.Clamp(-Content.Width * (currentScale - 1), 0);
Content.TranslationY = targetY.Clamp(-Content.Height * (currentScale - 1), 0);
// Apply scale factor.
Content.Scale = currentScale;
}
if (e.Status == GestureStatus.Completed)
{
// Store the translation delta's of the wrapped user interface element.
xOffset = Content.TranslationX;
yOffset = Content.TranslationY;
}
}
You should move the below lines out of PinchGestureRecognizer_PinchUpdated method,otherwise you're essentially initializing these values every time you fire.
double currentScale = 1;
double startScale = 1;
double xOffset = 0;
double yOffset = 0;
private void PinchGestureRecognizer_PinchUpdated(object sender, PinchGestureUpdatedEventArgs e)
{
if (e.Status == GestureStatus.Started)
{
// Store the current scale factor applied to the wrapped user interface element,
// and zero the components for the center point of the translate transform.
startScale = Content.Scale;
Content.AnchorX = 0;
Content.AnchorY = 0;
}
if (e.Status == GestureStatus.Running)
{
// Calculate the scale factor to be applied.
currentScale += (e.Scale - 1) * startScale;
currentScale = Math.Max(1, currentScale);
// The ScaleOrigin is in relative coordinates to the wrapped user interface element,
// so get the X pixel coordinate.
double renderedX = Content.X + xOffset;
double deltaX = renderedX / Width;
double deltaWidth = Width / (Content.Width * startScale);
double originX = (e.ScaleOrigin.X - deltaX) * deltaWidth;
// The ScaleOrigin is in relative coordinates to the wrapped user interface element,
// so get the Y pixel coordinate.
double renderedY = Content.Y + yOffset;
double deltaY = renderedY / Height;
double deltaHeight = Height / (Content.Height * startScale);
double originY = (e.ScaleOrigin.Y - deltaY) * deltaHeight;
// Calculate the transformed element pixel coordinates.
double targetX = xOffset - (originX * Content.Width) * (currentScale - startScale);
double targetY = yOffset - (originY * Content.Height) * (currentScale - startScale);
// Apply translation based on the change in origin.
Content.TranslationX = targetX.Clamp(-Content.Width * (currentScale - 1), 0);
Content.TranslationY = targetY.Clamp(-Content.Height * (currentScale - 1), 0);
// Apply scale factor.
Content.Scale = currentScale;
}
if (e.Status == GestureStatus.Completed)
{
// Store the translation delta's of the wrapped user interface element.
xOffset = Content.TranslationX;
yOffset = Content.TranslationY;
}
}

Zoom out lags on Xamarin.Form implementation

I need to zoom in/out an image in a Xamarin.Form App.
My problem is that when I made the pinch gesture to zooming out the image it starts to lag and flashing all over the display. Instead the zoom in works perfectly.
I had already follow the official guide (https://learn.microsoft.com/it-it/xamarin/xamarin-forms/app-fundamentals/gestures/pinch) and any sort of forum/comunity but I wasn't able to reach a working answer.
Could anyone help me?
I copy/paste here the section of the code. I localize the bug where I try to set the currentScale variable.
NOTE: I have other methods into the class but they don't manage images or related properties so I don't copy/paste them.
public partial class ResizeFoto : ContentPage
{
double currentScale = 1;
double startScale = 1;
double minScale = 0;
double maxScale = 2.5;
private void PinchGestureRecognizer_PinchUpdated(object sender,PinchGestureUpdatedEventArgs e){
switch (e.Status)
{
case GestureStatus.Started:
startScale = imgUserFoto.Scale;
break;
case GestureStatus.Running:
// LAS test 4
//Input gesture:
//Definition: "The distance between the user's digits, divided by the
//last reported distance between the user's digits in the pinch gesture"
// --> ZOOM IN = e.Scale > 1
// --> ZOOM OUT = e.Scale < 1
//ZOOM IN --> works good
if (e.Scale > 1)
{
currentScale += (e.Scale - 1) * startScale;
currentScale = Math.Min(currentScale, maxScale);
imgUserFoto.Scale = currentScale;
}
//ZOOM OUT --> not working, bug
else if (e.Scale < 1)
{
//HERE MAYBE THE BUG
currentScale = minScale + (e.Scale - 1) * startScale;
//also tried: currentScale = (e.Scale - 1) * startScale;
currentScale = Math.Max(minScale, currentScale);
imgUserFoto.Scale = currentScale;
}
}
break;
case GestureStatus.Completed:
break;
}
}
}
Recently I had to do something similar and I almost searched the whole earth and found nothing then I came up with this:
using System;
using Xamarin.Forms;
using FFImageLoading.Forms;
public class ZoomImage : CachedImage
{
private const double MIN_SCALE = 1;
private const double MAX_SCALE = 4;
private const double OVERSHOOT = 0.15;
private double StartScale, LastScale;
private double StartX, StartY;
public ZoomImage()
{
var pinch = new PinchGestureRecognizer();
pinch.PinchUpdated += OnPinchUpdated;
GestureRecognizers.Add(pinch);
var pan = new PanGestureRecognizer();
pan.PanUpdated += OnPanUpdated;
GestureRecognizers.Add(pan);
var tap = new TapGestureRecognizer { NumberOfTapsRequired = 2 };
tap.Tapped += OnTapped;
GestureRecognizers.Add(tap);
Scale = MIN_SCALE;
TranslationX = TranslationY = 0;
AnchorX = AnchorY = 0;
}
protected override SizeRequest OnMeasure(double widthConstraint, double heightConstraint)
{
Scale = MIN_SCALE;
TranslationX = TranslationY = 0;
AnchorX = AnchorY = 0;
return base.OnMeasure(widthConstraint, heightConstraint);
}
private void OnTapped(object sender, EventArgs e)
{
if (Scale > MIN_SCALE)
{
this.ScaleTo(MIN_SCALE, 250, Easing.CubicInOut);
this.TranslateTo(0, 0, 250, Easing.CubicInOut);
}
else
{
AnchorX = AnchorY = 0.5; //TODO tapped position
this.ScaleTo(MAX_SCALE, 250, Easing.CubicInOut);
}
}
private void OnPanUpdated(object sender, PanUpdatedEventArgs e)
{
switch (e.StatusType)
{
case GestureStatus.Started:
StartX = (1 - AnchorX) * Width;
StartY = (1 - AnchorY) * Height;
break;
case GestureStatus.Running:
AnchorX = Clamp(1 - (StartX + e.TotalX) / Width, 0, 1);
AnchorY = Clamp(1 - (StartY + e.TotalY) / Height, 0, 1);
break;
}
}
private void OnPinchUpdated(object sender, PinchGestureUpdatedEventArgs e)
{
switch (e.Status)
{
case GestureStatus.Started:
LastScale = e.Scale;
StartScale = Scale;
AnchorX = e.ScaleOrigin.X;
AnchorY = e.ScaleOrigin.Y;
break;
case GestureStatus.Running:
if (e.Scale < 0 || Math.Abs(LastScale - e.Scale) > (LastScale * 1.3) - LastScale)
{ return; }
LastScale = e.Scale;
var current = Scale + (e.Scale - 1) * StartScale;
Scale = Clamp(current, MIN_SCALE * (1 - OVERSHOOT), MAX_SCALE * (1 + OVERSHOOT));
break;
case GestureStatus.Completed:
if (Scale > MAX_SCALE)
this.ScaleTo(MAX_SCALE, 250, Easing.SpringOut);
else if (Scale < MIN_SCALE)
this.ScaleTo(MIN_SCALE, 250, Easing.SpringOut);
break;
}
}
private T Clamp<T>(T value, T minimum, T maximum) where T: IComparable
{
if (value.CompareTo(minimum) < 0)
return minimum;
else if (value.CompareTo(maximum) > 0)
return maximum;
else
return value;
}
}
What this does:
Pinch zoom, Pan and Swipe movements together with double tap centre zoom and un-zoom
Note: I have used FFimageLoading's CachedImage because I needed to cache the data in case you do not intend this replace CachedImage with Xamarin.Forms.Image

how to enable scrolling when zoom in and zoom out on a content page in xamarin forms

i am trying to zoom in and zoom out on a content page using xamarin.forms.
I am able zoom in and zoom out but the problem is scrolling is not working.
i want zoom an image. with this code zooming is working perfectly. But while zooming i am not able to see full image. i must scroll to view the rest of the image. for that i need to scroll. but scrolling is not working.
XAML
xmlns:helper="clr-namespace:KPGTC.Deals.Mobile.Helpers"
<helper:PinchToZoomContainer>
<helper:PinchToZoomContainer.Content>
<Image x:Name="img_Popup"/>
</helper:PinchToZoomContainer.Content>
</helper:PinchToZoomContainer>
Code:
public class PinchToZoomContainer : ContentView
{
double MIN_SCALE = 1;
double MAX_SCALE = 4;
double startScale = 1;
double currentScale = 1;
double xOffset = 0;
double yOffset = 0;
bool _isActive = false;
public PinchToZoomContainer()
{
DependencyService.Get<IHelpers>().ShowAlert("Double-tap to zoom");
//var _pinchGesture = new PinchGestureRecognizer();
//_pinchGesture.PinchUpdated += OnPinchUpdated;
//GestureRecognizers.Add(_pinchGesture);
var _tapGesture = new TapGestureRecognizer { NumberOfTapsRequired = 2 };
_tapGesture.Tapped += On_Tapped;
GestureRecognizers.Add(_tapGesture);
var _panGesture = new PanGestureRecognizer();
_panGesture.PanUpdated += OnPanUpdated;
GestureRecognizers.Add(_panGesture);
TranslationX = 0;
TranslationY = 0;
_isActive = false;
}
private void OnPanUpdated(object sender, PanUpdatedEventArgs e)
{
if (_isActive)
{
if (e.TotalX > 0)
{
if (e.TotalX > 2)
{
TranslationX += 15;
}
}
else
{
if (e.TotalX < -2)
{
TranslationX -= 15;
}
}
}
}
private void On_Tapped(object sender, EventArgs e)
{
if (Scale > MIN_SCALE)
{
_isActive = false;
this.ScaleTo(MIN_SCALE, 250, Easing.CubicInOut);
this.TranslateTo(0, 0, 250, Easing.CubicInOut);
}
else
{
_isActive = true;
AnchorX = AnchorY = 0.5;
this.ScaleTo(MAX_SCALE, 250, Easing.CubicInOut);
}
}
void OnPinchUpdated(object sender, PinchGestureUpdatedEventArgs e)
{
if (e.Status == GestureStatus.Started)
{
startScale = Content.Scale;
Content.AnchorX = 0;
Content.AnchorY = 0;
}
if (e.Status == GestureStatus.Running)
{
currentScale += (e.Scale - 1) * startScale;
currentScale = Math.Max(1, currentScale);
double renderedX = Content.X + xOffset;
double deltaX = renderedX / Width;
double deltaWidth = Width / (Content.Width * startScale);
double originX = (e.ScaleOrigin.X - deltaX) * deltaWidth;
double renderedY = Content.Y + yOffset;
double deltaY = renderedY / Height;
double deltaHeight = Height / (Content.Height * startScale);
double originY = (e.ScaleOrigin.Y - deltaY) * deltaHeight;
double targetX = xOffset - (originX * Content.Width) * (currentScale - startScale);
double targetY = yOffset - (originY * Content.Height) * (currentScale - startScale);
Content.TranslationX = targetX.Clamp(-Content.Width * (currentScale - 1), 0);
Content.TranslationY = targetY.Clamp(-Content.Height * (currentScale - 1), 0);
Content.Scale = currentScale;
}
if (e.Status == GestureStatus.Completed)
{
xOffset = Content.TranslationX;
yOffset = Content.TranslationY;
}
}
}
Alright, this was quite a tough one and admittedly I don't understand fully how I made it, but I made it.
Some thoughts:
You mixed the translation of the container and the content, which is quite tricky to handle - if this is possible at all
When panning, you added 15 every time the pan event was raised, but there is a better way: Just store the initial offset of the content and then add the TotalX and TotalY respectively to the TranslationX and the TranslationY of the content (this was the easy part)
Panning while zooming was quite hard to get right and I had to find it out by trial and error
Basically you have to store the origin of the pinch gesture when the gesture starts and calculate the diff between the original origin and the current origin
Then you have to add the diff (multiplied by the with and height respectively of the control) to the target translation
Here is the code for the panning:
private void OnPanUpdated(object sender, PanUpdatedEventArgs e)
{
if (e.StatusType == GestureStatus.Started)
{
this.xOffset = this.Content.TranslationX;
this.yOffset = this.Content.TranslationY;
}
if (e.StatusType != GestureStatus.Completed
&& e.StatusType != GestureStatus.Canceled)
{
this.Content.TranslationX = this.xOffset + e.TotalX;
this.Content.TranslationY = this.yOffset + e.TotalY;
}
if (e.StatusType == GestureStatus.Completed)
{
this.xOffset = this.Content.TranslationX;
this.yOffset = this.Content.TranslationY;
}
}
And here for the pinching
void OnPinchUpdated(object sender, PinchGestureUpdatedEventArgs e)
{
if (e.Status == GestureStatus.Started)
{
this.startScale = this.Content.Scale;
this.Content.AnchorX = 0;
this.Content.AnchorY = 0;
this.startScaleOrigin = e.ScaleOrigin;
}
if (e.Status == GestureStatus.Running)
{
var originDiff = PinchToZoomContainer.CalculateDiff(e.ScaleOrigin, this.startScaleOrigin);
this.currentScale += (e.Scale - 1) * this.startScale;
this.currentScale = Math.Max(1, this.currentScale);
double renderedX = this.Content.X + this.xOffset;
double deltaX = renderedX / this.Width;
double deltaWidth = this.Width / (this.Content.Width * this.startScale);
double originX = (this.startScaleOrigin.X - deltaX) * deltaWidth;
double renderedY = this.Content.Y + this.yOffset;
double deltaY = renderedY / this.Height;
double deltaHeight = this.Height / (this.Content.Height * this.startScale);
double originY = (startScaleOrigin.Y - deltaY) * deltaHeight;
double targetX = this.xOffset - ((originX) * this.Content.Width) * (this.currentScale - this.startScale) - originDiff.X * this.Content.Width;
double targetY = this.yOffset - ((originY) * this.Content.Height) * (this.currentScale - this.startScale) - originDiff.Y * this.Content.Height;
this.Content.TranslationX = targetX.Clamp(-this.Content.Width * (this.currentScale - 1), 0);
this.Content.TranslationY = targetY.Clamp(-this.Content.Height * (this.currentScale - 1), 0);
this.Content.Scale = this.currentScale;
}
if (e.Status == GestureStatus.Completed)
{
this.xOffset = this.Content.TranslationX;
this.yOffset = this.Content.TranslationY;
}
}
(Of course you have to add Point startScaleOrigin to your class).
Finally you need the method to calculate the distance between two points
private static Point CalculateDiff(Point first, Point second)
{
return second.Offset(-first.X, -first.Y);
}
Unfortunately I did not manage to get the tapping right, but I think you should be able to figure it out from here.

Image only zoom in but not zoom out in Xamarin forms

I have content page which contain photo. I wanted to add the zoom in and zoom out options however when i tried to add the pinch gesture it is only zoom in but not zoom out? how can I add the zoom out options?
here is my XAML:
<?xml version="1.0" encoding="utf-8" ?>
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
x:Class="ComplexInstructionApp.ShowProgram"
Title="dsa">
<ContentPage.Content>
</ContentPage.Content>
<Grid>
<Image x:Name="IMI" Aspect="Fill"
RelativeLayout.WidthConstraint=
"{ConstraintExpression Type=RelativeToParent, Property=Width}"
RelativeLayout.HeightConstraint=
"{ConstraintExpression Type=RelativeToParent, Property=Height}"
/>
</Grid>
</ContentPage>
and here is my code:
public partial class ShowProgram : ContentPage
{
private double currentScale = 1;
private double startScale = 1;
private double xOffset = 0;
private double yOffset = 0;
public ShowProgram(string txt)
{
InitializeComponent();
switch (txt)
{
case "קובני":
IMI.Source = "Cuben_Expend.png";
break;
case "קובני מצומצם":
IMI.Source = "Cuben_short.png";
break;
case "אל איי":
IMI.Source = "LA_Expend.png";
break;
case "אל איי מצומצם":
IMI.Source = "LA_short.png";
break;
case "בצ'אטה":
IMI.Source = "bachata.png";
break;
case "זוק":
IMI.Source = "zouk.png";
break;
}
var pinchGesture = new PinchGestureRecognizer();
pinchGesture.PinchUpdated += (s, e) =>
{
if (e.Status == GestureStatus.Started)
{
// Store the current scale factor applied to the wrapped user interface element,
// and zero the components for the center point of the translate transform.
startScale = Content.Scale;
Content.AnchorX = 0;
Content.AnchorY = 0;
}
if (e.Status == GestureStatus.Running)
{
// Calculate the scale factor to be applied.
currentScale += (e.Scale - 1) * startScale;
currentScale = Math.Max(1, currentScale);
// The ScaleOrigin is in relative coordinates to the wrapped user interface element,
// so get the X pixel coordinate.
double renderedX = Content.X + xOffset;
double deltaX = renderedX / Width;
double deltaWidth = Width / (Content.Width * startScale);
double originX = (e.ScaleOrigin.X - deltaX) * deltaWidth;
// The ScaleOrigin is in relative coordinates to the wrapped user interface element,
// so get the Y pixel coordinate.
double renderedY = Content.Y + yOffset;
double deltaY = renderedY / Height;
double deltaHeight = Height / (Content.Height * startScale);
double originY = (e.ScaleOrigin.Y - deltaY) * deltaHeight;
// Calculate the transformed element pixel coordinates.
double targetX = xOffset - (originX * Content.Width) * (currentScale - startScale);
double targetY = yOffset - (originY * Content.Height) * (currentScale - startScale);
// Apply translation based on the change in origin.
Content.TranslationX = Clamp1(targetX ,- Content.Width * (currentScale - 1), 0);
Content.TranslationY = Clamp1(targetY, -Content.Height * (currentScale - 1), 0);
// Apply scale factor.
Content.Scale = currentScale;
}
if (e.Status == GestureStatus.Completed)
{
// Store the translation delta's of the wrapped user interface element.
xOffset = Content.TranslationX;
yOffset = Content.TranslationY;
}
};
IMI.GestureRecognizers.Add(pinchGesture);
}
public double Clamp1(double self, double min, double max)
{
return Math.Min(max, Math.Max(self, min));
}
}

How to draw circle on the MAP using GMAP.NET in C#

I am using GMAP.NET in c#. I am able to display the map on the form, now i am trying to draw a CIRCLE mouse by clicking on a certian point, keeping the left mouse button and dragging the mouse upto specific place. Once the circle is drawn I want to get its radius in miles from the center point which I am sure GMAP is capable of doing it. I am using Opentstreet maps.
I am just unable to achive this functionly, anybody who has played with GMAP control kindly share your experience with some code which will work.
Thanks
The only way that I am aware of that can achieve such a result is to create a list with PointLatLng points and draw them as a polygon. Here is an example:
private void CreateCircle(PointF point, double radius, int segments)
{
List<PointLatLng> gpollist = new List<PointLatLng>();
double seg = Math.PI * 2 / segments;
for (int i = 0; i < segments; i++)
{
double theta = seg * i;
double a = point.X + Math.Cos(theta) * radius;
double b = point.Y + Math.Sin(theta) * radius;
PointLatLng gpoi = new PointLatLng(a,b);
gpollist.Add(gpoi);
}
GMapPolygon gpol = new GMapPolygon(gpollist, "pol");
overlayOne.Polygons.Add(gpol);
}
If you want to use the typical GDI features associated with the drawing class, you can simply inherit the GMapMarker class. This allows you to draw simple shapes, like circles, and create custom properties (for instance, one that will calculate the radius in miles of the shape):
public class GMapPoint : GMap.NET.WindowsForms.GMapMarker
{
private PointLatLng point_;
private float size_;
public PointLatLng Point
{
get
{
return point_;
}
set
{
point_ = value;
}
}
public GMapPoint(PointLatLng p, int size)
: base(p)
{
point_ = p;
size_ = size;
}
public override void OnRender(Graphics g)
{
g.FillRectangle(Brushes.Black, LocalPosition.X, LocalPosition.Y, size_, size_);
//OR
g.DrawEllipse(Pens.Black, LocalPosition.X, LocalPosition.Y, size_, size_);
//OR whatever you need
}
}
To draw points on the map:
GMapOverlay points_ = new GMapOverlay("pointCollection");
points_.Markers.Add(new GMapPoint(new PointLatLng(35.06, -106.36), 10));
gMapControl1.Overlays.Add(points_);
(And because I had some questions about it) Since we are inhereting from the markers class, we can still take advantage of the tooltiptext capability:
GMapPoint pnt = new GMapPoint(new PointLatLng(35.06, -106.36), 10);
pnt.Size = new Size(10,10);
pnt.ToolTipText = "Text Here";
pnt.ToolTipMode = MarkerTooltipMode.Always;
points_.AddMarker(pnt);
private void CreateCircle(Double lat, Double lon, double radius, int ColorIndex)
{
PointLatLng point = new PointLatLng(lat, lon);
int segments = 1080;
List<PointLatLng> gpollist = new List<PointLatLng>();
for (int i = 0; i < segments; i++)
{
gpollist.Add(FindPointAtDistanceFrom(point, i*(Math.PI/180), radius / 1000));
}
GMapPolygon polygon = new GMapPolygon(gpollist, "Circle");
switch (ColorIndex) {
case 1:
polygon.Fill = new SolidBrush(Color.FromArgb(80, Color.Red));
break;
case 2:
polygon.Fill = new SolidBrush(Color.FromArgb(80, Color.Orange));
break;
case 3:
polygon.Fill = new SolidBrush(Color.FromArgb(20, Color.Aqua));
break;
default:
MessageBox.Show("No search zone found!");
break;
}
polygon.Stroke = new Pen(Color.Red, 1);
markers.Polygons.Add(polygon);
gMapCtl.Overlays.Add(markers);
}
public static GMap.NET.PointLatLng FindPointAtDistanceFrom(GMap.NET.PointLatLng startPoint, double initialBearingRadians, double distanceKilometres)
{
const double radiusEarthKilometres = 6371.01;
var distRatio = distanceKilometres / radiusEarthKilometres;
var distRatioSine = Math.Sin(distRatio);
var distRatioCosine = Math.Cos(distRatio);
var startLatRad = DegreesToRadians(startPoint.Lat);
var startLonRad = DegreesToRadians(startPoint.Lng);
var startLatCos = Math.Cos(startLatRad);
var startLatSin = Math.Sin(startLatRad);
var endLatRads = Math.Asin((startLatSin * distRatioCosine) + (startLatCos * distRatioSine * Math.Cos(initialBearingRadians)));
var endLonRads = startLonRad + Math.Atan2(Math.Sin(initialBearingRadians) * distRatioSine * startLatCos,distRatioCosine - startLatSin * Math.Sin(endLatRads));
return new GMap.NET.PointLatLng(RadiansToDegrees(endLatRads), RadiansToDegrees(endLonRads));
}
public static double DegreesToRadians(double degrees)
{
const double degToRadFactor = Math.PI/180;
return degrees * degToRadFactor;
}
public static double RadiansToDegrees(double radians)
{
const double radToDegFactor = 180/Math.PI;
return radians * radToDegFactor;
}
public static double DistanceTwoPoint(double startLat, double startLong, double endLat, double endLong) {
var startPoint = new GeoCoordinate(startLat, startLong);
var endPoint = new GeoCoordinate(endLat, endLong);
return startPoint.GetDistanceTo(endPoint);
}
I hit the same problem and on entry I had Lon, Lat and radius, here is my solution. It works like a charm :)
private void CreateCircle(Double lat, Double lon, double radius)
{
PointLatLng point = new PointLatLng(lat, lon);
int segments = 1000;
List<PointLatLng> gpollist = new List<PointLatLng>();
for (int i = 0; i < segments; i++)
gpollist.Add(FindPointAtDistanceFrom(point, i, radius / 1000));
GMapPolygon gpol = new GMapPolygon(gpollist, "pol");
markers.Polygons.Add(gpol);
}
public static GMap.NET.PointLatLng FindPointAtDistanceFrom(GMap.NET.PointLatLng startPoint, double initialBearingRadians, double distanceKilometres)
{
const double radiusEarthKilometres = 6371.01;
var distRatio = distanceKilometres / radiusEarthKilometres;
var distRatioSine = Math.Sin(distRatio);
var distRatioCosine = Math.Cos(distRatio);
var startLatRad = DegreesToRadians(startPoint.Lat);
var startLonRad = DegreesToRadians(startPoint.Lng);
var startLatCos = Math.Cos(startLatRad);
var startLatSin = Math.Sin(startLatRad);
var endLatRads = Math.Asin((startLatSin * distRatioCosine) + (startLatCos * distRatioSine * Math.Cos(initialBearingRadians)));
var endLonRads = startLonRad + Math.Atan2(
Math.Sin(initialBearingRadians) * distRatioSine * startLatCos,
distRatioCosine - startLatSin * Math.Sin(endLatRads));
return new GMap.NET.PointLatLng(RadiansToDegrees(endLatRads), RadiansToDegrees(endLonRads));
}
public static double DegreesToRadians(double degrees)
{
const double degToRadFactor = Math.PI / 180;
return degrees * degToRadFactor;
}
public static double RadiansToDegrees(double radians)
{
const double radToDegFactor = 180 / Math.PI;
return radians * radToDegFactor;
}
call
CreateCircle(51.640980, -2.673544, 1143.899431);
private void CreateCircle(PointF point, double radius, int segments)
{
List<PointLatLng> gpollist = new List<PointLatLng>();
double seg = Math.PI * 2 / segments;
int y = 0;
for (int i = 0; i < segments; i++)
{
double theta = seg * i;
double a = point.x + Math.cos( theta ) * radius;
double b = point.y + Math.sin( theta ) * radius;
PointLatLng gpoi = new PointLatLng(a,b);
gpollist.Add(gpoi);
}
GMapPolygon gpol = new GMapPolygon(gpollist, "pol");
overlayOne.Polygons.Add(gpol);
}`enter code here`
so the apartment is not going ellipses
Here's how to draw a red circle, with black border on the map in WPF:
public class YourMapControl : GMapControl
{
protected override void OnRender(DrawingContext drawingContext)
{
base.OnRender(drawingContext);
Point center(40.730610, -73.935242);
double radius = 0.1;
drawingContext.DrawEllipse(Brushes.Red, Pens.Black, center, radius, radius);
}
}
As far as the second part of the question itself, you would use the following built-in MapControl functions:
public bool DisableAltForSelection; //if true, selects area just by holding mouse and moving
public bool SelectionUseCircle; //use circle for selection
public event SelectionChange OnSelectionChange; //occurs when mouse selection is changed
public RectLatLng SelectedArea { get; set; } //returns rect with coordinates of the selected area
GMapOverlay markers = new GMapOverlay("markers");
private void CreateCircle(Double lat, Double lon, double radius, int segments)
{
markers.Polygons.Clear();
PointLatLng point = new PointLatLng(lat, lon);
List<PointLatLng> gpollist = new List<PointLatLng>();
for (int i = 0; i < segments; i++)
gpollist.Add(FindPointAtDistanceFrom(point, i, radius / 1000));
List<PointLatLng> gpollistR = new List<PointLatLng>();
List<PointLatLng> gpollistL = new List<PointLatLng>();
foreach (var gp in gpollist)
{
if (gp.Lng > lon)
{
gpollistR.Add(gp);
}
else
{
gpollistL.Add(gp);
}
}
gpollist.Clear();
List<PointLatLng> gpollistRT = new List<PointLatLng>();
List<PointLatLng> gpollistRB = new List<PointLatLng>();
foreach (var gp in gpollistR)
{
if (gp.Lat > lat)
{
gpollistRT.Add(gp);
}
else
{
gpollistRB.Add(gp);
}
}
gpollistRT.Sort(new LngComparer());
gpollistRB.Sort(new Lng2Comparer());
gpollistR.Clear();
List<PointLatLng> gpollistLT = new List<PointLatLng>();
List<PointLatLng> gpollistLB = new List<PointLatLng>();
foreach (var gp in gpollistL)
{
if (gp.Lat > lat)
{
gpollistLT.Add(gp);
}
else
{
gpollistLB.Add(gp);
}
}
//gpollistLT.Sort(new LngComparer());
gpollistLB.Sort(new Lng2Comparer());
gpollistLT.Sort(new LngComparer());
gpollistL.Clear();
gpollist.AddRange(gpollistRT);
gpollist.AddRange(gpollistRB);
gpollist.AddRange(gpollistLB);
gpollist.AddRange(gpollistLT);
GMapPolygon gpol = new GMapPolygon(gpollist, "pol");
gpol.Stroke = new Pen(Color.Red, 1);
markers.Polygons.Add(gpol);
}
public static GMap.NET.PointLatLng FindPointAtDistanceFrom(GMap.NET.PointLatLng startPoint, double initialBearingRadians, double distanceKilometres)
{
const double radiusEarthKilometres = 6371.01;
var distRatio = distanceKilometres / radiusEarthKilometres;
var distRatioSine = Math.Sin(distRatio);
var distRatioCosine = Math.Cos(distRatio);
var startLatRad = DegreesToRadians(startPoint.Lat);
var startLonRad = DegreesToRadians(startPoint.Lng);
var startLatCos = Math.Cos(startLatRad);
var startLatSin = Math.Sin(startLatRad);
var endLatRads = Math.Asin((startLatSin * distRatioCosine) + (startLatCos * distRatioSine * Math.Cos(initialBearingRadians)));
var endLonRads = startLonRad + Math.Atan2(
Math.Sin(initialBearingRadians) * distRatioSine * startLatCos,
distRatioCosine - startLatSin * Math.Sin(endLatRads));
return new GMap.NET.PointLatLng(RadiansToDegrees(endLatRads), RadiansToDegrees(endLonRads));
}
public static double DegreesToRadians(double degrees)
{
const double degToRadFactor = Math.PI / 180;
return degrees * degToRadFactor;
}
public static double RadiansToDegrees(double radians)
{
const double radToDegFactor = 180 / Math.PI;
return radians * radToDegFactor;
}
and this class
class LngComparer : IComparer<PointLatLng>
{
#region IComparer Members
public int Compare(PointLatLng x, PointLatLng y)
{
if (x == null || y == null)
throw new ArgumentException("At least one argument is null");
if (x.Lng == y.Lng)
{
if (x.Lat > y.Lat)
{
return 1;
}
else if (x.Lat < y.Lat)
{
return -1;
}
else
{
return 0;
}
}
if (x.Lng < y.Lng) return -1;
return 1;
}
#endregion
}
class Lng2Comparer : IComparer<PointLatLng>
{
#region IComparer Members
public int Compare(PointLatLng x, PointLatLng y)
{
if (x == null || y == null)
throw new ArgumentException("At least one argument is null");
if (x.Lng == y.Lng)
{
if (x.Lat > y.Lat)
{
return 1;
}
else if (x.Lat > y.Lat)
{
return -1;
}
else
{
return 0;
}
}
if (x.Lng > y.Lng) return -1;
return 1;
}
#endregion
}
My code draws arcs and inherits from GMapMarker too. The arc sweeps from point A to point B with the pivot point at C. Where point A and B are coincidental, a circle will be drawn.
public class CustomArc : GMapMarker, ISerializable {
[NonSerialized]
public Pen pen;
private int radius = 20;
private int pen_width = 2;
private float start = 0.0f;
private float sweep = 0.0f;
private GPoint ptA;
private GPoint ptB;
private GPoint ptC;
private List<PointF> points;
private static Logger logger = LogManager.GetCurrentClassLogger();
public CustomArc(GPoint ptA, GPoint ptB, GPoint ptC, PointLatLng geo) : base(geo) {
this.ptA = ptA;
this.ptB = ptB;
this.ptC = ptC;
initialise();
}
private void initialise() {
this.pen = new Pen(Brushes.White, this.pen_width);
this.radius = (int)UIMaths.distance(ptC, ptA);
this.points = new List<PointF>();
if (ptA == ptB) {
this.sweep = 360.0f;
} else {
// Calculate the radius
this.sweep = (float)UIMaths.sweepAngleDeg(ptA, ptB, ptC);
}
this.start = (float)UIMaths.startAngle(ptC, ptB);
Size = new Size(2 * radius, 2 * radius);
Offset = new Point(-Size.Width / 2, -Size.Height / 2);
Console.Out.WriteLine("Radius {0}, Start {1:0.0}, Sweep {2:0.0}", radius, start, sweep);
}
public override void OnRender(Graphics g) {
try {
Rectangle rect = new Rectangle(LocalPosition.X, LocalPosition.Y, Size.Width, Size.Height);
g.DrawArc(pen, rect, start, sweep);
} catch (ArgumentException ex) {
logger.Error(ex.Message);
}
}
public sealed override void Dispose() {
if (pen != null) {
pen.Dispose();
pen = null;
}
base.Dispose();
}
#region ISerializable Members
void ISerializable.GetObjectData(SerializationInfo info, StreamingContext context) {
base.GetObjectData(info, context);
}
protected CustomArc(SerializationInfo info, StreamingContext context)
: base(info, context) {
}
#endregion
}
My approach was to override paint event then define a rectangle. Once you define a rectangle you can draw circle or arc or pie.
private void MainMap_Paint(object sender, PaintEventArgs e)
{
drawPie(e.Graphics, 50, 22.321, 45.44498);
}
private void drawPie(Graphics g, int angle, double latitude, double longitude)
{
PointLatLng pn = new PointLatLng(latitude , longitude );
double dist = 295; // 200 km
// define rectangle points
PointLatLng p1 = FindPointAtDistanceFrom(pn, 315 * constants.DEGREES_TO_RADIAN, dist);
PointLatLng p2 = FindPointAtDistanceFrom(pn, 45 * constants.DEGREES_TO_RADIAN, dist);
PointLatLng p3 = FindPointAtDistanceFrom(pn, 135 * constants.DEGREES_TO_RADIAN, dist);
PointLatLng p4 = FindPointAtDistanceFrom(pn, 225 * constants.DEGREES_TO_RADIAN, dist);
GPoint dp1 = MainMap.FromLatLngToLocal(p1);
GPoint dp2 = MainMap.FromLatLngToLocal(p2);
GPoint dp3 = MainMap.FromLatLngToLocal(p3);
GPoint dp4 = MainMap.FromLatLngToLocal(p4);
RectangleF rec = new RectangleF(dp1.X, dp1.Y, dp2.X - dp1.X, dp3.Y - dp1.Y);
SolidBrush ptlbrush = new SolidBrush(Color.Cyan);
Pen ptlpen = new Pen(ptlbrush, 1);
float direction1 = (-90 + angle - 45) % 360;
float startAngle = direction1;
float sweepAngle = 90;
var brush = new SolidBrush(Color.FromArgb(50, 80, 0, 150));
g.DrawPie(ptlpen, rec, startAngle, sweepAngle);
if (angleFilledBox.Checked == true)
g.FillPie(brush, Rectangle.Round(rec), startAngle, sweepAngle);
}

Categories

Resources