3

I need to solve a minimization problem with Matlab and I'm wondering which is the easiest solution. All the potential solutions that I've been thinking in require lot of programming effort.

Suppose that I have a lat/long coordinate point (A,B), what I need is to search for the nearest point to this one in a map of lat/lon coordinates.

In particular, the latitude and longitude arrays are two matrices of 2030x1354 elements (1km distance) and the idea is to find the unique indexes in those matrices that minimize the distance to the coordinates (A,B), i.e., to find the closest values to the given coordinates (A,B).

Any help would be very appreciated.

Thanks!

Rody Oldenhuis
  • 37,726
  • 7
  • 50
  • 96
cardogar
  • 359
  • 1
  • 4
  • 17
  • You have 2, 2D matrices of 1D values, right? – macduff Jul 01 '13 at 18:47
  • Just checking... do you by any chance have a third matrix that gives a value at each lat/lon, and are you trying to look up the correct value in that matrix for arbitrary lat/lon pairs? If so, then you want either interp2 or griddata, depending on the particulars of your lat/lon matrices. – Peter Jul 01 '13 at 19:08
  • Yes macduff, I have 2D matrices with 1D values. – cardogar Jul 02 '13 at 08:34
  • No Peter, I don't have any third matrix with extra values. But thanks anyway! – cardogar Jul 02 '13 at 08:34

2 Answers2

2

Let Lat and Long denote latitude and longitude matrices, then

dist2=sum(bsxfun(@minus, cat(3,A,B), cat(3,Lat,Long)).^2,3);
[I,J]=find(dist2==min(dist2(:)));

I and J contain the indices in A and B that correspond to nearest point. Note that if there are multiple answers, I and J will not be scalar values, but vectors.

Mohsen Nosratinia
  • 9,844
  • 1
  • 27
  • 52
  • Thanks for your answer! I'd need to add to my previous information that the desired (A,B) coordinates will be located along the whole globe including the poles. Since they correspond to track points of satellite orbits. According to Rody Oldenhuis' answer this approach will be not useful for that purpose. – cardogar Jul 02 '13 at 08:43
  • I already tested and it worked so fine. I will check the cases for the poles, but so far your proposed method is giving just what I needed it. Thanks. – cardogar Jul 02 '13 at 09:31
2

This is always a fun one :)

First off: Mohsen Nosratinia's answer is OK, as long as

  • you don't need to know the actual distance
  • you can guarantee with absolute certainty that you will never go near the polar regions
  • and will never go near the ±180° meridian

For a given latitude, -180° and +180° longitude are actually the same point, so simply looking at differences between angles is not sufficient. This will be more of a problem in the polar regions, since large longitude differences there will have less of an impact on the actual distance.

Spherical coordinates are very useful and practical for purposes of navigation, mapping, and that sort of thing. For spatial computations however, like the on-surface distances you are trying to compute, spherical coordinates are actually pretty cumbersome to work with.

Although it is possible to do such calculations using the angles directly, I personally don't consider it very practical: you often have to have a strong background in spherical trigonometry, and considerable experience to know its many pitfalls -- very often there are instabilities or "special points" you need to work around (the poles, for example), quadrant ambiguities you need to consider because of trig functions you've introduced, etc.

I've learned to do all this in university, but I also learned that the spherical trig approach often introduces complexity that mathematically speaking is not strictly required, in other words, the spherical trig is not the simplest representation of the underlying problem.

For example, your distance problem is pretty trivial if you convert your latitudes and longitudes to 3D Cartesian X,Y,Z coordinates, and then find the distances through the simple formula

distance (a, b) = R · arccos( a/|a| · b/|b| )

where a and b are two such Cartesian vectors on the sphere. Note that |a| = |b| = R, with R = 6371 the radius of Earth.

In MATLAB code:

% Some example coordinates (degrees are assumed)
lon = 360*rand(2030, 1354);
lat = 180*rand(2030, 1354) - 90;

% Your point of interest
P = [4, 54];

% Radius of Earth
RE = 6371;

% Convert the array of lat/lon coordinates to Cartesian vectors
% NOTE: sph2cart expects radians
% NOTE: use radius 1, so we don't have to normalize the vectors
[X,Y,Z] = sph2cart( lon*pi/180,  lat*pi/180, 1);

% Same for your point of interest    
[xP,yP,zP] = sph2cart(P(1)*pi/180, P(2)*pi/180, 1);

