Converting from world coordinates to tile location - google-maps-android-api-2

me and a friend are trying to build an android app for class that uses google maps and we have been spending days on this one error.
Ideally the app receives updates of the user's location, stores them, and paints over any stored coordinate. That turned out to be a nightmare to implement so right now we're just trying to paint over the entire tile if that tile contains a coordinate that the user has visited. However we think something's wrong with our conversion from coordinate to tile location because the higher the zoom the more our painted tile moves north and a little west from the actual coordinate.
private boolean isCoordInTileForZoom(int tileX, int tileY, int zoom)
{
//coordinate to check if they are in the current tile
float lon = WILF_TEST_COOR.x;
float lat = WILF_TEST_COOR.y;
//coordinates in terms of map length and map height
float mapX = lon + 180;
float mapY = (lat * -1) + 150;
//number of tiles in a row or column (2^zoom)
int tiles = (int) Math.pow(2,zoom);
//the height and width of a single tile
double tileWidth = 360.0/tiles;
double tileHeight = 180.0/tiles;
int mapCol = (int) (mapX/tileWidth);
int mapRow = (int) (mapY/tileHeight);
Log.d("Minimap.mapGridDetails"," \n" + "Zoom level: " + zoom + "\nMap Rows: " + tiles + "\nTile Width: " + tileWidth + "\nTile Height: " + tileHeight);
if (mapCol == tileX && mapRow == tileY)
{
return true;
}
else
{
return false;
}
}
Now we're assuming the world is 360 degrees across and 180 degrees tall which means, at zoom level 2, each tile should be 90x45 (a 4x4 grid). And it works at zoom 2 and I think 3 but at zoom 4 and beyond the painted tile jumps north of the expected spot.
My feeling is that the problem lies in our assumption of how coordinate conversion works (we're assuming Google's world map is a nicely laid out flat surface which is perhaps exceptionally naive of us) or maybe the google map is actually taller than 180 degrees. Either way, this thing is due in a few days so we thank you in advance for any advice.

Have you seen this: https://developers.google.com/maps/documentation/javascript/examples/map-coordinates ?
Although its JavaScript, it shows the principle of the correct coordinate conversion.
You may also check my answer to this SO question. The class TileProjection I provided there should answer your question. You may e.g. use its method getTileBounds, to check whether your coordinates are inside the tile, or you may use the method latLngToPoint and check, whether x and y of the point are both in the range 0 - TILE_SIZE.

Related

Get XY Tile Coordinate at Z Zoom Level with Leaflet

I have figured out how to get XYZ coordinates by extending Leaflet with a createTile function.
But what I'm wanting to know is how do I access the XY tile name/coordinate for a fixed Z zoom level around my GPS coordinates, even if I'm not zoomed in.
Why? I'm working on a P2P/decentralized version of Uber, and the XY coordinates are a good common/shared location index for users to lookup/subscribe/query against. As in, everybody within that X mile radius all will know the same XY coordinate name and use that as a deterministic key to find each other with.
This project will "Convert lon, lat to screen pixel x, y from 0, 0 origin, at a certain zoom level." https://github.com/mapbox/sphericalmercator
UPDATED:
function lng2tile(lon,z) { return (Math.floor((lon+180)/360*Math.pow(2,z))) }
function lat2tile(lat,z) { return (Math.floor((1-Math.log(Math.tan(lat*Math.PI/180) + 1/Math.cos(lat*Math.PI/180))/Math.PI)/2 *Math.pow(2,z))) }
Or try this:
var row = Math.floor((location.lng + 180) / (360 / Math.pow(2, zoomLevel)));
var col = Math.floor((90 + (location.lat * -1)) / (180 / Math.pow(2, (zoomLevel - 1))));

Longitude and Latitude to location on sphere in Unity

