How do you zoom to bounding box using Xamarin Maps? - visual-studio

I have a Xamarin Android map (VS2019) and a number of lat/long points. What I want to do is have the map zoom in (or out), given a set of coordinates that set a bounding box.
I have this code (below) which doesn't seem to do anything. I've read all the documentation which suggests it should work but doesn't. I have checked and the bounding box coordinates are set correctly but the map doesn't do anything.
According to the docs calling LayOut should make it update but it doesn't do anything, no errors, just nothing.
How do I achieve this, any help would be much appreciated.
MyMap.Layout(GetBounds(points));
private Rectangle GetBounds(List<Point> points)
{
var minx = (from x in points select x.X).Min();
var maxx = (from x in points select x.X).Max();
var miny = (from x in points select x.Y).Min();
var maxy = (from x in points select x.Y).Max();
Rectangle rectangle = new Rectangle(minx, miny, maxx - minx, maxy - miny);
return rectangle;
}

Here's what I did
I created a rectangle with the bounds where I need them
Rectangle rectangle = GetBounds(points);
Then I got the rectangle location
Position p1 = new Position(rectangle.X, rectangle.Y);
Position p2 = new Position((rectangle.X + rectangle.Width), (rectangle.Y + rectangle.Height));
... and then got the center point of the rectangle
double x = rectangle.X + (rectangle.Width / 2);
double y = rectangle.Y + (rectangle.Height / 2);
Then called .MoveToRegion using that data, and it works
MyMap.MoveToRegion(
MapSpan.FromCenterAndRadius(
new Xamarin.Forms.Maps.Position(x, y), Distance.BetweenPositions(p1, p2))
);

Related

D3 Donut chart projected to sphere/globe