% The minimum distance, and the linear index where that distance was found
% NOTE: force the dot product into the interval [-1 +1]. This prevents 
% slight overshoots due to numerical artifacts
dotProd = xP*X(:) + yP*Y(:) + zP*Z(:);
[minDist, index] = min( RE*acos( min(max(-1,dotProd),1) ) );

% Convert that linear index to 2D subscripts
[ii,jj] = ind2sub(size(lon), index)

If you insist on skipping the conversion to Cartesian and use lat/lon directly, you'll have to use the Haversine formula, as outlined on this website for example, which is also the method used by distance() from the mapping toolbox.

Now, all of this is valid for the whole Earth, provided you find the smooth spherical Earth accurate enough an approximation. If you want to include the Earth's oblateness or some higher order shape model (or God forbid, distances including terrain), you need to do far more complicated stuff. But I don't think that is your goal here :)

PS - I wouldn't be surprised that if you would write everything out that I did, you'll probably re-discover the Haversine formula. I just prefer to be able to calculate something as simple as distances along the sphere from first principles alone, rather than from some black box formula you had implanted in your head sometime long ago :)

Rody Oldenhuis
  • 37,726
  • 7
  • 50
  • 96
  • Thanks for your detailed explanation. I understand what you proposed and I think that's a really good solution. However I tried to implement it and it doesn't work fine. Maybe I'm doing something wrong but I tested with your proposed example and the resulting indexes (ii,jj) did not provided coordinates close to the given P (4,54) either. Any suggestion? – cardogar Jul 02 '13 at 09:29
  • @cardogar: See my latest edit; there is indeed that slight numerical issue with the `arccos()` I forgot about :) Anyway, how are you computing the "real" minima? I get the right results over here (as verified by visually inspecting the point cloud a number of times). If you do something along the lines of `[minLatDiff,Ilat] = min(abs(P(1)-lat(:))), [minLonDiff,Ilon] = min(abs(P(2)-lon(:)))` you are fooling yourself, since indeed the lat/lon differences will probably be less, but the *indices* for those minima will be different...you're comparing apples with oranges in that case :) – Rody Oldenhuis Jul 02 '13 at 09:46
  • Great! It's fine now with the edited code. I just checked it and now I get the expected results. However, I still have better results with the Mohsen Nosratinia's approach. I did a visual checking and then I computed the real distance: distdim(distance(A,B,lat(ii,jj),lon(ii,jj)),'deg','kilometers') – cardogar Jul 02 '13 at 13:48
  • @cardogar: try his method for all longitudes equal to -179°, except for one that has +179°. Choose your point of interest at longitude 179° as well. Irrespective of the lat values, you'll pretty much always find that one point that lies at +179°; obviously, his method is OK, but not *general*. – Rody Oldenhuis Jul 02 '13 at 13:59
  • @cardogar: as for your checks -- I cannot reproduce what you say; when I try your line of code, I get the exact same indices for where the minimum distance occurs. However, I *do* always get a difference of several kilometers between the values I compute versus the distances computed with `distance()`. Not sure why this is...I'll look into it. – Rody Oldenhuis Jul 02 '13 at 14:15
  • @cardogar: scrap all that -- sloppy error on my side. I actually get *exactly* the same distances from both my method and `distance()` (save for numerical noise of order 1e-12)... – Rody Oldenhuis Jul 02 '13 at 14:33
  • When trying both methods you get almost always the same distances. But not always, e.g. in the case of my particular problem results are better for Mohsen's method. Try to iterate both methods several times, you will see that sometimes the Mohsen's model provides better results. Why? no idea. – cardogar Jul 03 '13 at 09:12
  • If you try this a couple of times you will see what I mean for i=1:15 lon=360*rand(2030,1354);lat=180*rand(2030,1354)-90;P=[4,54];[X,Y,Z]=sph2cart(lat*pi/180,lon*pi/180,1);[xP,yP,zP]=sph2cart(P(1)*pi/180,P(2)*pi/180,1);dotProd=xP*X(:)+yP*Y(:)+zP*Z(:);[~,index]=min(6371*acos(min(max(-1,dotProd),1)));[I,J]=ind2sub(size(lon),index);distdim(distance(P(1),P(2),lat(I,J),lon(I,J)),'deg','kilometers') dist2=sum(bsxfun(@minus,cat(3,P(1),P(2)),cat(3,lat,lon)).^2,3);[I,J]=find(dist2==min(dist2(:)));distdim(distance(P(1),P(2),lat(I,J),lon(I,J)),'deg','kilometers') end – cardogar Jul 03 '13 at 09:18
  • @cardogar: well, as long as you take care of all the pitfalls with his method I mentioned, there should be no problem with it. Chasing down the cause of the difference is a worthwhile, but potentially very time consuming endeavor. If it works better, then use it. – Rody Oldenhuis Jul 03 '13 at 09:21
  • @cardogar: well, that's because you have the lat/lon of `P` in the wrong order (in both your calls to `distance()` and Mohsen's method) :) When I correct that, I find that my method produces the same distances as distance(), whereas Mohsen's method produces distances that are often *wildly* off the ones found by `distance()`, plus, his minimum distance is usually *greater* than the one I find. – Rody Oldenhuis Jul 03 '13 at 10:09
  • +1 for a thorough answer. I totally missed that the points are on a sphere. – Mohsen Nosratinia Jul 03 '13 at 10:46
  • First, thank you so much for all your useful discussion and great help. Your answers are really helpful. However, I think you're confused since my lat/lon are in the right order. 4 for lat and 54 for long, and I use the same for both methods as you may see above. I will try to send you a clear example in which the distances found by your method are slightly worse than Mohsen's. – cardogar Jul 03 '13 at 14:02
  • @cardogar: Yes, I was mistaken, but only about where it goes wrong, not about *what* goes wrong. Look at `help sph2cart` and look at `help distance`. The function `distance` uses the convention lat-first, lon-second. The function `sph2cart` on the other hand uses lon-first (th), lat-second (phi). You use the first convention in both bases, which is not correct. – Rody Oldenhuis Jul 03 '13 at 14:16
  • Well, the thing is the distance computed with your method changed but I still have the same wrong behaviour. However I cannot reproduce the case without reading my lat/lon data. I'm trying to create a code that generates a lat/lon array that reproduces the error but I didn't manage so far. In my fake examples your code always shows the same or better results. It's just with my real data when I get the opposite. Still working on it. – cardogar Jul 03 '13 at 15:41
  • OK, I found one simple case to show you the problem. P=[-64.80602,161.9197] lat=[-64.7944,-64.79752,-64.80064;-64.80313,-64.80625,-64.80937;-64.81185,-64.81498,-64.8181];lon=[161.8596,161.8801,161.9005;161.8524,161.8728,161.8933;161.8451,161.8656,161.886]; Searching for the P coordinate in that dataset I get: d = 2.220563 (with your method) d = 1.088622 (Mohsen' method) By any strange reason, for that P and the rest of my case-study points your model work worse. – cardogar Jul 03 '13 at 16:05
  • @cardogar: ...I can't reproduce this. With your `P`, `lat` and `lon`, I used `RE = 6371; [X,Y,Z] = sph2cart( lon*pi/180, lat*pi/180, 1); [xP,yP,zP] = sph2cart(P(2)*pi/180, P(1)*pi/180, 1); dotProd = xP*X(:) + yP*Y(:) + zP*Z(:); dist = RE*acos( min(max(-1,dotProd),1) ); [minDist, index] = min( dist ), dist2 = sqrt((P(1)-lat).^2 + (P(2)-lon).^2 ); [minDist2, index2] = min( RE*pi/180*dist2(:))` and get `minDist=1.0881e+00` (me) `minDist2=2.2172e+00` (Mohsen) – Rody Oldenhuis Jul 03 '13 at 20:10
  • Solved. I got crazy looking for the mistake. I didn't understand why you didn't see the same issue. I was using the same values, same formulas... but different results! And the problem was related to the precision of the variables. My P values are read as single precision numbers, if you use [xP,yP,zP]=sph2cart(single(P(2)*pi/180),single(P(1)*pi/180),1); then you may see my results. But well, it's finally solved just using [xP,yP,zP]=sph2cart(double(P(2)*pi/180),double(P(1)*pi/180),1). Thank you so much for all your help and patience. I really appreciate the time you devoted in this problem. – cardogar Jul 04 '13 at 14:05
  • @cardogar: Hmm, weird...well, anyway, good find :) And you're welcome! – Rody Oldenhuis Jul 04 '13 at 14:22