views:

663

answers:

7

I need to sort a coordinate list for a rectangle counterclockwise, and make the north-east corner the first coordinate. These are geographic coordinates (i.e. Longitude, Latitude) in decimal form.1

For example, here are the 4 corners of a rectangle, starting with the north-west corner and moving clockwise:

[
  { "lat": 34.495239, "lng": -118.127747 }, # north-west
  { "lat": 34.495239, "lng": -117.147217 }, # north-east
  { "lat": 34.095174, "lng": -117.147217 }, # south-east
  { "lat": 34.095174, "lng": -118.127747 }  # south-west
]

I need to sort these counterclockwise and change the "anchor"/starting point to be north-east:

[
  { "lat": 34.495239, "lng": -117.147217 }, # north-east
  { "lat": 34.495239, "lng": -118.127747 }, # north-west
  { "lat": 34.095174, "lng": -118.127747 }, # south-west
  { "lat": 34.095174, "lng": -117.147217 }  # south-east
]

I do not know what order the list will be in initially (i.e. clockwise or counterclockwise). I do not know which corner the first coordinate in the list represents.


1This is not a true rectangle when mapped to the surface of the earth, however since I do have 2 opposing corners I am calling it a rectangle for readability. Shapes that wrap +180/-180 longitude or +90/-90 latitude are not an issue.

+1  A: 

If you take the cross-product of two vectors from a corner then the sign of the result will tell you if it's clockwise or counterclockwise.

Aaron
I need more than the direction, I need the list sorted in the order posted for further operations.
Crescent Fresh
@Crescent Fresh - Knowing the direction is the first step to sorting it properly, isn't it? If you need it in counter-clockwise order and the list as is is in clockwise order then you reverse the list and - ta da - you have it in counter-clockwise order.
Aaron
Sorry, I'm not making myself very clear: I need the list sorted starting with the *north-east* corner. Simply knowing the direction does not tell me which item in the list is north-east.
Crescent Fresh
A: 

So, you have 4 points.

You always start with the NW point.

You know that the points are sorted, just not in which direction.

It's a simple test of the first two points whether the list is clockwise or counter clockwise.

if (pt1.y != pt2.y) then direction = clockwise.

If you detect that the points are clockwise, simple reverse the last 3 points in the list.

So.

Counter clockwise points: (0,1), (0,0), (1,0), (1,1)

Clockwise points: (0,1), (1,1), (1,0), (0,0)

You can see if you reverse pts2-4 your clockwise list becomes counterclockwise.

EDIT: I had my points starting from the NE, fixt.

Will Hartung
I *don't* know if the list starts at NW. Sorry if that was unclear.
Crescent Fresh
+3  A: 

Rather than sorting, you can just "rebuild" the rectangle in any order you desire.

From the original set, collect the min and max latitude and min and max longitude. Then construct the rectangle in any order you want.

Northwest corner is max latitude and min longitude. Southwest corner is min latitude and min longitude. Etc.

Adrian McCarthy
"Rebuilding" the list from two opposing corners is equivalent to sorting the entire list I suppose, albeit a bit unorthodox.
Crescent Fresh
+1  A: 

Associate an angle with each point (relative to an interior point), and then moving around is trivial.

To calculate the angle, find a point in the middle of the shape, for example, (average_lat, average_lng) will be in the center. Then, atan2(lng - average_lng, lat - average_lat) will be the angle of that point.

tom10
+2  A: 

solution seems pretty straightforward:

>>> import math
>>> mlat = sum(x['lat'] for x in l) / len(l)
>>> mlng = sum(x['lng'] for x in l) / len(l)
>>> def algo(x):
    return (math.atan2(x['lat'] - mlat, x['lng'] - mlng) + 2 * math.pi) % 2*math.pi

>>> l.sort(key=algo)

basically, algo normalises the input into the [0, 2pi] space and it would be naturally sorted "counter-clockwise".

SilentGhost
Nice. This looks similar to what @tom10 was trying to convey (without the mapping to `[0, 2pi]`), is that right?
Crescent Fresh
yeah, haven't seen his reply before I've implemented my code. the only difference indeed is obtaining this order. and I've tested my code too :)
SilentGhost
@SilentGhost: you say in your answer the list will be sorted clockwise, but doesn't this actually sort it counterclockwise as I asked for?
Crescent Fresh
@Crescent Fresh: sure, it sorts counter-clockwise, it was just a typo.
SilentGhost
+3  A: 

Assuming that your "rectangles" are always parallel to the equator and meridians (that's what your example implies, but it's not stated explicitely), i.e. you have just two pairs of different lat and lng values: (lat0, lat1) and (lng0, lng1).

You get following 4 corners:

NE: (lat = max(lat0, lat1), lng = max(lng0, lng1))
NW: (lat = max(lat0, lat1), lng = min(lng0, lng1))
SW: (lat = min(lat0, lat1), lng = min(lng0, lng1))
SE: (lat = min(lat0, lat1), lng = max(lng0, lng1))

(this is not supposed to be python code)

Curd
+1 because this explanation is clearer than mine and makes the assumption explicit
Adrian McCarthy
@Curd: the footnote at the bottom of the question was trying to convey what you stated (didn't want to get into Lines of Lat/Lng in the question). Anyway, finding the opposing corners `(lat0, lat1)` and `(lng0, lng1)` is *itself* a search for minimum and maximum points (yielding SW and NE corners respectively), correct?
Crescent Fresh
@Crescent Fresh: in your question above I don't understand what you mean by "opposing corners...". With "(lat0, lat1)" I just meant the list of your two latitude values. The order doesn't matter.
Curd
@Curd: in my scenario I have 4 coordinates, not 2. Finding the 2 that you speak of is an effort to find 2 out of the 4 that are opposite to each other.
Crescent Fresh
+1  A: 

It is easy. First, we sort the coordinates so we know in which order we have them, then we simply pick them out:

Sort them first by lat then by lng, biggest first. Then we swap the last two:

L = [
  { "lat": 34.495239, "lng": -118.127747 }, # north-west
  { "lat": 34.495239, "lng": -117.147217 }, # north-east
  { "lat": 34.095174, "lng": -117.147217 }, # south-east
  { "lat": 34.095174, "lng": -118.127747 }  # south-west
]


L = sorted(L, key=lambda k: (-k["lat"], -k["lng"]))

L[-2], L[-1] = L[-1], L[-2]
import pprint
pprint.pprint(L)

output

[{'lat': 34.495238999999998, 'lng': -117.147217},
 {'lat': 34.495238999999998, 'lng': -118.127747},
 {'lat': 34.095174, 'lng': -118.127747},
 {'lat': 34.095174, 'lng': -117.147217}]

(The minuses in the key function are there so that bigger values sort before smaller values. By sorting we put north before south, then east before west; to get the desired order we simply swap the two last (southern) values.)

kaizer.se
I wonder if this works crossing the equator or prime meridian (where lat/lng flips from `-` to `+`)...
Crescent Fresh
Should work fine actually. Would have liked it better without the swapping though ;) +1
Crescent Fresh
it is not possible without swapping: we need to sort north before south before we know which points are the southernmost, which we then sort reversely wrt east-west.
kaizer.se