Getting latitude and longitude of the Sun on a world map with PyEphem - astronomy

I'm trying to determine the latitude and longitude of say the Sun, the Moon and Mars. I need the result relative to the Earth's equator and the Prime Meridian in order to produce a result similar to this map.
I believe that's also what the author of this question wanted, however the answer there doesn't add up for me (comparing with values from the first link).
Expected result, obtained from the page linked to earlier:
On Thursday, 1 January 2015, 00:00:00 UTC the Sun is at its zenith at Latitude: 23° 02' South, Longitude: 179° 29' West
>>> import ephem; from math import degrees
>>> b = ephem.Sun(epoch='date'); b.compute('2015/1/1 00:00:00')
>>> print("{},{}".format(degrees(b.dec), degrees(b.ra)))
-23.040580418272267,281.12827017399906
So the latitude/declination seems about right, but no 180° wraparound will fix that right ascension, probably because it starts at the Vernal Equinox.
I have also unsuccessfully tried to use an observer at 0,0.
Can this be done using PyEphem, Skyfield or astropy? It seems odd that artificial satellites in PyEphem have the convenient sublat and sublong attributes, but it's so hard for celestial bodies.

I finally figured it out. Sort of. Actually I just ported the relevant bits of libastro to Python. Note that this code runs against the current git version of Skyfield (be6c7296).
Here goes (gist version):
#!/usr/bin/env python3
from datetime import datetime, timezone
from math import atan, atan2, degrees, floor, pi, radians, sin, sqrt
from skyfield.api import earth, JulianDate, now, sun
def earth_latlon(x, y, z, time):
"""
For an object at the given XYZ coordinates relative to the center of
the Earth at the given datetime, returns the latitude and longitude
as it would appear on a world map.
Units for XYZ don't matter.
"""
julian_date = JulianDate(utc=time).tt
# see https://en.wikipedia.org/wiki/Julian_date#Variants
# libastro calls this "mjd", but the "Modified Julian Date" is
# something entirely different
dublin_julian_date = julian_date - 2415020
# the following block closely mirrors libastro, so don't blame me
# if you have no clue what the variables mean or what the magic
# numbers are because I don't either
sidereal_solar = 1.0027379093
sid_day = floor(dublin_julian_date)
t = (sid_day - 0.5) / 36525
sid_reference = (6.6460656 + (2400.051262 * t) + (0.00002581 * (t**2))) / 24
sid_reference -= floor(sid_reference)
lon = 2 * pi * ((dublin_julian_date - sid_day) *
sidereal_solar + sid_reference) - atan2(y, x)
lon = lon % (2 * pi)
lon -= pi
lat = atan(z / sqrt(x**2 + y**2))
return degrees(lat), degrees(-lon)
if __name__ == '__main__':
print("2015-01-01 00:00:00:")
time = datetime(2015, 1, 1, tzinfo=timezone.utc)
x, y, z = earth(JulianDate(utc=time)).observe(sun).apparent().position.au
print(earth_latlon(x, y, z, time))
print("now:")
time = datetime.now(timezone.utc)
x, y, z = earth(JulianDate(utc=time)).observe(sun).apparent().position.au
print(earth_latlon(x, y, z, time))
Output:
2015-01-01 00:00:00:
(-23.05923949080624, -179.2173856294249)
now:
(-8.384551051991025, -47.12917634395421)
As you can see, the values for 2015-01-01 00:00:00 match the reference values from the question. Not precisely, but it's good enough for me. For all I know, my values might be better.
Due to my ignorance about the undocumented magic numbers used in the libastro code, I cannot make this work for bodies other than Earth.
#BrandonRhodes: Let me know if you're interested in having this functionality in Skyfield, then I'll try to throw together a pull request.

Related

Retrieving JSON data from the osmbuildings.org data site