I want to use d3 for the next task:
display rotating globe with donut chart in center of every country. It should be possible to interact with globe (select country, zoom, rotate).
Seems d3 provide an easy way to implement every part of it but I can not get donuts part working as I need.
There is an easy way draw donut chart with the help of d3.arc:
var arc = d3.arc();
var data = [3, 23, 17, 35, 4];
var radius = 15/scale;
var _arc = arc.innerRadius(radius - 7/scale)
.outerRadius(radius).context(donutsContext);
var pieData = pie(data);
for (var i = 0; i < pieData.length; i++) {
donutsContext.beginPath();
donutsContext.fillStyle = color(i);
_arc(pieData[i]);
}
by with code as it is donuts are displayed on a plane on top of the globe, like:
globe with donut
​
while I want them to be 'wrapped' around the globe
There is d3.geoCircle method that can be projected to globe correctly. I got 'ring' projected correctly to the globe with the help of two circles:
var circle = d3.geoCircle()
.center(centroid)
.radius(2);
var outerCircle = circle();
var circle = d3.geoCircle()
.center(centroid)
.radius(1);
var innerCircle = circle();
var interCircleCoordinates = [];
for (var i = innerCircle.coordinates[0].length - 1; i >= 0; i--) {
interCircleCoordinates.push(innerCircle.coordinates[0][i]);
}
outerCircle.coordinates.push(interCircleCoordinates);
​globe with rings
but I really need to get a donut.
The other way I tried is getting image from donuts and wrapping this image around globe with the help of pixels manipulation:
var image = new Image;
image.onload = onload;
image.src = img;
function onload() {
window.dx = image.width;
window.dy = image.height;
context.drawImage(image, 0, 0, dx, dy);
sourceData = context.getImageData(0, 0, dx, dy).data;
target = context.createImageData(width, height);
targetData = target.data;
for (var y = 0, i = -1; y < height; ++y) {
for (var x = 0; x < width; ++x) {
var p = projection.invert([x, y]), λ = p[0], φ = p[1];
if (λ > 180 || λ < -180 || φ > 90 || φ < -90) { i += 4; continue; }
var q = ((90 - φ) / 180 * dy | 0) * dx + ((180 + λ) / 360 * dx | 0) << 2;
var r = sourceData[q];
var g = sourceData[++q];
var b = sourceData[++q];
targetData[++i] = r;
targetData[++i] = g;
targetData[++i] = b;
targetData[++i] = 125;//
}
}
context.clearRect(0,0, width, height);
context.putImageData(target, 0, 0);
};
by this way I get extremely slow rotating and interaction with a globe for a globe size I need (1000px)
So my questions are:
Is there is some way to project donuts that are generated with the help of d3.arc to a sphere (globe, orthographic projection)?
Is there is some way to get a donut from geoCircle?
Maybe there is some other way to achieve my goal I do not see
There is one way that comes to mind to display donuts on a globe. The key challenge is that d3 doesn't project three dimensional objects very well - with one exception, geographic features. Consequently, an "easy" solution is to convert your pie charts into geographic features and project them with the rest of your features.
To do this you need to:
Use a pie/donut generator as you normally would
Go along the paths generated to get points approximating the pie shape.
Convert the points to long/lat points
Assemble those points into geojson
Project them onto the map.
The first point is easy enough, just make a pie chart with an inner radius.
Now you have to select each path and find points along its perimeter using path.getPointAtLength(), this will be dependent on path length, so path.getTotalLength() will be handy (and corners are important, so you might want to incorporate a little bit of complexity for these corner cases to ensure you get them)).
Once you have the points, you need the use of a second projection, azimuthal equidistant would be best. If the pie chart is centered on [0,0] in svg coordinate space, rotate the azimuthal (don't center), so that the centroid coordinate is located at [0,0] in svg space (you can use translates on the pies to position them, but it will just add extra steps). Take each point and run it through projection.invert() using the second projection. You will need to update the projection for each donut chart as each one will have a different geographic centroid.
Once you have lat long points, it's easy - you've already done it with the geo circle function - convert to geojson and project with the orthographic projection.
This approach gave me something like:
Notes: Depending on your data, it might be easiest to preprocess your data into geojson and store that as opposed to calculating the geojson each page load.
You are using canvas, while you don't need to actually use an svg, you need to still be able to access svg functions like getPointAtLength, you do not need to have an svg or display svg elements by using a custom element replicating a path :
document.createElementNS(d3.namespaces.svg, 'path');
Oh, and make sure the second projection's translate is set - the default is [480,250] for all (most?) d3 projections, that will throw things off if unaccounted for.

Fit to screen from different Orthographic camera positions

Made a simple jsFiddle example to illustrate a problem.
I'm trying to fit object's bounding box to screen from different camera positions. In example in dat.GUI panel you can change camera position and then click button fit to screen.
When changing y and z (positive) camera positions to find camera's top and bottom properties code below is used
var top = boundingBox.max.y * Math.cos(angleToZAxis) + boundingBox.max.z * Math.sin(angleToZAxis); // line 68
var bottom boundingBox.min.y * Math.cos(angleToZAxis) + boundingBox.min.z * Math.sin(angleToZAxis);
I would like to know how I can include camera's x position and negative positions in this calculation, what is the math behind this. Should I use rotation matrix and how to use it?
Or maybe it can be achieved in some simple way with threejs methods, can't figure out, tried the code below but something is wrong:
var matrix = new THREE.Matrix4();
matrix.lookAt ( this.camera.position, new THREE.Vector3(0, 0, 0), new THREE.Vector3(0, 1, 0) );
var bbMax = boundingBox.max.clone().applyMatrix4(matrix);
var bbMin = boundingBox.min.clone().applyMatrix4(matrix)
;
to fit an orthographic camera you have to simply change its zoom and position
you can calculate zoom from the bounding box of your object
(I used the boxes from geometry, but you will have to take in account matrices of the objects in group; I used them because .setFromObject was not returning consistent value)
Canvas3D.prototype.fitToScreen = function() {
this.group.children[0].geometry.computeBoundingBox();
var boundingBox = this.group.children[0].geometry.boundingBox.clone();
this.group.children[1].geometry.computeBoundingBox();
boundingBox.union(this.group.children[1].geometry.boundingBox);
var rotation = new THREE.Matrix4().extractRotation(this.camera.matrix);
boundingBox.applyMatrix4(rotation);
this.camera.zoom = Math.min(this.winWidth / (boundingBox.max.x - boundingBox.min.x),
this.winHeight / (boundingBox.max.y - boundingBox.min.y)) * 0.95;
this.camera.position.copy(boundingBox.center());
this.camera.updateProjectionMatrix();
this.camera.updateMatrix();
};
using this will not work in your fiddle because you are using OrbitControls and they rotate camera on update based on their own state - so either update that state or create your own controls
also either move camera back after
this.camera.position.copy(boundingBox.center());
or set near plane to -1000 to avoid having cut object
this.camera = new THREE.OrthographicCamera(this.winWidth / -2,
this.winWidth / 2 , this.winHeight / 2, this.winHeight / -2, -10000, 10000);
EDIT
now i see that you dont want to just fit the object but the whole box...
to do so an easy way is to project the points of the box and get the distances of extremes in pixels, then you can set ortho camera directly
boundingBox = new THREE.Box3().setFromObject(this.group);
//take all 8 vertices of the box and project them
var p1 = new THREE.Vector3(boundingBox.min.x,boundingBox.min.y,boundingBox.min.z).project(this.camera);
var p2 = new THREE.Vector3(boundingBox.min.x,boundingBox.min.y,boundingBox.max.z).project(this.camera);
var p3 = new THREE.Vector3(boundingBox.min.x,boundingBox.max.y,boundingBox.min.z).project(this.camera);
var p4 = new THREE.Vector3(boundingBox.min.x,boundingBox.max.y,boundingBox.max.z).project(this.camera);
var p5 = new THREE.Vector3(boundingBox.max.x,boundingBox.min.y,boundingBox.min.z).project(this.camera);
var p6 = new THREE.Vector3(boundingBox.max.x,boundingBox.min.y,boundingBox.max.z).project(this.camera);
var p7 = new THREE.Vector3(boundingBox.max.x,boundingBox.max.y,boundingBox.min.z).project(this.camera);
var p8 = new THREE.Vector3(boundingBox.max.x,boundingBox.max.y,boundingBox.max.z).project(this.camera);
//fill a box to get the extremes of the 8 points
var box = new THREE.Box3();
box.expandByPoint(p1);
box.expandByPoint(p2);
box.expandByPoint(p3);
box.expandByPoint(p4);
box.expandByPoint(p5);
box.expandByPoint(p6);
box.expandByPoint(p7);
box.expandByPoint(p8);
//take absolute value because the points already have the correct sign
var top = box.max.y * Math.abs(this.camera.top);
var bottom = box.min.y * Math.abs(this.camera.bottom);
var right = box.max.x * Math.abs(this.camera.right);
var left = box.min.x * Math.abs(this.camera.left);
this.updateCamera(left, right, top, bottom);
this code also stretches the view to fit exactly into the window so you will have to check for the aspect ratio and change one size accordingly, but that should be trivial

Rotation is different after resizing movieclip

I am trying to implement transform methods to rotate and resize a movieclip at runtime and I have a problem when recalculating the rotation to follow the mouse.
I have a squared movieclip and a rotator object (a circle) at the right bottom corner of the square where I am listening for mouse events and on mouse move I do this:
_rotation = -(Math.atan2((event.stageX - this.x ), (event.stageY - this.y ))) * 180/Math.PI;
this.rotation = rotation + 45;//this is because my rotator object is on right,bottom corner
this works perfect as long as I don´t modify the width or height of the object but if I do modify then there is a small "jump" on the rotation of the object that I am not able of avoid.
I know this is due event.stageX and even.stageY are different as the rotator object, the one with the mouse listener, has moved after the resize event but no clue how to avoid the "jump".
Please excuse my bad English
You need to rotate the object around its center.
/**
* Rotates the object based on its center
* Parameters: #obj => the object to rotate
* # rotation => angle to rotate
* */
public function RotateAroundCenter(obj:Object, rotation:Number):void
{
var bound:Rectangle = new Rectangle();
// get the bounded rectangle of objects
bound = obj.getRect(this);
// calculate mid poits
var midx1:Number = bound.x + bound.width/2;
var midy1:Number = bound.y + bound.height/2;
// assign the rotation
obj.rotation = rotation;
// assign the previous mid point as (x,y)
obj.x = midx1;
obj.y = midy1;
// get the new bounded rectangle of objects
bound = obj.getRect(this);
// calculate new mid points
var midx2:Number = bound.x + bound.width/2;
var midy2:Number = bound.y + bound.height/2;
// calculate differnece between the current mid and (x,y) and subtract
//it to position the object in the previous bound.
var diff:Number = midx2 - obj.x;
obj.x -= diff;
diff = midy2 - obj.y;
obj.y -= diff;
}
Usage:
_rotation = -(Math.atan2((event.stageX - this.x ), (event.stageY - this.y ))) * 180/Math.PI;
RotateAroundCenter(this, rotation + 45);
Check this link

Is there any other way to calculate X value in coords of other coord system?

Sorry, for weird topic name, but I don't know how else to name it.
The problem is.. I have a zedgraph control. There is some lines drawn inside.
I have the coords of left border of chart area and right border of chart area.
I draw vertical lines as PictureBoxes over zedgraph control, so they moves in different coords. This vertical lines can be moved to left and right.
That way I trying to get X value in coords of XAxis:
public double Get_X_InContextOfChart(int left_coord_of_border) //Left of vertical line
{
//zed_graph.GraphPane.XAxis.Scale.Min is minimal X value shown on XAxis
//zed_graph.GraphPane.Chart.Rect.Left is Left of YAxis
//Same for else if, aside of using Right and maximum X at right
if (zed_graph.GraphPane.XAxis.Scale.Min != 0) //to avoid division by zero
return (left_coord_of_border * zed_graph.GraphPane.XAxis.Scale.Min) / zed_graph.GraphPane.Chart.Rect.Left;
else if (zed_graph.GraphPane.XAxis.Scale.Max != 0)
return (left_coord_of_border * zed_graph.GraphPane.XAxis.Scale.Max) / zed_graph.GraphPane.Chart.Rect.Right;
return double.NaN;
}
This code calculate X fine as long as else if used, but in my example it calculate something wrong.
I hope someone understand this.
Updated with new code:
public double Get_X_InContextOfChart(int left_coord_of_border)
{
double scale_left = zed_graph.GraphPane.XAxis.Scale.Min;
double scale_right = zed_graph.GraphPane.XAxis.Scale.Max;
double graph_width = zed_graph.GraphPane.Chart.Rect.Width;
double x = left_coord_of_border;
return scale_left + (scale_right - scale_left) / graph_width * x;
}
Result:
scale_left = -0.1
scale_right = 0.9
graph_width = 540 // pixels
x = 190 // pixels
scale_x = scale_left + (scale_right - scale_left) / graph_width * x // 0.25

How to change only width of path(rect, circle) while mouse drag using paper.js

I have stucked at resizing only width of an item in the canvas using paper.js
I have done in following ways to resize , but it results in resizing both left and right sides from the center of rectangle/circle.
function onMouseDrag(event){
(selectedItem.bounds.contains(event.point)) ?
selectedItem.scale(0.9871668311944719,1) : selectedItem.scale(1.013, 1);
}
above code resizes in both x-directions.
Help me to resize width only in one direction.
thanks,
suribabu.
You can center the scale operation at any point by using the form:
scale(hor, ver, point)
So in your case, if you want to scale from the left-center of your selected item, you could use:
function onMouseDrag(event){
(selectedItem.bounds.contains(event.point)) ?
selectedItem.scale(0.9871668311944719, 1, selectedItem.bounds.left) : selectedItem.scale(1.013, 1, selectedItem.bounds.left);
}
I am not sure what you mean with scale only the width. If you want to have thicker path than changing the strokeWidth instead might do what you want.
If you are wondering why the scaled path expands in both directions on the x-axis than you might check the location of the paths local center. If some nodes of the path have negative coordinates relative to this local center, scaling them with a positive value decreases their coordinates even more.
Perhaps you should normalize all the vertices, means move them directly so that the smallest x and y value of all vertices is 0.
Greetings and good luck
Try this:
function onMouseDown(e) {
var cx = e.point.x;
var cy = e.point.y;
var rectangle = new Rectangle(e.point, new Size(1,1));
path = new Path.Ellipse(rectangle);
path.strokeColor = 'red';
}
function onMouseDrag(e) {
path.remove();
var x = Math.min(e.point.x, cx),
y = Math.min(e.point.y, cy),
w = Math.abs(e.point.x -cx),
h = Math.abs(e.point.y -cy);
var rectangle = new Rectangle(x,y,w,h);
path = new Path.Ellipse(rectangle);
path.strokeColor = 'red'
}

Resources