Mini overview map in a UWP App using MapControl - c#

I'm trying to build a mini overview/orientation map that is synchronized with a full MapControl view, like this: (Full Screenshot)
I'm getting trouble trying to calculate the width, height and location of the little red rectangle that is inside the mini map according to the MapControl's size, location and zoom.
It should be synchronized with the MapControl's view, and a click on the small map should also change the CenterPoint of the MapControl.
The full map is a MapControl in UWP and a the mini-map is just a Border UIElement over a static image.
I'm using the following formulas. They works but not accurately. The error margin is pretty noticeable, specially for big zooms.
For calculating the location and the size of the red rectangle:
var positions = MapControl.GetVisibleRegion(MapVisibleRegionKind.Near).Positions.ToArray();
var topLeft = positions[0];
var bottomLeft = positions[1];
var topRigt = positions[2];
//Transfering the Longitude system from [-180, 180] to [0, 360]
var centerX = (MapControl.Center.Position.Longitude + 180) * (SmallMapWidth / 360);
//Transfering the Latitude system from [-90, 90] to [0, 180]
var centerY = (-MapControl.Center.Position.Latitude + 90) * (SmallMapHeight / 180);
var topLeftX = topLeft.Longitude + 180;
var topRightX = topRigt.Longitude + 180;
//MapControl wraparound by default. In that case, the topRightX might be smaller than topLeftX, as it will start from the 'beginning'.
var deltaX = Math.Abs(topLeftX - (topLeftX < topRightX ? topRightX : 360 - topRightX));
//The width of the red rectangle
SmallMapViewPortWidth = Math.Abs(deltaX) * (SmallMapWidth / 360);
//The height of the red rectangle
SmallMapViewPortHeight = Math.Abs(topLeft.Latitude - bottomLeft.Latitude) * (SmallMapHeight / 180);
//The center point of the red rectangle.
RedRectangleCenterPoint = (centerX - SmallMapViewPortWidth / 2, centerY - SmallMapViewPortHeight / 2);
The following is for navigating the MapControl to a point that was clicked on the overview map. X and Y are the point that was clicked relatively to the overview map.
var lon = 360 * x / SmallMapWidth - 180;
var lat = 90 - 180 * y / SmallMapHeight;
What is wrong with my calculations? Why there is a pretty noticeable error margin?

The issue was actually the image that I was using for the orientation map. It was resolved once I started using proper Bing Maps images.
It seems there is an official API for rendering images in different sizes based on Bing Maps' projection
https://learn.microsoft.com/en-us/bingmaps/rest-services/imagery/get-a-static-map

Related

Set zoom level to see only 3 pins in Xamarin Forms

I'm using Xamarin Forms and I'm using the Google Maps nuget for iOS and Android. I have a ListView with different places that I click to navigate to the MapPage to see the location of the place there.
Since the pins on the map are on different places I cannot set one zoom level for every location. I always want the 3 closest pins to be shown upon entering the page.
I have tried these solutions but I'm looking for a Forms solution for this problem.
LatLng marker1LatLng = new LatLng(marker1lat, marker1lng);
Latlng marker2LatLng = new LatLng(marker2lat, marker2lng);
LatLngBounds.Builder b = new LatLngBounds.Builder()
.Include(marker1LatLng)
.Include(marker2LatLng);
Also tried this but no luck:
var markers = [];//some array
var bounds = new google.maps.LatLngBounds();
for (var i = 0; i < markers.length; i++) {
bounds.extend(markers[i].getPosition());
}
map.fitBounds(bounds);
How to dynamically set the zoom level depending on the lat & lon of those 3 pins that I want to be visible.
I have a total of 10 pins for each place on the map but only wanna show closest 3. Once I zoom out i wanna be able to see all 10 tho.
Thanks for all the help in advance!
First, you need to find the three closest pins to the selected location, which I assume you will want to be the center of the map. Once you find the three closest pins, calculate how far the furthest of the 3 pins is and the use the
map.MoveToRegion (MapSpan.FromCenterAndRadius (
new Position (centerLatitude,centerLongitude), Distance.FromKilometers (distanceToThirdPin)));
where centerLatitude and centerLongitude are the coordinates of the selected location that will be at the center, and distanceToThirdPin is the calculated distance (in Km) to the farthest of the 3 pins.
Formula to calculate distance between a pair of latitudes and longitudes:
double R = 6371.0; // Earth's radius
var dLat = (Math.PI / 180) * (pinLatitude - centerLatitude);
var dLon = (Math.PI / 180) * (pinLongitude - centerLongitude);
var lat1 = (Math.PI / 180) * centerLatitude;
var lat2 = (Math.PI / 180) * pinLatitude;
var a = Math.Sin(dLat/2) * Math.Sin(dLat/2) + Math.Sin(dLon/2) * Math.Sin(dLon/2) * Math.Cos(lat1) * Math.Cos(lat2);
var c = 2 * Math.Atan2(Math.Sqrt(a), Math.Sqrt(1-a));
var d = R * c; // distance in Km.
So use the above formula to find the distances to all of your pins, then find the three closest. Once found, use the distance to the third closest pin as the distanceToThirdPin value when you call the map.MoveToRegion method.