Hi all,
I'm trying to transform locations based upon longitude and latitude to a vector3 location, which will be placed on a sphere in Unity. However, the location seems to be constantly off (compared to the actual location).
I use the following code at the moment:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class testPosLatLong : MonoBehaviour {
public float longi;
public float lati;
public float radius;
public Transform marker;
// Use this for initialization
void Start () {
// Transfer to Radians from Degrees
float templongi = longi * Mathf.PI / 180;
float templati = lati * Mathf.PI / 180;
float Xpos = radius * Mathf.Cos(templati) * Mathf.Cos(templongi);
float Ypos = radius * Mathf.Cos(templati) * Mathf.Sin(templongi);
float Zpos = radius * Mathf.Sin(templati);
Debug.Log ("X, Y, Z" + Xpos + " " + Ypos + " " + Zpos);
// Set the X,Y,Z pos from the long and lat
Instantiate(marker);
marker.position = new Vector3 (Xpos, Zpos, Ypos);
}
// Update is called once per frame
void Update () {
}
}
I've tried to set the longitude and latitude to zero, which looks like it got the right position on the sphere:
But when I try the longitude and latitude of Amsterdam for example it gives the following results (left side), while it should be the result on the right side.
Am I missing something or what is going wrong here? I tried googling a lot, but couldn't find anything that might explain my current problem. The project itself can be found here: https://wetransfer.com/downloads/31303bd353fd9fde874e92338e68573120171205170107/1208ac%3E
Hope somebody can help.
I think your globe is what's wrong. It's tough to see from your images, but to me the equator looks like it's in slightly the wrong spot, and the North Pole looks to be too crowded with Greenland. I suspect is has to do with the projection you're using to paste the globe image onto the sphere.
Typically there are very complex ways of projecting 2D maps/images onto 3D surfaces. There's a whole field of geospatial analysis on how to do this accurately. It's a tough problem.
Also, The actual earth isn't exactly a sphere, so this could give you errors as well, but I would guess these would result in much smaller errors that what you describe. Accurate maps reperesent the 3D earth as a "geoid". to get locally accurate maps, people typically project data differently in small areas to maximize accuracy at a local scale. Otherwise you see large distortions. So as you zoom into a global map, the projections actually change significantly.
One possible way to test where the issue is coming from could be to set up control points of known coordinates both on your map image and on the globe, and then test to see if they line up. Like put a spike sticking out of the world at where Amsterdam SHOULD be on the globe, and where it is on the actual image. If these two don't line up then you can be pretty sure where your problem lies. This is called "Georeferencing".
I think for 3D projections you'd likely need at least 4 such "control points". You might take a look at the geospatial community on stackexchange for more detailed info on projecting maps and swapping between coordinate systems.

Map distance to zoom in Google Static Maps

I am using Google Static Maps to display maps in my AppleTV app. What I need is to somehow map a distance of e.g. 1km to the zoom parameter of the Static Maps API.
In other words I have an imageView in which I wish to load the map image and if I know that the height of my imageView is 400px, and I wish for this map to show a real Earth surface of 1000m North to South, how would I tell the API to return me the map with this exact zoom?
I found a very similar question here, however no suitable answer is provided.
As stated at Google Maps Documentation:
Because the basic Mercator Google Maps tile is 256 x 256 pixels.
Also note that every zoom level, the map has 2 n tiles.
Meaning that at zoomLevel 2,the pixels in any direction of a map are = 256 * 2² = 1024px.
Taking into account that the earth has a perimeter of ~40,000 kilometers, in zoom 0, every pixel ~= 40,000 km/256 = 156.25 km
At zoom 9, pixels are 131072: 1px = 40,000 km / 131072 = 0.305 km ... and so on.
If we want 400px = 1km, we have to choose the closest approximation possible, so: 1px = 1km/400 = 0.0025km
I tried zoom = 15 and obtained 1px = 0.00478 and zoom = 16 that gave me 1px = 0.00238km
Meaning that you should use zoom = 16, and you will have 0.955km every 400px in the Equator line and only for x coordinates.
As you go north or south in latitude, perimeter is everytime smaller, thus changing the distance. And of course it also changes the correlation in the y axis as the projection of a sphere is tricky.
If you want to calculate with a function the exact distance, you should use the one provided by Google at their documentation:
// Describe the Gall-Peters projection used by these tiles.
gallPetersMapType.projection = {
fromLatLngToPoint: function(latLng) {
var latRadians = latLng.lat() * Math.PI / 180;
return new google.maps.Point(
GALL_PETERS_RANGE_X * (0.5 + latLng.lng() / 360),
GALL_PETERS_RANGE_Y * (0.5 - 0.5 * Math.sin(latRadians)));
},
fromPointToLatLng: function(point, noWrap) {
var x = point.x / GALL_PETERS_RANGE_X;
var y = Math.max(0, Math.min(1, point.y / GALL_PETERS_RANGE_Y));
return new google.maps.LatLng(
Math.asin(1 - 2 * y) * 180 / Math.PI,
-180 + 360 * x,
noWrap);
}
};

