I'm trying to calculate the given tile a node is in from OpenStreetMap so that I can manually add nodes directly into the current_nodes table. If I want to add a new node at latitude X and longitude Y, I can then calculate the 'tile' value that's needed before inserting into the current_nodes table.
I'm trying to convert the current PostgreSQL function into C# but it's not giving the output expected. I'm not sure I've converted it correctly or if I'm missing something.
//maptile_for_point(scaled_lat bigint, scaled_lon bigint, zoom integer)
DECLARE
lat CONSTANT DOUBLE PRECISION := scaled_lat / 10000000.0;
lon CONSTANT DOUBLE PRECISION := scaled_lon / 10000000.0;
zscale CONSTANT DOUBLE PRECISION := 2.0 ^ zoom;
pi CONSTANT DOUBLE PRECISION := 3.141592653589793;
r_per_d CONSTANT DOUBLE PRECISION := pi / 180.0;
x int4;
y int4;
BEGIN
-- straight port of the C code. see db/functions/maptile.c
x := floor((lon + 180.0) * zscale / 360.0);
y := floor((1.0 - ln(tan(lat * r_per_d) + 1.0 / cos(lat * r_per_d)) / pi) * zscale / 2.0);
RETURN (x << zoom) | y;
END;
So far I have:
public const double pi = 3.141592653589793;
public const double r_per_d = pi / 180.0;
public static long GetTile(double scaled_lat, double scaled_lon, int zoom)
{
double lat = scaled_lat / 10000000.0;
double lon = scaled_lon / 10000000.0;
return GetTile(lat, lon, zoom);
}
public static long GetTile(long lat, long lon, int zoom)
{
double zscale = Math.Pow(2.0, zoom);
long x = (long)Math.Floor((lon + 180.0) * zscale / 360.0);
long y = (long)Math.Floor((1.0 - Math.Log(Math.Tan(lat * r_per_d) + 1.0 / Math.Cos(lat * r_per_d)) / pi) * zscale / 2.0);
return (x << zoom) | y;
}
I call the function but loop through possible zoom values but none of them are matching the expected output.
Using data that's already in the table (526868344, -18000386) it should return 2062343892.
for (int i = 0; i < int.MaxValue; i++)
{
var r1 = NodeTileHelper.GetTile(526868344, -18000386, i);
if (r1 == 2062343892)
{
;
}
}
It looks like the problem might be how the method overloads are defined.
The following snippet of code is going to call the GetTile(long, long, int) overload, so it may not account for the scaling that is present in the other method.
NodeTileHelper.GetTile(526868344, -18000386, i);
In addition the following code in the scaling method recursively calls itself, because of the variable declarations. In order to account for the different method, you will need to convert lat and lon to long types.
double lat, lon;
// ...
return GetTile(lat, lon, zoom);
Given the original stored procedure definition, I think it would make more sense to consolidate this into one method instead of using overloads. Alternately, I think using different names for the methods would make it clearer than simply using the difference between parameter types (e.g,. long vs. double).
Related
I want to calculate bearing between 2 GPS positions, I foollowed this page recommandations for my algorythm:
public static double Bearing(IPointGps pt1, IPointGps pt2)
{
double x = Math.Cos(pt1.Latitude) * Math.Sin(pt2.Latitude) - Math.Sin(pt1.Latitude) * Math.Cos(pt2.Latitude) * Math.Cos(pt2.Longitude - pt1.Longitude);
double y = Math.Sin(pt2.Longitude - pt1.Longitude) * Math.Cos(pt2.Latitude);
// Math.Atan2 can return negative value, 0 <= output value < 2*PI expected
return (Math.Atan2(y, x) + Math.PI * 2)%(Math.PI * 2);
}
Then I transform my value in degrees using this method
public static double RadiansToDegrees(double angle)
{
return (angle * 180.0) / Math.PI;
}
I have the following test sample:
Point1 (lat, long) = 43.6373638888888888888888888888889, 1.35762222222222222222222222222222
Point2 (lat, long) = 43.6156444444444444444444444444444,1.380225
Expected bearing = 323°
However, I obtain a bearing of 315.5° (5.5062235835910762 rad). If i calculate the expected radian value, i get 5.637413 which leaves no doubt that my problem lies in my bearing method.
I already implemented other computation methods using .Net Math package (including Cos, Sin, Tan and ATan methods) and my unit tests pass with 1e-12 precision. What am I missing?
PS: I also tryied to reimplement the Atan2 method in case there is a lack of precision in it. I obtain the very same result
edit: My Latitude and Longitude are double as per the following interface
public interface IPointGps
{
double Latitude { get; }
double Longitude { get; }
}
Math.Sin() and all similar methods expect argument in radians, but your latitudes and longitudes are in degrees. You have to convert IPointGps to radians before you calculate bearing, or modify Bearing calculation, e.g.:
public static double Bearing(IPointGps pt1, IPointGps pt2)
{
double x = Math.Cos(DegreesToRadians(pt1.Latitude)) * Math.Sin(DegreesToRadians(pt2.Latitude)) - Math.Sin(DegreesToRadians(pt1.Latitude)) * Math.Cos(DegreesToRadians(pt2.Latitude)) * Math.Cos(DegreesToRadians(pt2.Longitude - pt1.Longitude));
double y = Math.Sin(DegreesToRadians(pt2.Longitude - pt1.Longitude)) * Math.Cos(DegreesToRadians(pt2.Latitude));
// Math.Atan2 can return negative value, 0 <= output value < 2*PI expected
return (Math.Atan2(y, x) + Math.PI * 2) % (Math.PI * 2);
}
public static double DegreesToRadians(double angle)
{
return angle * Math.PI / 180.0d;
}
returns bearing 5.637716736134105.
It looks like your latitude and longitude variables are float (single precision). If that is the case, then your are facing a precision error.
I want to calculate bearing between 2 GPS positions, I foollowed this page recommandations for my algorythm:
public static double Bearing(IPointGps pt1, IPointGps pt2)
{
double x = Math.Cos(pt1.Latitude) * Math.Sin(pt2.Latitude) - Math.Sin(pt1.Latitude) * Math.Cos(pt2.Latitude) * Math.Cos(pt2.Longitude - pt1.Longitude);
double y = Math.Sin(pt2.Longitude - pt1.Longitude) * Math.Cos(pt2.Latitude);
// Math.Atan2 can return negative value, 0 <= output value < 2*PI expected
return (Math.Atan2(y, x) + Math.PI * 2)%(Math.PI * 2);
}
Then I transform my value in degrees using this method
public static double RadiansToDegrees(double angle)
{
return (angle * 180.0) / Math.PI;
}
I have the following test sample:
Point1 (lat, long) = 43.6373638888888888888888888888889, 1.35762222222222222222222222222222
Point2 (lat, long) = 43.6156444444444444444444444444444,1.380225
Expected bearing = 323°
However, I obtain a bearing of 315.5° (5.5062235835910762 rad). If i calculate the expected radian value, i get 5.637413 which leaves no doubt that my problem lies in my bearing method.
I already implemented other computation methods using .Net Math package (including Cos, Sin, Tan and ATan methods) and my unit tests pass with 1e-12 precision. What am I missing?
PS: I also tryied to reimplement the Atan2 method in case there is a lack of precision in it. I obtain the very same result
edit: My Latitude and Longitude are double as per the following interface
public interface IPointGps
{
double Latitude { get; }
double Longitude { get; }
}
Math.Sin() and all similar methods expect argument in radians, but your latitudes and longitudes are in degrees. You have to convert IPointGps to radians before you calculate bearing, or modify Bearing calculation, e.g.:
public static double Bearing(IPointGps pt1, IPointGps pt2)
{
double x = Math.Cos(DegreesToRadians(pt1.Latitude)) * Math.Sin(DegreesToRadians(pt2.Latitude)) - Math.Sin(DegreesToRadians(pt1.Latitude)) * Math.Cos(DegreesToRadians(pt2.Latitude)) * Math.Cos(DegreesToRadians(pt2.Longitude - pt1.Longitude));
double y = Math.Sin(DegreesToRadians(pt2.Longitude - pt1.Longitude)) * Math.Cos(DegreesToRadians(pt2.Latitude));
// Math.Atan2 can return negative value, 0 <= output value < 2*PI expected
return (Math.Atan2(y, x) + Math.PI * 2) % (Math.PI * 2);
}
public static double DegreesToRadians(double angle)
{
return angle * Math.PI / 180.0d;
}
returns bearing 5.637716736134105.
It looks like your latitude and longitude variables are float (single precision). If that is the case, then your are facing a precision error.
In my previous question, I looked to speed up list selection based on a function result. Now, my bottleneck is the function itself.
It's a basic Haversine function, using the code below:
private static double Haversine(double lat1, double lat2, double lon1, double lon2)
{
const double r = 6371e3; // meters
var dlat = (lat2 - lat1)/2;
var dlon = (lon2 - lon1)/2;
var q = Math.Pow(Math.Sin(dlat), 2) + Math.Cos(lat1) * Math.Cos(lat2) * Math.Pow(Math.Sin(dlon), 2);
var c = 2 * Math.Atan2(Math.Sqrt(q), Math.Sqrt(1 - q));
var d = r * c;
return d / 1000;
}
So... why does it need to be so fast? The issue is that I'm calling it a lot. Think north of 16,500,000 times.
Obviously, that's a lot. And in my use case I'm passing it objects that it has to get the location data from and then convert Latitude and Longitude to radians, which increases the time further (only by about 15%). I don't know that there's much I can do about that, but I do know that by passing it purely doubles in radians (as above) it takes ~4.5 seconds - which is more than 75% of the processing time in my implementation. The lines assigning values to q and c seems to take up the most time.
As it's being called a lot, I'm looking to make it a bit faster. I'm open to multithreaded solutions (and am currently working on one myself), but it may be a bit more difficult to implement given the use case in my previous question (linked above).
This was as optimized as I could get the answer (and, to my knowledge, this is the most optimized the answer could possibly get without doing some wizard-level optimization on the formula itself):
private static double Haversine(double lat1, double lat2, double lon1, double lon2)
{
const double r = 6378100; // meters
var sdlat = Math.Sin((lat2 - lat1) / 2);
var sdlon = Math.Sin((lon2 - lon1) / 2);
var q = sdlat * sdlat + Math.Cos(lat1) * Math.Cos(lat2) * sdlon * sdlon;
var d = 2 * r * Math.Asin(Math.Sqrt(q));
return d;
}
On my machine, this formula, when run 16.5 million times, runs at almost exactly 3 seconds, whereas the above version runs at just shy of 5.
However, I maintain that the biggest optimization could be in the system that actually calls this method. 33,000 times on each of 500 Latitude-Longitude pairs? That's a system that is likely in dire need of optimization itself. For starters, you could first calculate the linear-distance-squared of your pairs and only process pairs that are below a certain threshold. Or you could maintain a look-up table to avoid calculating the same pair more than once. Or, depending on the source of that 33,000 number, you can prioritize so that you don't need to call the method nearly that much.
For me this is more accurate
public static class Haversine {
public static double calculate(double lat1, double lon1, double lat2, double lon2) {
var R = 6372.8; // In kilometers
var dLat = toRadians(lat2 - lat1);
var dLon = toRadians(lon2 - lon1);
lat1 = toRadians(lat1);
lat2 = toRadians(lat2);
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.Asin(Math.Sqrt(a));
return R * c;
}
public static double toRadians(double angle) {
return Math.PI * angle / 180.0;
}
}
void Main() {
Console.WriteLine(String.Format("The distance between coordinates {0},{1} and {2},{3} is: {4}", 36.12, -86.67, 33.94, -118.40, Haversine.calculate(36.12, -86.67, 33.94, -118.40)));
}
// Returns: The distance between coordinates 36.12,-86.67 and 33.94,-118.4 is: 2887.25995060711
I've read several links discussing storing 2 or 3 floats in one float. Here's an example:
Storing two float values in a single float variable
and another:
http://uncommoncode.wordpress.com/2012/11/07/float-packing-in-shaders-encoding-multiple-components-in-one-float/
and yet another:
decode rgb value to single float without bit-shift in glsl
I've seen others but all of them use the same principle. If you want to encode x and y, they multiply y by some factor and then add x to it. Well this makes since on paper, but I don't understand how in the world it can work when stored to a floating value. Floating values only have 7 significant digits. If you add a big number and a small number, the small number is just truncated and lost. The precision only shows the value of the big number.
Since everyone seems to prescribe the same method, I tried it myself and it did exactly what I thought it would do. When I decoded the numbers, the number that wasn't multiplied turned out as 0.0. It was completely lost in the encoded float.
Here's an example of some MaxScript I tried to test it:
cp = 256.0 * 256.0
scaleFac = 16777215
for i = 1 to 20 do (
for j = 1 to 20 do (
x = (i as float / 20.01f) as float;
y = (j as float / 20.01f) as float;
xScaled = x * scaleFac;
yScaled = y * scaleFac;
f = (xScaled + yScaled * cp) as float
print ("x[" + xScaled as string + "] y[" + yScaled as string + "]" + " e[" + f as string + "]")
dy = floor(f / cp)
dx = (f - dy * cp)
print ("x[" + dx as string + "] y[" + dy as string + "]" + " e[" + f as string + "]")
)
)
dx is 0.0 everytime. Can anyone shed some light on this? NOTE: It doesn't matter whether I make cp = 128, 256, 512 or whatever. It still gives me the same types of results.
This method works for storing two integers. You're effectively converting your floating point numbers to large integers by multiplying by scaleFac, which is good, but it would be better to make it explicit with int(). Then you need to make sure of two things: cp is greater than the largest number you're working with (scaleFac), and the square of cp is small enough to fit into a floating point number without truncation (about 7 digits for a single precision float).
Here is a working code in C to pack two floats into one float and unpack them.
You should change scaleFactor and cp parameters as according to your possible value ranges (yourBiggestNumber * scaleFactor < cp). It is a precision battle. Try printing a few results to find good values for your case. The example below allows floats in [0 to 1) range.
#include <math.h>
/* yourBiggestNumber * scaleFactor < cp */
double scaleFactor = 65530.0;
double cp = 256.0 * 256.0;
/* packs given two floats into one float */
float pack_float(float x, float y) {
int x1 = (int) (x * scaleFactor);
int y1 = (int) (y * scaleFactor);
float f = (y1 * cp) + x1;
return f;
}
/* unpacks given float to two floats */
int unpack_float(float f, float* x, float* y){
double dy = floor(f / cp);
double dx = f - (dy * cp);
*y = (float) (dy / scaleFactor);
*x = (float) (dx / scaleFactor);
return 0;
}
It will work only if your individual floats are small enough to be packed into one float location.
So you can pack 2 numbers by "dividing" this into two to store 2 numbers that can be represented by half the space.
Here's the code I use for packing and unpacking floats. It works by packing first float (0..1) into the first four bytes of a 8-bit (0..256) number, and the next float into the remaining 4 bits. The resulting numbers have 16 possible combinations each (2^4). In some cases this is good enough:
private float PackFloatsInto8Bits( float v1, float v2 )
{
var a = Mathf.Round( v1 * 15f );
var b = Mathf.Round( v2 * 15f );
var bitShiftVector = new Vector2( 1f/( 255f/16f ), 1f/255f );
return Vector2.Dot( new Vector2( a, b ), bitShiftVector );
}
private Vector2 UnpackFloatsFrom8Bits( float input )
{
float temp = input * 15.9375f;
float a = Mathf.Floor(temp) / 15.0f;
float b = Frac( temp ) * 1.0667f;
return new Vector2(a, b);
}
I'm trying to merge two databases for consolidating two clients' websites. However, Client A has been using regular Lat/Lon pairs for geolocation, while Client B is using Lambert 72 (X/Y) coordinates.
I've built a script that should convert these coordinates (as I'm not sure which coordinates will be used in the final merged database, I'm trying converting them either way).
I took some snippets from here: http://zoologie.umh.ac.be/tc/algorithms.aspx
Please note that all coordinates mentioned below point to locations in Belgium.
I'm converting some coordinates to see if the calculations are correct, but the coordinates I'm getting seem to be way off. For reference, the center of Belgium is roughly (North 50.84323737103243, East 4.355735778808594), so I'd expect all coordinates to be close to these values.
I converted the Lambert 72 value (X: 151488250, Y: 170492909) to a Lat/Lon pair, but the result is: (-87.538.... , -50.724....) which is way off from the expected values.
If I convert full circle (Lambert->LatLon->Lambert and vice versa), I get the same result values as I entered, so I know my conversions are at least consistent and the conversions are perfect inversions of one another.
I tried some online converter tools as well, and they give me the same (-87.538.... , -50.724....) result.
Since multiple sources yield the same results, and my conversions are correct inversions of eachother, I'm figuring the calculations themselves are correct, but the resulting values still need to be converted/offset further?
I consider myself to be sufficient in algebra, but cartographic projections completely elude me.
Can someone please shed some light on this?
Extra Info
I hope I posted this in the correct forum. I'm not really sure where to put this as this is a mix of geography, mathematics and coding/conversion...
The mentioned Lambert coordinates (X: 151488250, Y: 170492909) point to a location in Brussels, so the Lat/Lon result should be very near to (North 50.84323737103243, East 4.355735778808594).
Please find my conversion functions below:
public static Lambert72 LatLon_To_Lambert72(LatLon latlon)
{
var lat = latlon.Lat;
var lng = latlon.Lon;
double LongRef = 0.076042943;
//=4°21'24"983
double bLamb = 6378388 * (1 - (1 / 297));
double aCarre = Math.Pow(6378388, 2);
double eCarre = (aCarre - Math.Pow(bLamb, 2)) / aCarre;
double KLamb = 11565915.812935;
double nLamb = 0.7716421928;
double eLamb = Math.Sqrt(eCarre);
double eSur2 = eLamb / 2;
//conversion to radians
lat = (Math.PI / 180) * lat;
lng = (Math.PI / 180) * lng;
double eSinLatitude = eLamb * Math.Sin(lat);
double TanZDemi = (Math.Tan((Math.PI / 4) - (lat / 2))) * (Math.Pow(((1 + (eSinLatitude)) / (1 - (eSinLatitude))), (eSur2)));
double RLamb = KLamb * (Math.Pow((TanZDemi), nLamb));
double Teta = nLamb * (lng - LongRef);
double x = 0;
double y = 0;
x = 150000 + 0.01256 + RLamb * Math.Sin(Teta - 0.000142043);
y = 5400000 + 88.4378 - RLamb * Math.Cos(Teta - 0.000142043);
return new Lambert72(x, y);
}
public static LatLon Lambert72_To_LatLon(Lambert72 lb72)
{
double X = lb72.X;
double Y = lb72.Y;
double LongRef = 0.076042943;
//=4°21'24"983
double nLamb = 0.7716421928;
double aCarre = Math.Pow(6378388, 2);
double bLamb = 6378388 * (1 - (1 / 297));
double eCarre = (aCarre - Math.Pow(bLamb, 2)) / aCarre;
double KLamb = 11565915.812935;
double eLamb = Math.Sqrt(eCarre);
double eSur2 = eLamb / 2;
double Tan1 = (X - 150000.01256) / (5400088.4378 - Y);
double Lambda = LongRef + (1 / nLamb) * (0.000142043 + Math.Atan(Tan1));
double RLamb = Math.Sqrt(Math.Pow((X - 150000.01256), 2) + Math.Pow((5400088.4378 - Y), 2));
double TanZDemi = Math.Pow((RLamb / KLamb), (1 / nLamb));
double Lati1 = 2 * Math.Atan(TanZDemi);
double eSin = 0;
double Mult1 = 0;
double Mult2 = 0;
double Mult = 0;
double LatiN = 0;
double Diff = 0;
double lat = 0;
double lng = 0;
do {
eSin = eLamb * Math.Sin(Lati1);
Mult1 = 1 - eSin;
Mult2 = 1 + eSin;
Mult = Math.Pow((Mult1 / Mult2), (eLamb / 2));
LatiN = (Math.PI / 2) - (2 * (Math.Atan(TanZDemi * Mult)));
Diff = LatiN - Lati1;
Lati1 = LatiN;
} while (Math.Abs(Diff) > 2.77777E-08);
lat = (LatiN * 180) / Math.PI;
lng = (Lambda * 180) / Math.PI;
return new LatLon(lat, lng);
}
I am the author of the page you mention in your post.
I don't know if you have resolved your problem but the Lambert coordinates you give are not correct. I think that you have to divide them by 1000. That gives x=151488.250 and y=170492.909 which are possible coordinates and corresponding to a street in... Brussels.
Be careful to the choice of the datum when converting to and from lat/lng values.