Radial tree graph layout: fix beizer curves

i want to render nice radial tree layout and a bit stumbled with curved edges. The problem is that with different angles between source and target points the edges are drawn differently. Provided pics are from the single graph so you can see how they're differ for different edge directions. I think the point is in beizer curve control points generation and i just can't understand how to fix them.
I want them to be drawn the same way no matter what's the direction of the edge.
How can i achieve this as in Pic1?
How can i achieve this as in Pic2?
Like here: https://bl.ocks.org/mbostock/4063550
Thank you!
Code:
//draw using DrawingContext of the DrawingVisual
//gen 2 control points
double dx = target.X - source.X, dy = target.Y - source.Y;
var pts = new[]
{
new Point(source.X + 2*dx/3, source.Y),
new Point(target.X - dx/8, target.Y - dy/8)
};
//get geometry
var geometry = new StreamGeometry { FillRule = FillRule.EvenOdd };
using (var ctx = geometry.Open())
{
ctx.BeginFigure(START_POINT, false /* is filled */, false /* is closed */);
ctx.BezierTo(pts[0], pts[1], END_POINT, true, false);
}
geometry.Freeze();
//draw it
dc.DrawGeometry(DrawingBrush, DrawingPen, geometry);
UPDATE 1:
I've got the angle between previous vertex and source in radians using the following formula: Math.Atan2(prev.Y - source.Y, source.X - prev.X);
But still i get the edges like in Pic.4.
UPDATE 2
The prev vertex pos for branchAngle calculation is inaccurate so i decided to take an average angle between all edges in a branch as the branchAngle. This approach fails when edges from one brach are around the 180 deg mark and branch can have edge angles like 175, 176.. -176!! I use this code to make them all positive:
var angle = Math.Atan2(point1.Y - point2.Y, point1.X - point2.X);
while (angle < 0d)
angle += Math.PI*2;
But now the angles can be 350, 359.. 2!!! Quite difficult to calc an average :) Can you please advice me how i can work this around?
Pic1
Pic2
Pic3
Pic4
Looking at the graph from the link you provided each branch in the tree has it's own angle, which is used to declare the control points of the branch. This branchAngle is the same as the one of the vector going from the first node to the previous one (every branch can spawn several branches in turn). The angle of the first branch (first node = previous node = center) seems to be around -60°.
Setting the type of curve can be done by compensating this branch angle (0°, -90°, -180°,...) for all branches in the tree. Resulting in the controlAngle used for laying out the control points.
Generating the control points while taking into account the angles:
//gen per branch
double branchAngle = 30 * Math.PI / 180; //e.g., calc vector angle here
double cosB = Math.Cos(branchAngle);
double sinB = Math.Sin(branchAngle);
//depending on the desired curve compensate -90°, -180°,...
double controlAngle = branchAngle - (90 * Math.PI / 180);
double cosA = Math.Cos(controlAngle);
double sinA = Math.Sin(controlAngle);
//gen 2 control points
//calculate dx dy after rotation with branchAngle
double dxbase = target.X - source.X, dybase = target.Y - source.Y;
double dx = dxbase*sinB - dybase*cosB
double dy = dxbase*cosB + dybase*sinB
//control points based on controlAngle
var pts = new[]
{
new Point(source.X + (2*dx/3)*cosA , source.Y + (2*dx/3)*sinA),
new Point(target.X - (dx/8)*cosA + (dy/8)*sinA, target.Y - (dx/8)*sinA - (dy/8)*cosA)
};
Quick check
branchAngle = 30° &
compensation = -90° ->
controlAngle = -60°

Windows Phone: Display GeoCoordinate accuracy on a map