Calculating coordinates from reference points

I'm working on a game in Unity where you can walk around in a city that also exists in real life.
In the game you should be able to enter real-world coordinates, or use your phone's GPS, and you'll be transported to the in-game position of those coordinates.
For this, i'd need to somehow convert the game coordinates to latitude and longitude coordinates. I have some coordinates from specific buildings, and i figured i might be able to write a script to determine the game coordinates from those reference points.
I've been searching for a bit on Google, and though i have probably come across the right solutions occasionally, i've been unable to understand them enough to use it in my code.
If someone has experience with this, or knows how i could do this, i'd appreciate it if you could help me understand it :)
Edit: Forgot to mention that other previous programmers have already placed the world at some position and rotation they felt like using, which unfortunately i can't simply change without breaking things.
Tim Falken
This is simple linear math. The main issues you'll come across is the fact that your game coordinate system will be probably be reversed along one or more axis. You'll probably need to reverse the direction along the latitude (Y) axis of your app. Aside from that it is just a simple conversion of the scales. Since you say that this is the map of a real place you should be able to easily figure out the min\max lon\lat which your map covers. Take the absolute value of the difference between these two values and divide that by the width\height of your map in each direction. This will be the change in latitude per map unit value. Store this value and it should be easy to convert both ways between the two units. Make functions that abstract the details and you should have no problems calculating this either way.
I assume that you have been able to retrieve the GPS coordinates OK.
EDIT:
By simple linear math I mean something like this (this is C++ style psuedo code and completely untested; in a real world example the constants would all be member variables instead):
define('MAP_WIDTH', 1000);
define('MAP_HEIGHT', 1000);
define('MIN_LON', 25.333);
define('MIN_LAT', 20.333);
define('MAX_LON', 27.25);
define('MAX_LAT', 20.50);
class CoordConversion {
float XScale=abs(MAX_LON-MIN_LON)/MAP_WIDTH;
float YScale=abs(MAX_LAT-MIN_LAT)/MAP_HEIGHT;
int LonDir = MIN_LON<MAX_LON?1:-1;
int LatDir = MIN_LAT<MAX_LAT?1:-1;
public static float GetXFromLon(float lon) {
return (this.LonDir>0?(lon-MIN_LON):(lon-MAX_LON))*this.XScale;
}
public static float GetYFromLat(float lat) {
return (this.LatDir >0?(lat-MIN_LAT):(lat-MAX_LAT))*this.YScale;
}
public static float GetLonFromX(float x) {
return (this.LonDir>0?MIN_LON:MAX_LON)+(x/this.XScale);
}
public static float GetLatFromY(float y) {
return (this.LonDir>0?MIN_LAT:MAX_LAT)+(y/this.YScale);
}
}
EDIT2: In the case that the map is rotated you'll want to use the minimum and maximum lon\lat actually shown on the map. You'll also need to rotate each point after the conversion. I'm not even going to attempt to get this right off the top of my head but I can give your the code you'll need:
POINT rotate_point(float cx,float cy,float angle,POINT p)
{
float s = sin(angle);
float c = cos(angle);
// translate point back to origin:
p.x -= cx;
p.y -= cy;
// rotate point
float xnew = p.x * c - p.y * s;
float ynew = p.x * s + p.y * c;
// translate point back:
p.x = xnew + cx;
p.y = ynew + cy;
}
This will need to be done in when returning a game point and also it needs to be done in reverse before using a game point to convert to a lat\lon point.
EDIT3: More help on getting the coordinates of your maps. First find the city or whatever it is on Google maps. Then you can right click the highest point (furthest north) on your maps and find the highest longitude. Repeat this for all four cardinal directions and you should be set.

