Background
I want to add a model manager function that filters a queryset based on the proximity to coordinates. I found this blog posting with code that is doing precisely what I want.
Code
The snippet below seems to make use of geopy functions that have since been removed. It coarsely narrows down the queryset by limiting the range of latitude and longitude.
# Prune down the set of all locations to something we can quickly check precisely
rough_distance = geopy.distance.arc_degrees(arcminutes=geopy.distance.nm(miles=distance)) * 2
queryset = queryset.filter(
latitude__range=(latitude - rough_distance, latitude + rough_distance),
longitude__range=(longitude - rough_distance, longitude + rough_distance)
)
Problem
Since some of the used geopy functions have been removed/moved, I'm trying to rewrite this stanza. However, I do not understand the calculations---barely passed geometry and my research has confused me more than actually helped me.
Can anyone help? I would greatly appreciate it.
In case anybody else is looking at this now, since I tried to use geopy and just hit up against it, the modern equivalent of the rough_distance snippet above is:
import geopy
rough_distance = geopy.units.degrees(arcminutes=geopy.units.nautical(miles=1))
It looks like distance in miles is being converted to nautical miles, which are each equal to a minute of arc, which are 1/60th of an arc degree each. That value is then doubled, and then added and subtracted from a given latitude and longitude. These four values can be used to form a bounding box around the coordinates.
You can lookup any needed conversion factors on Wikipedia. There's also a relevant article there titled Horizontal position representation which discusses pros and cons of alternatives to longitude and latitude positioning which avoid some of their complexities. In other words, about the considerations involved with replacing latitude and longitude with another horizontal position representation in calculations.
The Earth is not a sphere, only approximately so. If you need a more accurate calculation, use pyproj. Then you can calculate the location based a reference ellipsoid (e.g. WGS84).
martineau's answer is right on, in terms of what the snippet actually does, but it is important to note that 1 minute of arc represents very different distances depending on location. At the equator, the query covers the least axis aligned bounding box enclosing a circle of diameter distance, but off the equator, the bounding box does not completely contain that circle.
This code from the blog is sloppy:
def near(self, latitude=None, longitude=None, distance=None):
if not (latitude and longitude and distance):
return []
If latitude == 0 (equator) or longitude == 0 (Greenwich meridian), it returns immediately. Should be if latitude is None or longitude is None .......
#TokenMacGuy's answer is an improvement, but:
(a) The whole idea of the "bounding box" is to avoid an SQL or similar query calculating a distance to all points that otherwise satisfy the query. With appropriate indexes, the query will execute much faster. It does this at the cost of leaving the client to (1) calculate the coordinates of the bounding box (2) calculate and check the precise distance for each result returned by the query.
If step 2 is omitted, you get errors, even at the equator. For example "find all pizza shops in a 5-mile radius" means you get answers up to 7.07 miles (that's sqrt(5*2 + 5*2)) away in the corners of the box.
Note that the code that you show seems to be arbitrarily doubling the radius. This would mean you get points 14.1 miles away.
(b) As #TokenMacGuy said, away from the equator, it gets worse. The bounding box so calculated does not include all points that you are interested in -- unless of course you are overkilling by doubling the radius.
(c) If the circle of interest includes either the North or South Pole, the calculation is horribly inexact, and needs adjusting. If the circle of interest is crossed by the 180-degree meridian (i.e. the International Date Line without the zigzags), the results are a nonsense; you need to detect this case and apply a 2-part query (one part for each side of the meridian).
For solutions for problems (b) and (c), see this article.
If the coordinates on the earth are known, you can use geopy to get a good estimate of the decimal degrees to miles (or any distance units) scale at that point:
SCALE_VAL = 0.1
lat_scale_point = (cur_lat + SCALE_VAL, cur_long)
long_scale_point = (cur_lat, cur_long + SCALE_VAL)
cur_point = (cur_lat, cur_long)
lat_point_miles = distance.distance(cur_point, lat_scale_point).miles
long_point_miles = distance.distance(cur_point, long_scale_point).miles
# Assumes that 'radius_miles` is the range around the point you want to look for
lat_rough_distance = (radius_miles / lat_point_miles) * SCALE_VAL
long_rough_distance = (radius_miles / long_point_miles) * SCALE_VAL
Some caveats:
Special-case handling for the the scale points is needed around polls or prime meridean
Depending on how large or small you want your radius to be, you could pick a more appropriate SCALE_VAL
Related
I am trying to come up with a calculation that creates a column that comes up with a number that shows density for that specific location in a 5 mile radius, i.e if there are many other locations near it or not. I would like to compare these locations with themselves to achieve this.
I'm not familiar with the math needed to achieve this and have tried to find a solution for some time now.
Ok, i'm not super clear with what your problem may be but i will try to give you my approach.
Let's first assume that the area you are querying for points is small enough to be considered flat hence the geo coordinates of your area will basically be cartesian coordinates.
You choose your circle's center as (x,y) and then you have to find which of your points are within radius of your cirle: in cartesian coordinates being inside of a circle means that the distance of the points from your center are smaller than a given radius. You save those points in your choice of data structure and the density will probably be the number of your points divided by the area of the circle.
I hope i understood the problem correctyl!
I was asked to find 5 equidistant points to a semi circle in python. So this is the code I wrote as that's what made sense to me mathematically
import math
r=float(input())/2
op=[[round(r*math.cos(i*math.pi/180),2),round(r*math.sin(i*math.pi/180),2)] for i in range(36,181,36)]
print(op)
But when I executed, my code failed the test cases as the answer the professor gave was based on the below code.
import math
r=float(input())/2
op=[[round(r*math.cos(i*math.pi/4),2),round(r*math.sin(i*math.pi/4),2)] for i in range(5)]
print(op)
What I can't seem to understand is why did he take range of 5 and how does i*pi/4 works?
Is the way I took my range(36,181,36) wrong? but it seems correct mathematically.
Below is the entire question for reference.(I haven't written the area finding part as I know how to code that its just this part of finding the coordinates that confuses me.)
A mobile phone network tower A covers the circular area which has the
diameter of D meters. Another mobile phone network tower B covers the
square shaped area with a side of D meters. The coverage area of
network tower B is overlapped on the network coverage area of tower A
in such a way that one side of square is perfectly fit with the
diameter of the circle through the direction of θ between 0 to π
radians. Write a Python function for returning the area of the shape
of the overlapped network coverage area. Also, the function should
return the five equally spaced co-ordinates (x, y) of the boundary of
the overlapped circle. Use for loop and nested list for the
calculation of co-ordinates. The output should be limited to two
decimal values.
Why pi/4...radian math? because we know that our semicircle is defined by a theta angle of 0-180 degrees or 0-pi. Since we need 5 points....and we start from 0....python counts 0,1,2,3,4. Therefore at MAX angle where i = 4 the calculated angle MUST be pi...therefore pi/4. Can be easily substituted for degree math if you like. math.cos and math.sin use radian math hence the use of pi/4 instead of 180/4.
Also your range is incorrect...as it does not satisfy the condition of 0-pi or 0-180 degrees
I was struggling with using the drawmapscale function at matplotlib basemap. I couldn't understand what the syntax is extactly mean. So far I have understood the following?
e.g. map.drawmapscale(80.625, 5.75, ???, ???, 100)
As I understood, above function generate mapscale at longitude 80.625 and latitude 5.75. It should represent 100 km. But how do you understand the other two parameters? I played with some random numbers, but results are not good. I have searched on the web no satisfactory answer was found. Any help is appreciated.
Looking at the documentation
drawmapscale(lon, lat, lon0, lat0, length, **kwargs)
Draw a map scale at lon,lat of length length representing distance in the map projection coordinates at lon0,lat0.
From that one would assume that lon0,lat0 need to be the coordinates of the place in the map where 100km are to be measured.
As a start one may choose lon0 == lon and lat0 == lat. This is of course the less erroneous, the smaller the map. Whether this gives good results would also depend on the projection in use. One may also choose to use the coordinates of the middle of the map, since they would be closest to the viewer's expectation.
EDIT: Just found out that I need to convert latitude, longitude and elevation of a location on earth to J2000 coordinates and nothing to do with ra/dec or the moon. Sorry for this. Your answers did give me a lot of insights. Please see the edited question below.
Question: how do i convert latitude, longitude and elevation to J2000 coordinates (XYZ). Is there a conversion present in ephem? I checked the docs but I couldnt find something I need (or mightve overlooked something due to my lack of knowledge in this field). Thanks
***************** OLD (Disregard) ******************
I have the moon position in Right Ascension (RA) and Declination (Dec) and I want to convert them into X Y Z coordinates. Is there a built-in PyEphem function for this? Also, what is the math behind it? Thanks.
EDIT: I am using the J2000 coordinate system (which is equatorial i think, this is my first time working with astronomy). I have the distance to moon available. The ra/dec values are already in J2000 (equatorial) coordinates.
X points North
Y points West
Z points towards the sky
Best answer:
It has just come to my attention that, in June 2011, the Naval Observatory released a Python interface to the powerful NOVAS reference software with which the highest-precision astronomical computations are performed:
http://aa.usno.navy.mil/software/novas/novas_py/novaspy_intro.php
With this library you can get the answer you are seeking, at far higher precision than PyEphem has ever offered:
from novas import compat as novas
jd_tt = novas.julian_date(2012, 9, 8, 12.00)
delta_t = 66.603 # from http://maia.usno.navy.mil/ser7/deltat.preds
lat = 42.3583 # positive is north
lon = -71.0603 # negative is west
observer = novas.make_observer_on_surface(lat, lon, 0, 0, 0)
print novas.geo_posvel(jd_tt, delta_t, observer)
On my machine this gives the answer:
((-3.5081406460494928e-06, 3.135277713623258e-05, 2.858239912567112e-05),
(-0.00019753847060164693, -2.2329942271278055e-05, 2.4885824275734915e-07))
Try it yourself and see if this gives you the kind of results that you need!
Newer answer:
It appears that the answer is “no” — PyEphem, to my surprise, gives no easy way to get the answer to the question "where, in x, y, z coordinates, is (say) Boston at time t ?”
This is a surprise because “libastro”, the library behind PyEphem, of course has to compute this internally in order to figure out where other objects are relative to an observer. It seems to do so in two places. In parallax.c it defines ta_par() which talks only about angles on the outside, but on its inside you can see that it temporarily computes the x, y, z of the observer. You can even see the important constant 298.257 hidden inside there, which measures how flat the earth is, since it is not a perfect sphere.
The other place is in earthsat.c which looks like a completely different code base from the rest of “libastro”, and so it duplicates some of the logic. Its EarthFlat constant of 298.25 is a bit less precise, but is doing the same job. And its function, GetSitPosition(), actually exposes x,y,z coordinates instead of keeping them hidden. But it is declared static so there is no way to call in to this useful function from outside!
So for the moment, PyEphem gives you no way to compute your x,y,z directly. But it does provide one important piece of information: the current sidereal time, which you will (I think) be able to use to figure how far around the earth Boston (or wherever) has traveled by time t, which will be important in figuring out your coordinates.
I will see if I can work up a quick solution in Python that combines the hour angle from PyEphem with some explicit trigonometry to get you an answer. But, for the moment, no: PyEphem does not expose this information directly, sadly enough; I will put it on the list of things for a future version!
Older answer, from when the question was about the x,y,z position of the Moon:
PyEphem does not, alas, have built-in functions for converting from the polar coordinates used in amateur astronomy to the x/y/z coordinates which will let you map out how objects are distributed in space around the Earth. But the conversion is easy to do yourself:
import ephem
import math
m = ephem.Mars('2012/8/1')
print m.ra, m.dec
x = math.cos(m.dec) * math.cos(m.ra)
y = math.cos(m.dec) * math.sin(m.ra)
z = math.sin(m.dec)
print x, y, z
print 'sanity check: vector length =', math.sqrt(x*x + y*y + z*z)
The output of this script is:
12:58:51.20 -6:24:05.6
-0.961178016954 -0.252399543786 -0.111495695074
sanity check: vector length = 1.0
The position of Mars for the random date that I used here are quite reasonable values: an RA that is almost one hour more than halfway around the great circle (since 12h would be exactly halfway), and a declination that pushes the position a bit south. Thus the x, y, and z that we get out: the z is a slightly negative number since -6° is indeed south of the equator, and x and y are both negative since going 13h around a 24h circle puts you down in the negative/negative quadrant of a normal unit circle.
Note that although J2000 has a north and south — so that we can truthfully say that the slightly negative z is a southward direction — it does not have an east and west, since the earth turning below it is constantly swinging east and west in all directions. Instead, RA measures from “the first point of Ares” which is the direction in which the sun lies during the spring Equinox. So x and y are not east or west; they are coordinates pointing out into the solar system on a fixed axis defined by the direction that the Earth sits in every Spring.
This x y z vector I have created is a “unit vector” — a little vector that has the magnitude 1.0, as I verified in the script to make sure I had the formulae correct. If you were computing x y and z coordinates for objects whose distance from the earth you knew, then you could get a real vector — whose magnitude were distances, instead of fractions of 1 — by multiplying each of the three x y and z by the distance to the object.
Does that help you out? From your description — and your question about east and west — I could not tell if you wanted RA and dec turned into x y z or whether you are actually wanting the azimuth and altitude converted (but the math is the same either way). That would look something like:
x = math.cos(m.alt) * math.cos(m.az)
y = math.cos(m.alt) * math.sin(m.az)
z = math.sin(m.alt)
What are you trying to accomplish with these coordinates? That could help us make sure that we are giving them to you in a useful format.
I suggest you look at the PyEphem documentation for coordinate conversion.
Basically, PyEphem only deals with three coordinate systems - equatorial, ecliptic, and galactic - each defined by two angles and an epoch (adjustment offset for polar progression).
Depending on what your coordinate scheme looks like, you should be able to use trigonometry to convert to it if you also have the object's distance.
Edit: your "X-Y-Z" coordinates seem to be lefthanded ecliptic coordinates.
from ephem import Equatorial, Ecliptic, degree
def convert_equatorial_to_XYZ(ra, dec, dist=1.0, epoch='2000'):
"""
Given
ra right ascension (in hours)
dec declination (in degrees)
dist distance (optional, defaults to 1.0)
epoch epoch (optional, assumes J2000)
Return
degrees North, degrees West, distance
"""
eq = Equatorial(ra, dec, epoch=epoch)
ec = Ecliptic(eq)
return ec.lat/degree, 360.0 - ec.lon/degree, dist
I have two dimensional discrete spatial data. I would like to make an approximation of the spatial boundaries of this data so that I can produce a plot with another dataset on top of it.
Ideally, this would be an ordered set of (x,y) points that matplotlib can plot with the plt.Polygon() patch.
My initial attempt is very inelegant: I place a fine grid over the data, and where data is found in a cell, a square matplotlib patch is created of that cell. The resolution of the boundary thus depends on the sampling frequency of the grid. Here is an example, where the grey region are the cells containing data, black where no data exists.
1st attempt http://astro.dur.ac.uk/~dmurphy/data_limits.png
OK, problem solved - why am I still here? Well.... I'd like a more "elegant" solution, or at least one that is faster (ie. I don't want to get on with "real" work, I'd like to have some fun with this!). The best way I can think of is a ray-tracing approach - eg:
from xmin to xmax, at y=ymin, check if data boundary crossed in intervals dx
y=ymin+dy, do 1
do 1-2, but now sample in y
An alternative is defining a centre, and sampling in r-theta space - ie radial spokes in dtheta increments.
Both would produce a set of (x,y) points, but then how do I order/link neighbouring points them to create the boundary?
A nearest neighbour approach is not appropriate as, for example (to borrow from Geography), an isthmus (think of Panama connecting N&S America) could then close off and isolate regions. This also might not deal very well with the holes seen in the data, which I would like to represent as a different plt.Polygon.
The solution perhaps comes from solving an area maximisation problem. For a set of points defining the data limits, what is the maximum contiguous area contained within those points To form the enclosed area, what are the neighbouring points for the nth point? How will the holes be treated in this scheme - is this erring into topology now?
Apologies, much of this is me thinking out loud. I'd be grateful for some hints, suggestions or solutions. I suspect this is an oft-studied problem with many solution techniques, but I'm looking for something simple to code and quick to run... I guess everyone is, really!
~~~~~~~~~~~~~~~~~~~~~~~~~
OK, here's attempt #2 using Mark's idea of convex hulls:
alt text http://astro.dur.ac.uk/~dmurphy/data_limitsv2.png
For this I used qconvex from the qhull package, getting it to return the extreme vertices. For those interested:
cat [data] | qconvex Fx > out
The sampling of the perimeter seems quite low, and although I haven't played much with the settings, I'm not convinced I can improve the fidelity.
I think what you are looking for is the Convex Hull of the data That will give a set of points that if connected will mean that all your points are on or inside the connected points
I may have mixed something, but what's the motivation for simply not determining the maximum and minimum x and y level? Unless you have an enormous amount of data you could simply iterate through your points determining minimum and maximum levels fairly quickly.
This isn't the most efficient example, but if your data set is small this won't be particularly slow:
import random
data = [(random.randint(-100, 100), random.randint(-100, 100)) for i in range(1000)]
x_min = min([point[0] for point in data])
x_max = max([point[0] for point in data])
y_min = min([point[1] for point in data])
y_max = max([point[1] for point in data])