I am currently displaying my location on a map successfully. I have a Geoposition of my location and from this I can get MyGeoPosition.Coordinate.Accuracy which is in metres. I'd like to display this on the map like most mobile mapping applications do to provide context to the quality of the position accuracy.
What I'd like to know is the best way to display this. I was planning to draw an ellipse but I don't know how to calculate the width and height of the ellipse from the meteres value and the zoom level of the map.
Any ideas would be appreciated.
Look at this "Retrieving current location".
double myAccuracy = ...;
GeoCoordinate myCoordinate = ...;
double metersPerPixels = (Math.Cos(myCoordinate.Latitude * Math.PI / 180) * 2 * Math.PI * 6378137) / (256 * Math.Pow(2, myMap.ZoomLevel));
double radius = myAccuracy / metersPerPixels;
Ellipse ellipse = new Ellipse();
ellipse.Width = radius * 2;
ellipse.Height = radius * 2;
ellipse.Fill = new SolidColorBrush(Color.FromArgb(75, 200, 0, 0));
Mmmm... Not sure If this reply is what you want, but:
Why don't you draw a colored circle, and change the color (red-yellow-green) and change the color depending a range of meters, for example if accuracy is less than 100 color gren, from 100 to 500 yellow, more than 500 red.

Common Mercator Projection formulas for Google Maps not working correctly

I am building a Tile Overlay server for Google maps in C#, and have found a few different code examples for calculating Y from Latitude. After getting them to work in general, I started to notice certain cases where the overlays were not lining up properly. To test this, I made a test harness to compare Google Map's Mercator LatToY conversion against the formulas I found online. As you can see below, they do not match in certain cases.
Case #1
Zoomed Out: The problem is most
evident when zoomed out. Up close,
the problem is barely visible.
Case #2
Point Proximity to Top & Bottom of
viewing bounds: The problem is worse
in the middle of the viewing bounds,
and gets better towards the edges.
This behavior can negate the behavior
of Case #1
The Test:
I created a google maps page to
display red lines using the Google Map
API's built in Mercator conversion,
and overlay this with an image using
the reference code for doing Mercator
conversion. These conversions are
represented as black lines. Compare
the difference.
The Results:
Equator http://www.kayak411.com/Mercator/MercatorComparison%20-%20Equator.png
North Zoomed Out http://www.kayak411.com/Mercator/MercatorComparison%20-%20North%20Zoomed%20Out.png
Check out the top-most and bottom-most lines:
North Top & Bottom Example http://www.kayak411.com/Mercator/MercatorComparison%20-%20North%20Zoomed%20Out%20-%20TopAndBottom.png
The problem gets visually larger but numerically smaller as you zoom in:
alt text http://www.kayak411.com/Mercator/MercatorComparison%20-%20North%20Zoomed%20Midway.png
And it all but disappears at closer zoom levels, regardless of screen orientation.
alt text http://www.kayak411.com/Mercator/MercatorComparison%20-%20North%20Zoomed%20In.png
The Code:
Google Maps Client Side Code:
var lat = 0;
for (lat = -80; lat <= 80; lat += 5) {
map.addOverlay(new GPolyline([new GLatLng(lat, -180), new GLatLng(lat, 0)], "#FF0033", 2));
map.addOverlay(new GPolyline([new GLatLng(lat, 0), new GLatLng(lat, 180)], "#FF0033", 2));
}
Server Side Code:
Tile Cutter :
http://mapki.com/wiki/Tile_Cutter
OpenStreetMap Wiki :
http://wiki.openstreetmap.org/wiki/Mercator
protected override void ImageOverlay_ComposeImage(ref Bitmap ZipCodeBitMap)
{
Graphics LinesGraphic = Graphics.FromImage(ZipCodeBitMap);
Int32 MapWidth = Convert.ToInt32(Math.Pow(2, zoom) * 255);
Point Offset =
Cartographer.Mercator2.toZoomedPixelCoords(North, West, zoom);
TrimPoint(ref Offset, MapWidth);
for (Double lat = -80; lat <= 80; lat += 5)
{
Point StartPoint = Cartographer.Mercator2.toZoomedPixelCoords(lat, -179, zoom);
Point EndPoint = Cartographer.Mercator2.toZoomedPixelCoords(lat, -1, zoom);
TrimPoint(ref StartPoint, MapWidth);
TrimPoint(ref EndPoint, MapWidth);
StartPoint.X = StartPoint.X - Offset.X;
EndPoint.X = EndPoint.X - Offset.X;
StartPoint.Y = StartPoint.Y - Offset.Y;
EndPoint.Y = EndPoint.Y - Offset.Y;
LinesGraphic.DrawLine(new Pen(Color.Black, 2),
StartPoint.X,
StartPoint.Y,
EndPoint.X,
EndPoint.Y);
LinesGraphic.DrawString(
lat.ToString(),
new Font("Verdana", 10),
new SolidBrush(Color.Black),
new Point(
Convert.ToInt32((width / 3.0) * 2.0),
StartPoint.Y));
}
}
protected void TrimPoint(ref Point point, Int32 MapWidth)
{
point.X = Math.Max(point.X, 0);
point.X = Math.Min(point.X, MapWidth - 1);
point.Y = Math.Max(point.Y, 0);
point.Y = Math.Min(point.Y, MapWidth - 1);
}
So, Anyone ever experienced this? Dare I ask, resolved this? Or simply have a better C# implementation of Mercator Project coordinate conversion?
Thanks!
Thank you all for your suggestions & assistance.
What I eventually found out is that it's not a formula or technical problem, I believe it's a methodology problem.
You can't define the viewing area in Lat/Lng format, and expect to populate it with the appropriate Mercator projections. That's where the distortion happens. Instead, you have to define the correct viewing box in Mercator, and project Mercator.
Doing that I was able to correctly match up with Google maps.
You may have to create several points along the longtitude in order for the points to be projected corretly along the latitude. In your examples you are only really projecting two points at the start and the end of the line and connecting the two.
The problem will be more apparent at the equator due to the more significant curvature of the earth. It will be less when zoomed in for the same reason.
Have a look at http://code.google.com/apis/maps/documentation/overlays.html#Great_Circles
Try creating your Google polylines with the geodsic parameter to see if this makes a difference. I think this adds points along the line and projects them automatically:
var lat = 0;
var polyOptions = {geodesic:true};
for (lat = -80; lat <= 80; lat += 5) {
map.addOverlay(new GPolyline([new GLatLng(lat, -180), new GLatLng(lat, 0)], "#FF0033", 2, polyOptions));
map.addOverlay(new GPolyline([new GLatLng(lat, 0), new GLatLng(lat, 180)], "#FF0033", 2, polyOptions));
}
I had to read into this as all my distance measurements were wrong in OpenLayers for similar reasons: http://geographika.co.uk/watch-out-for-openlayer-distances (more links/explanations)