I am trying to retrieve building data from osmbuildings.org
The osmbuildings documentation (https://osmbuildings.org/documentation/data/) features an example URL (https://data.osmbuildings.org/0.2/anonymous/tile/15/17605/10743.json) that returns the JSON file for buildings that are located in Berlin at roughly 52°32'30.6"N 13°25'23.2"E coordinates.
Can I use this same site to get a JSON file for buildings in a different location? For example, I believe the X,Y coordinates using XYZ tile coordinates would be 25835 and 5221 for a location in Singapore for a zoom of 15 and the corresponding url would be https://data.osmbuildings.org/0.2/anonymous/tile/15/25835/5221.json.
However, when I put this into the web browser, I don't get the json file like in the Berlin case. Can someone please explain?
I also tried different zoom values at the same location:
https://data.osmbuildings.org/0.2/anonymous/tile/16/51672/8779.json
And at a slightly different location with fewer buildings:
https://data.osmbuildings.org/0.2/anonymous/tile/16/51666/9459.json
If you do not get the JSON file, you are probably getting an empty reply. This is likely because your specified tile does not contain any building data.
This can have various reasons, e.g. there are regions where no building data is available. In your case however, it seems to me that your conversion into X,Y coordinates is not correct for Singapore.
In my following example, I use the coordinates for the Singapore Marina Bay (https://www.openstreetmap.org/#map=15/1.2742/103.8617).
I converted the lon/lat to tile numbers using the formula from the OSM wiki (https://wiki.openstreetmap.org/wiki/Slippy_map_tilenames). In pseudo code:
n = 2 ^ zoom
xtile = n * ((lon_deg + 180) / 360)
ytile = n * (1 - (ln(tan(lat_rad) + sec(lat_rad)) / π)) / 2
= n * (1 - (ln(tan(lat_rad) + (1 / cos(lat_rad))) / π)) / 2
Filling in the according lon = 103.8617 and lat = 1.2742 and zoom = 15, you get:
n = 2^15
= 32768
xtile = 32768 * ((103.8617 + 180) / 360)
= 25837.722737778
ytile = 32768 * (1 - (ln(tan(1.2742) + (1 / cos(1.2742))) / π)) / 2
= 16268.009923134
Disregarding the decimals, we get X = 25837 and Y = 16268. Making this into a link (according to https://osmbuildings.org/documentation/data/), we get
https://data.osmbuildings.org/0.2/anonymous/tile/15/25837/16268.json
which does return JSON data for some 40 buildings.
This also worked fine for me on other zoom levels. E.g. zooming in on the famous "Marina Bay Sands" (https://www.openstreetmap.org/#map=17/1.28338/103.86148) and calculating the link according to the pseudo-code above, I get:
https://data.osmbuildings.org/0.2/anonymous/tile/17/103350.810851556/65068.69652388.json
which returns a JSON that contains named buildings such as "Marina Bay Sands Tower 1", "Marina Bay Sands Tower 2", etc., which shows it worked as intended.

Wrapping around 360 degrees with SQL query

I am fairly new with using SQL and I would like to learn how to efficiently filter a data set of ecliptic coordinates (longitude, latitude) for regions of the sky. The data set is in the southern ecliptic hemisphere (from -90 to 0 and 0 to 360) as shown below (simulated in Python with some simple numpy arange commands).
I have figured out how to filter a specific patch of the sky in python with the following code:
x = (eclong + 360 - center) % 360
ind = x > 180
x[ind] = x[ind] - 360 #converts from 0 to 360 to -180 to +180
x = -x #reverses scale where east direction is on left
To allow data points to wrap around the South pole, I also have an additional required condition:
filtered_eclong = (x < 12/np.cos(eclat *np.pi/180)) & (-12/np.cos(eclat *np.pi/180) < x & eclat < 0)
12 degrees is how wide the patch of sky that I want to grab is and center is a variable for the central ecliptic longitude of my desired patch of sky. The last line in the query is a correction to include objects near the southern ecliptic pole. (Note: eclat = ecliptic latitude , eclong = ecliptic longitude, both in degrees) As an example of the results I would like to get, the patch of sky I want to filter is centered on a ecliptic longitude of 315.8 degrees, seen below:
I've saved the entire simulated southern hemisphere as a csv file and uploaded the eclong and eclat values as columns in a sql database. I would like to be able to recreate the same query I performed in my Python code above. This is the closest attempt I have:
select * from coords
where eclat < -6
and mod(abs(eclong-315.8+360),360)-360 < 180
and mod(abs(eclong-315.8),360) < 12/cos(radians(eclat));
which produces this result where the filtered Python result is shown in red and the filtered sql result is shown in blue:
As shown, my query doesn't include things that wrap around from 360 degrees to 0 degrees and I'm not quite sure how to include those missing points.
I think using some combination of THEN, ELSE or CASE statements would help:
select * from Catalog
where eclat < -6
CASE
WHEN mod(abs(eclong-315.8),360) < 12/cos(radians(eclat)) > 0 THEN 'I'm not sure what to do...'
WHEN mod(abs(eclong-315.8),360) < 12/cos(radians(eclat)) < 360 THEN 'I'm not sure what to do...'
into myDB.filename
Any SQL gurus out there?
Though I'm not familiar with ecliptic coordinates, it sounds like you're trying to reconcile the lack of negative numbers based on your title. If that's indeed what you're going for, who cares that there aren't any negative values—let's pretend there are!
To generalize, we'll need to ensure we can safely calculate ±180 degrees from any starting longitude. With this use case, starting from 11 degrees, we could rearrange the unhelpful values by subtracting 360 from anything greater than 11 + 180 and working with the result as a helper. This way, 359 degrees would instead show up as -1, and then you can do normal math. If your starting longitude is above 180, we'd need to get the lowest values above 360. In SQL, here's a (maybe inefficient, but straightforward) way I'd create a helper column:
CASE
WHEN center < 180 THEN CASE
WHEN eclong > center + 180 THEN eclong - 360
ELSE eclong
END
WHEN center > 180 THEN CASE
WHEN eclong < center - 180 THEN eclong + 360
ELSE eclong
END
ELSE eclong
END as friendly_eclong

What is initial bearing and final bearing

I am trying to calculate bearing between two lat/lon points as given in this link. I see that the bearing we get initially using the below equation is initial bearing.
public static double GetBearing(double latitude1, double longitude1, double latitude2, double longitude2)
{
var lat1 = ToRadians(latitude1);
var lat2 = ToRadians(latitude2);
var longdiff = ToRadians(longitude1 - longitude2);
var X = Math.Cos(lat2) * Math.Sin(longdiff);
var Y = Math.Cos(lat1) * Math.Sin(lat2) - Math.Sin(lat1) * Math.Cos(lat2) * Math.Cos(longdiff);
var bearing =ToDegrees(Math.Atan2(X, Y));
return (bearing+360)%360;
}
It is given that
For final bearing, simply take the initial bearing from the end point to the start point and reverse it (using θ = (θ+180) % 360).
I am confused about the difference between initial bearing and final bearing.
What is this initial and final bearing and which bearing should we take as the final answer for bearing between two points.
The bearing is the angle between direction along the shortest path to destination and direction to North. The reason we have initial and final one is that we live on sphere, so the shortest path is geodesic line. It is a straight line on globe, bit if you draw it on flat map - it will be a curve.
There are two ways to think about it. Thinking on flat map: as you travel from A to B, this curve changes direction slightly, so the angle between this line and North changes, i.e. bearing changes.
Or you can think on sphere, and then think about triangle A - B - North Pole. The bearing is angle between between AB and appropriate meridian. Initial bearing is angle between AB and meridian crossing A. Final one is angle between AB and meridian crossing B. They are different.
The single "final answer" bearing only makes sense when distance between A and B is short. Then the curvature of Earth does not matter much, and the initial and final bearings are very close to each other, so depending on precision needed one can talk about single bearing.
FYI: bearing and many related computations are implemented in the R package geosphere
The bearing function returns the initial bearing, but you can invert the coordinates to get the final bearing.
library(geosphere)
bearing(cbind(0,0),cbind(20,20))
#[1] 43.4035
finalb <- bearing(cbind(20,20),cbind(0,0))
(finalb + 180) %% 360
#[1] 46.9656
(these results should be more precise than the ones you get with algorithm you refer to)
def bearing(lat1, lon1, lat2, lon2):
# Convert latitude and longitude to radians
lat1 = math.radians(lat1)
lon1 = math.radians(lon1)
lat2 = math.radians(lat2)
lon2 = math.radians(lon2)
y = math.sin(lon2-lon1) * math.cos(lat2)
x = math.cos(lat1)*math.sin(lat2) - math.sin(lat1)*math.cos(lat2)*math.cos(lon2-lon1)
initial_bearing = math.degrees(math.atan2(y, x))
final_bearing = (initial_bearing + 180) % 360 if initial_bearing < 180 else (initial_bearing - 180) % 360
return initial_bearing, final_bearing

Function for line between 2 GPS coordinates

I'm trying to find a function lng = f(lat) that would help me draw a line between 2 given GPS coordinates, (lat1, lng1) and (lat2, lng2).
I've tried the traditional Cartesian formula y=mx+b where m=(y2-y1)/(x2-x1), but GPS coordinates don't seem to behave that way.
What would be a formula/algorithm that could help me achieve my goal.
PS: I'm using Google Maps API but let's keep this implementation agnostic if possible.
UPDATE: My implementation was wrong and it seems the algorithm is actually working as stated by some of the answers. My bad :(
What you want to do should actually work. Keep in mind however that if north is on top, the horizontal (x) axis is the LONGITUDE and the vertical (y) axis is the LATITUDE (I think you might have confused this).
If you parametrize the line as lat = func(long) you will run into trouble with vertical lines (i.e. those going exactly north south) as the latitude varies while the longitude is fixed.
Therefore I'd rather use another parametrization:
long(alpha) = long_1 + alpha * (long_2 - long_1)
lat(alpha) = lat_1 + alpha * (lat_2 - lat_1)
and vary alpha from 0 to 1.
This will not exactly coincide with a great circle (shortest path on a sphere) but the smaller the region you are looking at, the less noticeable the difference will be (as others posters here pointed out).
Here is a distance formula I use that may help. This is using javascript.
function Distance(lat1, lat2, lon1, lon2) {
var R = 6371; // km
var dLat = toRad(lat2 - lat1);
var dLon = toRad(lon2 - lon1);
var a = Math.sin(dLat / 2) * Math.sin(dLat / 2) + Math.cos(toRad(lat1)) * Math.cos(toRad(lat2)) * Math.sin(dLon / 2) * Math.sin(dLon / 2);
var c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a));
var d = R * c * 0.621371;
var r = Math.round(d * 100) / 100;
return r;
}
For short distances, where the earth curvature doesn't make a significant difference, it works fine to draw a line using regular two-dimensional geometry.
For longer distances the shortest way between two lines does not project as a straight line on a map, but as a curve. (For example, the shortest way from Sweden to Alaska would be straight over the noth pole, not past Canada and Iceland.) You would have to use three-dimensional geometry to draw a line on a surface of a sphere, then project that onto the map in the same way the earth surface is projected on the map.
Is your goal to find this equation or to actually draw a line?
If the latter, since you're using the Maps API, specify geodesic: true and draw it with a Polyline:
http://code.google.com/apis/maps/documentation/javascript/reference.html#Polyline

What is the unit in returned by MySQL GLength method?

I want to get the length in meters between 2 points in the surface of the Earth. But the GLength method returns an unexpected value
http://dev.mysql.com/doc/refman/5.0/en/geometry-property-functions.html#function_glength
SELECT GLength(GeomFromText(
'LINESTRING(-67.8246 -10.0073,-67.8236 -10.0082)', 4326))
actual result
0.00134536240471071
expected value in meters:
147
I guess it's about 3 years too late for the OP, but I happened across this question while researching a similar topic, so here's my tuppence worth.
According to www.movable-type.co.uk/scripts/latlong.html there are 3 ways of calculating distance across the Earth's surface, these being, from easiest to most complex (and thus from least to most accurate): Equirectangular Approximation, Spherical Law of Cosines and the Haversine Formula. The site also provides JavaScript. This is the function for Equirectangular approximation:
function _lineDistanceC(lat1, lon1, lat2, lon2) {
var R = 6371; // km
lat1 = lat1.toRad();
lon1 = lon1.toRad();
lat2 = lat2.toRad();
lon2 = lon2.toRad();
var x = (lon2-lon1) * Math.cos((lat1+lat2)/2);
var y = (lat2-lat1);
return Math.sqrt(x*x + y*y) * R;
}
This is my attempt at a SQL equivalent:
drop function if exists lineDistanceC;
delimiter //
CREATE FUNCTION lineDistanceC (la1 DOUBLE, lo1 DOUBLE, la2 DOUBLE, lo2 DOUBLE) RETURNS DOUBLE
BEGIN
SET #r = 6371;
SET #lat1 = RADIANS(la1);
SET #lon1 = RADIANS(lo1);
SET #lat2 = RADIANS(la2);
SET #lon2 = RADIANS(lo2);
SET #x = (#lon2-#lon1) * COS((#lat1+#lat2)/2);
SET #y = (#lat2 - #lat1);
RETURN (SQRT((#x*#x) + (#y*#y)) * #r);
END
//
delimiter ;
I have no idea how accurate this formula is, but the site seems very credible and definitely worth a visit for more detail. Using the coordinates in the question, the results (in kilometres) from the above JavaScript and SQL are 0.14834420231840376 and 0.1483442023182845, which makes them the same to a fraction of a millimetre, but not, curiously, the 147 metres that the OP was expecting. Enjoy.
1 degree (your input unit) ~= 69 miles. So, if you multiply your results by 69 miles and then convert to meters, you get roughly 149 meters. Not exactly what you expected but pretty close. Of course I don't think that accurately reproduces the spherical nature of the globe, but maybe I'm wrong.
I haven't found a source, but I was playing with this method and I concluded that the result is given in the same unit used in the WKT.
And the distance is calculated in planar coordinates. The SRID is not taken into account.
In a degree of latitude, there is about 69 miles. Looking at longitude, there is about 49 miles per degree. When i am trying to calculate distance between NYC and LONDON right number is 49.
Select Round(GLength(GeomFromText('LineString(40.756054 -73.986951,51.5001524 -0.1262362)')))*49;