Car turning circle and moving the sprite

I would like to use Cocos2d on the iPhone to draw a 2D car and make it steer from left to right in a natural way.
Here is what I tried:
Calculate the angle of the wheels and just move it to the destination point where the wheels point to. But this creates a very unnatural feel. The car drifts half the time
After that I started some research on how to get a turning circle from a car, which meant that I needed a couple of constants like wheelbase and the width of the car.
After a lot of research, I created the following code:
float steerAngle = 30; // in degrees
float speed = 20;
float carWidth = 1.8f; // as in 1.8 meters
float wheelBase = 3.5f; // as in 3.5 meters
float x = (wheelBase / abs(tan(steerAngle)) + carWidth/ 2);
float wheelBaseHalf = wheelBase / 2;
float r = (float) sqrt(x * x + wheelBaseHalf * wheelBaseHalf);
float theta = speed * 1 / r;
if (steerAngle < 0.0f)
theta = theta * -1;
drawCircle(CGPointMake(carPosition.x - r, carPosition.y),
r, CC_DEGREES_TO_RADIANS(180), 50, NO);
The first couple of lines are my constants. carPosition is of the type CGPoint. After that I try to draw a circle which shows the turning circle of my car, but the circle it draws is far too small. I can just make my constants bigger, to make the circle bigger, but then I would still need to know how to move my sprite on this circle.
I tried following a .NET tutorial I found on the subject, but I can't really completely convert it because it uses Matrixes, which aren't supported by Cocoa.
Can someone give me a couple of pointers on how to start this? I have been looking for example code, but I can't find any.
EDIT After the comments given below
I corrected my constants, my wheelBase is now 50 (the sprite is 50px high), my carWidth is 30 (the sprite is 30px in width).
But now I have the problem, that when my car does it's first 'tick', the rotation is correct (and also the placement), but after that the calculations seem wrong.
The middle of the turning circle is moved instead of kept at it's original position. What I need (I think) is that at each angle of the car I need to recalculate the original centre of the turning circle. I would think this is easy, because I have the radius and the turning angle, but I can't seem to figure out how to keep the car moving in a nice circle.
Any more pointers?
You have the right idea. The constants are the problem in this case. You need to specify wheelBase and carWidth in units that match your view size. For example, if the image of your car on the screen has a wheel base of 30 pixels, you would use 30 for the WheelBase variable.
This explains why your on-screen circles are too small. Cocoa is trying to draw circles for a tiny little car which is only 1.8 pixels wide!
Now, for the matter of moving your car along the circle:
The theta variable you calculate in the code above is a rotational speed, which is what you would use to move the car around the center point of that circle:
Let's assume that your speed variable is in pixels per second, to make the calculations easier. With that assumption in place, you would simply execute the following code once every second:
// calculate the new position of the car
newCarPosition.x = (carPosition.x - r) + r*cos(theta);
newCarPosition.y = carPosition.y + r*sin(theta);
// rotate the car appropriately (pseudo-code)
[car rotateByAngle:theta];
Note: I'm not sure what the correct method is to rotate your car's image, so I just used rotateByAngle: to get the point across. I hope it helps!
update (after comments):
I hadn't thought about the center of the turning circle moving with the car. The original code doesn't take into account the angle that the car is already rotated to. I would change it as follows:
...
if (steerAngle < 0.0f)
theta = theta * -1;
// calculate the center of the turning circle,
// taking int account the rotation of the car
circleCenter.x = carPosition.x - r*cos(carAngle);
circleCenter.y = carPosition.y + r*sin(carAngle);
// draw the turning circle
drawCircle(circleCenter, r, CC_DEGREES_TO_RADIANS(180), 50, NO);
// calculate the new position of the car
newCarPosition.x = circleCenter.x + r*cos(theta);
newCarPosition.y = circleCenter.y + r*sin(theta);
// rotate the car appropriately (pseudo-code)
[car rotateByAngle:theta];
carAngle = carAngle + theta;
This should keep the center of the turning circle at the appropriate point, even if the car has been rotated.