Projecting a 3D point to a 2D screen coordinate

Based on information in Chapter 7 of 3D Programming For Windows (Charles Petzold), I've attempted to write as helper function that projects a Point3D to a standard 2D Point that contains the corresponding screen coordinates (x,y):
public Point Point3DToScreen2D(Point3D point3D,Viewport3D viewPort )
{
double screenX = 0d, screenY = 0d;
// Camera is defined in XAML as:
// <Viewport3D.Camera>
// <PerspectiveCamera Position="0,0,800" LookDirection="0,0,-1" />
// </Viewport3D.Camera>
PerspectiveCamera cam = viewPort.Camera as PerspectiveCamera;
// Translate input point using camera position
double inputX = point3D.X - cam.Position.X;
double inputY = point3D.Y - cam.Position.Y;
double inputZ = point3D.Z - cam.Position.Z;
double aspectRatio = viewPort.ActualWidth / viewPort.ActualHeight;
// Apply projection to X and Y
screenX = inputX / (-inputZ * Math.Tan(cam.FieldOfView / 2));
screenY = (inputY * aspectRatio) / (-inputZ * Math.Tan(cam.FieldOfView / 2));
// Convert to screen coordinates
screenX = screenX * viewPort.ActualWidth;
screenY = screenY * viewPort.ActualHeight;
// Additional, currently unused, projection scaling factors
/*
double xScale = 1 / Math.Tan(Math.PI * cam.FieldOfView / 360);
double yScale = aspectRatio * xScale;
double zFar = cam.FarPlaneDistance;
double zNear = cam.NearPlaneDistance;
double zScale = zFar == Double.PositiveInfinity ? -1 : zFar / (zNear - zFar);
double zOffset = zNear * zScale;
*/
return new Point(screenX, screenY);
}
On testing however this function returns incorrect screen coordinates (checked by comparing 2D mouse coordinates against a simple 3D shape). Due to my lack of 3D programming experience I am confused as to why.
The block commented section contains scaling calculations that may be essential, however I am not sure how, and the book continues with the MatrixCamera using XAML. Initially I just want to get a basic calculation working regardless of how inefficient it may be compared to Matrices.
Can anyone advise what needs to be added or changed?
I've created and succesfully tested a working method by using the 3DUtils Codeplex source library.
The real work is performed in the TryWorldToViewportTransform() method from 3DUtils. This method will not work without it (see the above link).
Very useful information was also found in the article by Eric Sink: Auto-Zoom.
NB. There may be more reliable/efficient approaches, if so please add them as an answer. In the meantime this is good enough for my needs.
/// <summary>
/// Takes a 3D point and returns the corresponding 2D point (X,Y) within the viewport.
/// Requires the 3DUtils project available at http://www.codeplex.com/Wiki/View.aspx?ProjectName=3DTools
/// </summary>
/// <param name="point3D">A point in 3D space</param>
/// <param name="viewPort">An instance of Viewport3D</param>
/// <returns>The corresponding 2D point or null if it could not be calculated</returns>
public Point? Point3DToScreen2D(Point3D point3D, Viewport3D viewPort)
{
bool bOK = false;
// We need a Viewport3DVisual but we only have a Viewport3D.
Viewport3DVisual vpv =VisualTreeHelper.GetParent(viewPort.Children[0]) as Viewport3DVisual;
// Get the world to viewport transform matrix
Matrix3D m = MathUtils.TryWorldToViewportTransform(vpv, out bOK);
if (bOK)
{
// Transform the 3D point to 2D
Point3D transformedPoint = m.Transform(point3D);
Point screen2DPoint = new Point(transformedPoint.X, transformedPoint.Y);
return new Nullable<Point>(screen2DPoint);
}
else
{
return null;
}
}
Since Windows coordinates are z into the screen (x cross y), I would use something like
screenY = viewPort.ActualHeight * (1 - screenY);
instead of
screenY = screenY * viewPort.ActualHeight;
to correct screenY to accomodate Windows.
Alternately, you could use OpenGL. When you set the viewport x/y/z range, you could leave it in "native" units, and let OpenGL convert to screen coordinates.
Edit:
Since your origin is the center. I would try
screenX = viewPort.ActualWidth * (screenX + 1.0) / 2.0
screenY = viewPort.ActualHeight * (1.0 - ((screenY + 1.0) / 2.0))
The screen + 1.0 converts from [-1.0, 1.0] to [0.0, 2.0]. At which point, you divide by 2.0 to get [0.0, 1.0] for the multiply. To account for Windows y being flipped from Cartesian y, you convert from [1.0, 0.0] (upper left to lower left), to [0.0, 1.0] (upper to lower) by subtracting the previous screen from 1.0. Then, you can scale to the ActualHeight.
This doesn't address the algoritm in question but it may be useful for peple coming across this question (as I did).
In .NET 3.5 you can use Visual3D.TransformToAncestor(Visual ancestor). I use this to draw a wireframe on a canvas over my 3D viewport:
void CompositionTarget_Rendering(object sender, EventArgs e)
{
UpdateWireframe();
}
void UpdateWireframe()
{
GeometryModel3D model = cube.Content as GeometryModel3D;
canvas.Children.Clear();
if (model != null)
{
GeneralTransform3DTo2D transform = cube.TransformToAncestor(viewport);
MeshGeometry3D geometry = model.Geometry as MeshGeometry3D;
for (int i = 0; i < geometry.TriangleIndices.Count;)
{
Polygon p = new Polygon();
p.Stroke = Brushes.Blue;
p.StrokeThickness = 0.25;
p.Points.Add(transform.Transform(geometry.Positions[geometry.TriangleIndices[i++]]));
p.Points.Add(transform.Transform(geometry.Positions[geometry.TriangleIndices[i++]]));
p.Points.Add(transform.Transform(geometry.Positions[geometry.TriangleIndices[i++]]));
canvas.Children.Add(p);
}
}
}
This also takes into account any transforms on the model etc.
See also: http://blogs.msdn.com/wpf3d/archive/2009/05/13/transforming-bounds.aspx
It's not clear what you are trying to achieve with aspectRatio coeff. If the point is on the edge of field of view, then it should be on the edge of screen, but if aspectRatio!=1 it isn't. Try setting aspectRatio=1 and make window square. Are the coordinates still incorrect?
ActualWidth and ActualHeight seem to be half of the window size really, so screenX will be [-ActualWidth; ActualWidth], but not [0; ActualWidth]. Is that what you want?
screenX and screenY should be getting computed relative to screen center ...
I don't see a correction for the fact that when drawing using the Windows API, the origin is in the upper left corner of the screen. I am assuming that your coordinate system is
y
|
|
+------x
Also, is your coordinate system assuming origin in the center, per Scott's question, or is it in the lower left corner?
But, the Windows screen API is
+-------x
|
|
|
y
You would need the coordinate transform to go from classic Cartesian to Windows.

Categories

Resources