RvEYoda Posted August 20, 2009 Share Posted August 20, 2009 (edited) The lockon LUA export can provide me a LAT and LONG value of any object. However I need to calculate the distance and bearing from point A to point B, and here I run into a problem. I am using what is described here :http://www.movable-type.co.uk/scripts/latlong.html But when calculating the bearing, I get an error of something like 3-5 degrees. I have checked and rechecked both the numbers and algorithms, rewritten them, in multiple languages, but still get these errors. So I must assume that the lockon world is not curved in a manner that allows me to use these calculations. I really need to find some way of calculating bearing between two lockon Lat/Long coordinates with maximum 1 degree error ------- When I calculate distance between A->B I have an error of about 1%, but in this case I only require about 2-3% precision so that is not a problem. However, I really need some way of calculating bearings better. If anyone knows an algorithm that allows me to calculate the bearing from point A->B with the exported coordinates from lockon, that would be great. //Yoda Edited August 20, 2009 by =RvE=Yoda S = SPARSE(m,n) abbreviates SPARSE([],[],[],m,n,0). This generates the ultimate sparse matrix, an m-by-n all zero matrix. - Matlab help on 'sparse' Link to comment Share on other sites More sharing options...
Case Posted August 20, 2009 Share Posted August 20, 2009 (edited) So I must assume that the lockon world is not curved in a manner that allows me to use these calculations. The formula you are using is for a perfect sphere, which the Earth is not, it is flattened with f=1/298.257. However, it appears LockOn does not take this into account, and spherical coordinates in LockOn can easily be translated into points on a plane through a Gnomic projection; http://forums.eagle.ru/showpost.php?p=53829&postcount=7 You can try a simple tan phi=y/x to check if those bearings match the in game values. Edited August 20, 2009 by Case There are only 10 types of people in the world: Those who understand binary, and those who don't. Link to comment Share on other sites More sharing options...
104th_Crunch Posted August 20, 2009 Share Posted August 20, 2009 http://forums.eagle.ru/showthread.php?t=34937&highlight=Curvature So the current terrain in Lock On is just flat it seems. Link to comment Share on other sites More sharing options...
RvEYoda Posted August 20, 2009 Author Share Posted August 20, 2009 Thanks Case, I will try it later. Im sure it will give good range values, but im worried about bearings, ill see what happens S = SPARSE(m,n) abbreviates SPARSE([],[],[],m,n,0). This generates the ultimate sparse matrix, an m-by-n all zero matrix. - Matlab help on 'sparse' Link to comment Share on other sites More sharing options...
Case Posted August 20, 2009 Share Posted August 20, 2009 I think these are the algorithms that are used by LockOn, so I would not be surprised if these give exactly the same values as in the game. As far as I can see, all this piece of code does is define a Gnomic projection in an elaborate way. However, for the purpose of the game, such a projection is more than suitable. There are only 10 types of people in the world: Those who understand binary, and those who don't. Link to comment Share on other sites More sharing options...
RvEYoda Posted August 20, 2009 Author Share Posted August 20, 2009 I just realized my old reverse/empirically engineered algorithm for these things work almost identically with what is seen in your link Case. Ill see if I can compare them more in detail my old algo has about 0.5 deg precision (but does only work in the black sea area) S = SPARSE(m,n) abbreviates SPARSE([],[],[],m,n,0). This generates the ultimate sparse matrix, an m-by-n all zero matrix. - Matlab help on 'sparse' Link to comment Share on other sites More sharing options...
CHola Posted August 20, 2009 Share Posted August 20, 2009 Not sure about this specific point, but I've noticed LockOn has one large error in terms of astronomical accuracy. The thing is when you set to take off from Kheresones (f.e.) on the 010 runway, at let's say 06.30 hours, the Sun is rising at about 50° to starboard bow! Since Kheresones is well above Tropic of Cancer, this can't be. I've hoped this will be corrected in some patch, but apparently still isn't... Cheers, CHola Link to comment Share on other sites More sharing options...
asparagin Posted August 21, 2009 Share Posted August 21, 2009 question: curved terrain or not, what does it have to do with the bearing? Spoiler AMD Ryzen 9 5900X, MSI MEG X570 UNIFY (AM4, AMD X570, ATX), Noctua NH-DH14, EVGA GeForce RTX 3070 Ti XC3 ULTRA, Seasonic Focus PX (850W), Kingston HyperX 240GB, Samsung 970 EVO Plus (1000GB, M.2 2280), 32GB G.Skill Trident Z Neo DDR4-3600 DIMM CL16, Cooler Master 932 HAF, Samsung Odyssey G5; 34", Win 10 X64 Pro, Track IR, TM Warthog, TM MFDs, Saitek Pro Flight Rudders Link to comment Share on other sites More sharing options...
Vekkinho Posted August 21, 2009 Share Posted August 21, 2009 Not sure about this specific point, but I've noticed LockOn has one large error in terms of astronomical accuracy. The thing is when you set to take off from Kheresones (f.e.) on the 010 runway, at let's say 06.30 hours, the Sun is rising at about 50° to starboard bow! Since Kheresones is well above Tropic of Cancer, this can't be. I've hoped this will be corrected in some patch, but apparently still isn't... Which date is this?! Did you notice you can change dates in LO? Depends on a time of year, so if there's sunrise at 6:00 during on the June 21st and the sun's rising at ENE or 60° on your compass, check it on the Dec 21st and wait til 09:00! 1 [sIGPIC][/sIGPIC] Link to comment Share on other sites More sharing options...
CHola Posted August 21, 2009 Share Posted August 21, 2009 Well, as far as I know, during the Summer solstice on 21st of June (the longest possible day for Northern Hemisphere), the Sun is perpendicular to the Tropic of Cancer, which is at N23°30'. Kheresones is at N~44°30'. The conclusion: Above the Tropic of Cancer, the Sun must raise at an angle larger than 90° starboard, if we face directly to the North. Places that experience Sun rising at lower offset than 90°, must be south of Tropic of Cancer. Cheers, CHola Link to comment Share on other sites More sharing options...
DarkWanderer Posted August 21, 2009 Share Posted August 21, 2009 The lockon LUA export can provide me a LAT and LONG value of any object. However I need to calculate the distance and bearing from point A to point B, and here I run into a problem. I am using what is described here :http://www.movable-type.co.uk/scripts/latlong.html But when calculating the bearing, I get an error of something like 3-5 degrees. I have checked and rechecked both the numbers and algorithms, rewritten them, in multiple languages, but still get these errors. So I must assume that the lockon world is not curved in a manner that allows me to use these calculations. I really need to find some way of calculating bearing between two lockon Lat/Long coordinates with maximum 1 degree error Check the type of point connectiong curve you're using. You have your result as a "big circle" line, and LO uses loxodrome (because its map is in Mercator projection). That is the issue. 1 You want the best? Here i am... Link to comment Share on other sites More sharing options...
Vekkinho Posted August 21, 2009 Share Posted August 21, 2009 Well, as far as I know, during the Summer solstice on 21st of June (the longest possible day for Northern Hemisphere), the Sun is perpendicular to the Tropic of Cancer, which is at N23°30'. Kheresones is at N~44°30'. The conclusion: Above the Tropic of Cancer, the Sun must raise at an angle larger than 90° starboard, if we face directly to the North. Places that experience Sun rising at lower offset than 90°, must be south of Tropic of Cancer. Actually, I never did some detailed study of Sun/Moon behavior in FC, I know that by changing the date your mission takes place you affect sunrise/sundown timings but I didn't check where the sun meets horizon on June 21st and when on December 21st. Now I'll do that, as soon as I get home... [sIGPIC][/sIGPIC] Link to comment Share on other sites More sharing options...
Panzertard Posted August 21, 2009 Share Posted August 21, 2009 Yoda, have you checked some of the LUA "map" files? In DCS it's there quite a few functions that seem to rely on DCS's system (LoFC too I assume) ... MapViewTest.lua is one. Maybe you can find some functions being relevant for you in your project. :) The mind is like a parachute. It only works when it's open | The important thing is not to stop questioning Link to comment Share on other sites More sharing options...
Mugatu Posted August 22, 2009 Share Posted August 22, 2009 Yep I believe it's a flat earth problem too Yoda and when using WGS84 don't forget everything is calculated in relation to a reference point (earth radius at that point). Link to comment Share on other sites More sharing options...
asparagin Posted August 22, 2009 Share Posted August 22, 2009 can someone spare 2 minutes of his precious time and explain why is the bearing in a curved earth different than the one in a flat earth? Spoiler AMD Ryzen 9 5900X, MSI MEG X570 UNIFY (AM4, AMD X570, ATX), Noctua NH-DH14, EVGA GeForce RTX 3070 Ti XC3 ULTRA, Seasonic Focus PX (850W), Kingston HyperX 240GB, Samsung 970 EVO Plus (1000GB, M.2 2280), 32GB G.Skill Trident Z Neo DDR4-3600 DIMM CL16, Cooler Master 932 HAF, Samsung Odyssey G5; 34", Win 10 X64 Pro, Track IR, TM Warthog, TM MFDs, Saitek Pro Flight Rudders Link to comment Share on other sites More sharing options...
Vekkinho Posted August 22, 2009 Share Posted August 22, 2009 Earth is spherical surface so maps you see in your high school atlas that stretch the Earth's surface into a plain sheet tend to distort the layout and shape of oceans and continents. This also affects the geometry and distances. Lock On's map is made in such plane frame shape and you can see the "mistakes" it has by using your Mission Editor. Try a simple test. Select a ruler tool and draw a line anywhere in the map with 0° heading (N), it should be parallel to your screen edges, right? Now, if you take surface curvature into account this would be only possible in the dead center of the map, on the parts to the left (W) and to the right (E) this 0° line would be slightly canted. This is not a mistake with LO beacuse Earth curvature isn't modelled in the game so charts present surface as it is and are correct. But try comparing LOFC map with Google Earth and draw a line from Sukhumi AB to Khersones AB and check distance and bearing! Now do the same in GE and compare the difference. 1 [sIGPIC][/sIGPIC] Link to comment Share on other sites More sharing options...
asparagin Posted August 22, 2009 Share Posted August 22, 2009 oh i thought you ment the relative bearing... then it is clear. Spoiler AMD Ryzen 9 5900X, MSI MEG X570 UNIFY (AM4, AMD X570, ATX), Noctua NH-DH14, EVGA GeForce RTX 3070 Ti XC3 ULTRA, Seasonic Focus PX (850W), Kingston HyperX 240GB, Samsung 970 EVO Plus (1000GB, M.2 2280), 32GB G.Skill Trident Z Neo DDR4-3600 DIMM CL16, Cooler Master 932 HAF, Samsung Odyssey G5; 34", Win 10 X64 Pro, Track IR, TM Warthog, TM MFDs, Saitek Pro Flight Rudders Link to comment Share on other sites More sharing options...
RvEYoda Posted August 22, 2009 Author Share Posted August 22, 2009 Crap. Well I guess I have to stick with an algorithm specific for Lo's Black sea then. I was hoping of doing something completely general :P Thanks for your answers everyone S = SPARSE(m,n) abbreviates SPARSE([],[],[],m,n,0). This generates the ultimate sparse matrix, an m-by-n all zero matrix. - Matlab help on 'sparse' Link to comment Share on other sites More sharing options...
RvEYoda Posted August 23, 2009 Author Share Posted August 23, 2009 (edited) The formula you are using is for a perfect sphere, which the Earth is not, it is flattened with f=1/298.257. However, it appears LockOn does not take this into account, and spherical coordinates in LockOn can easily be translated into points on a plane through a Gnomic projection; http://forums.eagle.ru/showpost.php?p=53829&postcount=7 You can try a simple tan phi=y/x to check if those bearings match the in game values. Tried it, just making phi=atan(y/x) gives variable error in bearing up to +-7 degrees at the edges of the map. I will need something more accurate. I think the conversion algorithm here for x & y are accurate, however, since bearings do not correspond to actual x/y (draw bearing lines in map editor and you will se why). I will need something more accurate that takes the curvature of the lockon Lat/long lines mapped on the flat lockon world surface into account. For the moment I will stick with my own weird algorithms for making the bearing calculation. But I cannot mathematically motivate/prove my own stuff. The way I made my own stuff is ; I just tried some random 1st-3rd degree polynomials of "myLatLongAlt" and "hisLatLongAlt" and varied the coefficients until it gave good results in all directions tested in positions around map. That said, the linked algorithm can be used with good results for getting the range Edited August 23, 2009 by =RvE=Yoda S = SPARSE(m,n) abbreviates SPARSE([],[],[],m,n,0). This generates the ultimate sparse matrix, an m-by-n all zero matrix. - Matlab help on 'sparse' Link to comment Share on other sites More sharing options...
Moa Posted November 20, 2009 Share Posted November 20, 2009 I've implemented this for a moving map I'm making. Not only have I implemented the original algorithm to get gnomonic coordinates from spherical I've also derived and implemented the inverse operation (to derive you need to use the trigonometric identity sin^2 + cos^2 = 1, I'm glad that the PhD and $60k wasn't entirely wasted). Here is the Java code (unit tests with test data and a utility class will follow) which belongs in a class called MapProjection.java. It has JavaDoc that corrects some misleading names in the original C++ implementation (where 'Grad' was used which implies angular units of gradians, when the units actually appear to be degrees): /** * Utility class for doing projection in the LockOn map coordinate system. * LockOn Flaming Clifss 1.12b uses a Gnomonic Projection to obtain * gnomonic planar coordinates (x,z) from spherical coordinates: * * For more information see: * http://forums.eagle.ru/showpost.php?p=53829&postcount=7 * http://forums.eagle.ru/showthread.php?t=44427 * http://en.wikipedia.org/wiki/Gnomonic_projection * * @author Mike "Moa" Reid */ public class MapProjection { public MapProjection() { } final static float zeroX = 5000000.0f; // Real coordinates beginning final static float zeroZ = 6600000.0f; final static float centerX = 11465000.0f - zeroX; // Circle center final static float centerZ = 6500000.0f - zeroZ; final static float pn40x24_X = 4468608.57f - zeroX; // point 40dgN : 24dgE final static float pn40x24_Z = 5730893.72f - zeroZ; final static float pn48x24_X = 5357858.31f - zeroX; // point 48dgN : 24dgE final static float pn48x24_Z = 5828649.53f - zeroZ; final static float pn40x42_X = 4468608.57f - zeroX; // point 40dgN : 42dgE final static float pn40x42_Z = 7269106.20f - zeroZ; final static float pn48x42_X = 5357858.31f - zeroX; // точка 48dgN : 42dgE final static float pn48x42_Z = 7171350.00f - zeroZ; // distances from the circle center to 48dgN and 40dgN final static double lenNorth = Math.sqrt((pn48x24_X-centerX)*(pn48x24_X-centerX) + (pn48x24_Z-centerZ)*(pn48x24_Z-centerZ)); final static double lenSouth = Math.sqrt((pn40x24_X-centerX)*(pn40x24_X-centerX) + (pn40x24_Z-centerZ)*(pn40x24_Z-centerZ)); final static double lenN_S = lenSouth - lenNorth; final static double RealAngleMaxLongitude = Math.atan (((double)pn40x24_Z - centerZ)/(pn40x24_X - centerX)) * 180.0f / Math.PI; // Map bounds. Degrees! final static float EndWest = 24.0f; final static float EndEast = 42.0f; final static float EndNorth = 48.0f; final static float EndSouth = 40.0f; final static float MiddleLongitude = (EndWest + EndEast) / 2.0f; final static float ToLengthN_S = (float)((EndNorth - EndSouth) / lenN_S); final static double ToAngleW_E = (MiddleLongitude - EndWest) / RealAngleMaxLongitude; final static double degreesPerRadian = 180.0 / Math.PI; /** * Obtain the x,z LockOn map coordinates for a given latitude and longitude. * * @param latitude the input longitude (in degrees). * @param longitude the input latitude (in degrees). * * @return an array with the (x,z) coordinates in meters in (x in element 0, z in element 1). */ public static double[] getGnomonicCoordinates(double latitude, double longitude) { double angleRadians = (longitude - MiddleLongitude) / (ToAngleW_E * degreesPerRadian); double realLen = lenSouth - (latitude - EndSouth) / ToLengthN_S; double x = centerX - realLen * Math.cos(angleRadians); double z = centerZ + realLen * Math.sin(angleRadians); double[] destination = new double[2]; destination[0] = x; destination[1] = z; return destination; } /** * Obtain the longitude,latitude LockOn map coordinates for a given x,z gnomonic * projection coordinates. * * @param x the gnomonic x coordinate (in meters). * @param z the gnomonic y coordinate (in meters). * * @return an array with the (longitude,latitde) coordinates in degrees in (longitude in element 0, latitude in element 1). */ public static double[] getSphericalCoordinates(double x, double z) { double realLength = Math.sqrt((centerX - x)*(centerX - x) + (z - centerZ)*(z - centerZ)); double angleRadians = Math.acos((centerX - x) / realLength); double longitude = angleRadians * ToAngleW_E * degreesPerRadian + MiddleLongitude; double latitude = EndSouth - ToLengthN_S * (realLength - lenSouth); double[] destination = new double[2]; destination[0] = longitude; destination[1] = latitude; return destination; } /** * Determines the range and bearing from one location to another. * * @param latitude1 the longitude of the first location (in units of degrees). * @param longitude1 the latitude of the first location (in units of degrees). * * @param latitude2 the longitude of the second location (in units of degrees). * @param longitude2 the latitude of the second location (in units of degrees). * * @return an array containing the range in meters and bearing in degrees from * the first location to the second location. */ public static double[] getRangeAndBearing(double latitude1, double longitude1, double latitude2, double longitude2) { double[] coords1 = getGnomonicCoordinates(latitude1, longitude1); double[] coords2 = getGnomonicCoordinates(latitude2, longitude2); double deltaX = coords2[0] - coords1[0]; double deltaY = coords2[1] - coords1[1]; double rangeMeters = Math.sqrt(deltaX*deltaX + deltaY*deltaY); double bearingDegrees = Math.toDegrees(Math.atan2(deltaY, deltaX)); final double degreesInCircle = 360.0; while (bearingDegrees < 0.0) { bearingDegrees += degreesInCircle; } while (bearingDegrees >= degreesInCircle) { bearingDegrees -= degreesInCircle; } double[] rangeAndBearing = new double[2]; rangeAndBearing[0] = rangeMeters; rangeAndBearing[1] = bearingDegrees; return rangeAndBearing; } } Link to comment Share on other sites More sharing options...
Moa Posted November 20, 2009 Share Posted November 20, 2009 The JUnit test class MapProjectionTest.java (with test data useful for people writing the algorithm in languages other than Java): import java.util.ArrayList; import java.util.List; import org.junit.After; import org.junit.AfterClass; import org.junit.Before; import org.junit.BeforeClass; import org.junit.Test; import static org.junit.Assert.*; /** * Tests the MapProjection class. * * @author Mike "Moa" Reid */ public class MapProjectionTest { public MapProjectionTest() { } @BeforeClass public static void setUpClass() throws Exception { } @AfterClass public static void tearDownClass() throws Exception { } @Before public void setUp() { } @After public void tearDown() { } /** * Test of getGnomonicCoordinates method, of class MapProjection. */ @Test public void testGetGnomonicCoordinates() { double distanceTolerance = 35.0; // tolerance in meters, due to source data rounded to nearest arcsecond. List<LatLong> data = getLatLongTestData(); for (LatLong datum : data) { double latitudeDegrees = datum.getLatitude(); double longitudeDegrees = datum.getLongitude(); double[] result = MapProjection.getGnomonicCoordinates(latitudeDegrees, longitudeDegrees); double x = result[0]; double z = result[1]; double expectedX = datum.getX(); double expectedZ = datum.getZ(); if (Math.abs(expectedX - x) > distanceTolerance) { fail("Expected gnomonic x coordinate bigger than expected for location '" + datum.getDescription() + "', expected = " + expectedX + ", projected = " + x + ", difference = " + (x - expectedX)); } if (Math.abs(expectedZ - z) > distanceTolerance) { fail("Expected gnomonic z coordinate bigger than expected for location '" + datum.getDescription() + "', expected = " + expectedZ + ", projected = " + z + ", difference = " + (z - expectedZ)); } } } /** * Test of getSphericalCoordinates method, of class MapProjection. */ @Test public void testGetSphericalCoordinates() { double distanceTolerance = 1.0; // tolerance in meters. double angularTolerance = 0.2; // tolerance in bearing (n degrees). List<LatLong> data = getLatLongTestData(); for (LatLong datum : data) { double x = datum.getX(); double z = datum.getZ(); double[] result = MapProjection.getSphericalCoordinates(x, z); double longitude = result[0]; double latitude = result[1]; double expectedLongitude = datum.getLongitude(); double expectedLatitude = datum.getLatitude(); if (Math.abs(expectedLongitude - longitude) > distanceTolerance) { fail("Expected gnomonic longitude coordinate for location '" + datum.getDescription() + "', expected = " + expectedLongitude + ", projected = " + longitude + ", difference = " + (longitude - expectedLongitude)); } if (Math.abs(expectedLatitude - latitude) > angularTolerance) { fail("Expected gnomonic latitude coordinate bigger than expected for location '" + datum.getDescription() + "', expected = " + expectedLatitude + ", projected = " + latitude + ", difference = " + (latitude - expectedLatitude)); } } } /** * Test of getRangeAndBearing method, of class MapProjection. */ @Test public void testGetRangeAndBearing() { LatLong sukhumiLatLong = new LatLong("Sukhumi airbase", LatLong.toDegrees(42, 51, 39), LatLong.toDegrees(41, 8, 25), 0.0, 0.0); LatLong krasnodarCentralLatLong = new LatLong("Krasnodar central airbase", LatLong.toDegrees(45, 5, 1), LatLong.toDegrees(38, 57, 01), 0.0, 0.0); double latitude1 = sukhumiLatLong.getLatitude(); double longitude1 = sukhumiLatLong.getLongitude(); double latitude2 = krasnodarCentralLatLong.getLatitude(); double longitude2 = krasnodarCentralLatLong.getLongitude(); // Calculate the range and bearing from Sukhumi airbase to Krasnodar Central airbase. double expectedBearing = 320.0; // In degrees. double expectedRange = 304365; // In meters. double distanceTolerance = 35.0; // In meters. double bearingTolerance = 0.2; // In degrees. double[] result = MapProjection.getRangeAndBearing(latitude1, longitude1, latitude2, longitude2); assertNotNull(result); double range = result[0]; double bearing = result[1]; assertEquals(expectedRange, range, distanceTolerance); assertEquals(expectedBearing, bearing, bearingTolerance); } /** * Get LatLong objects that can be used for testing. * * @return an array of LatLong objects that are useful for testing. */ public List<LatLong> getLatLongTestData() { List<LatLong> data = new ArrayList<LatLong>(); // This test data was obtained in LockOn: Flaming Cliffs 1.12b using // the F11 view to move between airbases and then using the ALT+y key // to switch between polar and gnomonic coordinates (shown in the // bottom right of the screen). // Ukrainian (Crimean) locations. data.add(new LatLong("Khersones", LatLong.toDegrees(44, 34, 34), LatLong.toDegrees(33, 24, 6), -61729.2, -68106.5)); data.add(new LatLong("Saki", LatLong.toDegrees(45, 4, 52), LatLong.toDegrees(33, 35, 58), -5163.5, -52801.5)); data.add(new LatLong("Simferopol", LatLong.toDegrees(45, 2, 34), LatLong.toDegrees(33, 58, 55), -9154.2, -22654.1)); data.add(new LatLong("Razdolnoye", LatLong.toDegrees(45, 44, 4), LatLong.toDegrees(33, 31, 30), 67870.8, -59135.1)); data.add(new LatLong("Dzhankoy", LatLong.toDegrees(45, 41, 23), LatLong.toDegrees(34, 25, 15), 63676.9, 10661.5)); data.add(new LatLong("Kirovskoye", LatLong.toDegrees(45, 9, 38), LatLong.toDegrees(35, 11, 27), 5855.0, 72211.5)); data.add(new LatLong("Kerch-Bagerovo", LatLong.toDegrees(45, 23, 13), LatLong.toDegrees(36, 15, 35), 33940.6, 155181.1)); data.add(new LatLong("Belbek", LatLong.toDegrees(44, 41, 5), LatLong.toDegrees(33, 34, 9), -49501.4, -54885.1)); data.add(new LatLong("Krasnogvardeyskoye", LatLong.toDegrees(45, 34, 10), LatLong.toDegrees(34, 17, 35), 50080.5, 925.9)); data.add(new LatLong("Oktyabrskoye", LatLong.toDegrees(45, 19, 11), LatLong.toDegrees(34, 5, 49), 21939.8, -14006.7)); data.add(new LatLong("Gvardeyskoye", LatLong.toDegrees(45, 6, 19), LatLong.toDegrees(33, 58, 25), -2157.5, -23384.6)); // Southern Russia & Georgia. data.add(new LatLong("Anapa", LatLong.toDegrees(45, 0, 19), LatLong.toDegrees(37, 20, 59), -4737.0, 242694.9)); data.add(new LatLong("Krasnodar-Center", LatLong.toDegrees(45, 4, 34), LatLong.toDegrees(38, 56, 50), 11046.8, 367780.8)); data.add(new LatLong("Novorossiysk", LatLong.toDegrees(44, 40, 6), LatLong.toDegrees(37, 47, 11), -40432.2, 279253.6)); data.add(new LatLong("Krymsk", LatLong.toDegrees(44, 57, 40), LatLong.toDegrees(38, 0, 27), -6715.0, 294749.5)); data.add(new LatLong("Maykop", LatLong.toDegrees(44, 40, 27), LatLong.toDegrees(40, 3, 4), -26871.4, 458253.9)); data.add(new LatLong("Gelendzhik", LatLong.toDegrees(44, 34, 13), LatLong.toDegrees(38, 1, 3), -50287.0, 298195.6)); data.add(new LatLong("Sochi-Adler", LatLong.toDegrees(43, 26, 50), LatLong.toDegrees(39, 57, 2), -164274.3, 461912.7)); data.add(new LatLong("Krasnodar-Pashkovskiy", LatLong.toDegrees(45, 2, 12), LatLong.toDegrees(39, 10, 53), 8014.1, 386493.8)); data.add(new LatLong("Sukhumi", LatLong.toDegrees(42, 52, 0), LatLong.toDegrees(41, 8, 30), -220014.7, 564311.1)); data.add(new LatLong("Gudautu", LatLong.toDegrees(43, 6, 52), LatLong.toDegrees(40, 34, 36), -196846.7, 515809.8)); return data; } } Link to comment Share on other sites More sharing options...
Moa Posted November 20, 2009 Share Posted November 20, 2009 Last but not least, the utility class LatLong.java which represents a location by spherical (lat, long) or gnomonic (x,y) coordinates. This class is not called 'Location' since I use that name elsewhere. Hope the code helps anyone implementing map projections/coordinate conversions in Lockon. S! Moa /** * Represents a location specified by a latitude and a longitude. * * @author Mike "Moa" Reid. */ public class LatLong { /** * A description of the location (eg. "Novorossiysk"). */ private String description; /** * The latitude of the location (in degrees). Latitude is measured in a * positive direction North of the Equator. */ private double latitude; /** * The east longitude of the location (in degrees). East longitude is * measured in a positive direction East of the Prime Meridian (Greenwich). */ private double longitude; /** * The gnomonic projection x coordinate (corresponds to longitude) */ private double x; /** * The gnomonic projection z coordinate (corresponds to latitude) */ private double z; /** * Creates a location with no associated description, latitude or longitude. */ public LatLong() { } /** * Creates a location with an associated description, latitude and longitude. * * @param description the description of the location (eg. "Sochi-Adler"). * @param latitude the latitude of the location (in degrees). * @param longitude the longitude of the location (in degrees). * @param x the gnomonic projection x coordinate (corresponds to longitude). * @param z the gnomonic projection y coordinate (corresponds to latitude). */ public LatLong(String description, double latitude, double longitude, double x, double z) { this.description = description; this.latitude = latitude; this.longitude = longitude; this.x = x; this.z = z; } /** * Converts an angle specified as integer degrees, minutes and seconds * into a decimal representation of degrees. * * @param degrees the arc-degrees of the angle (from 0 to 359). * @param minutes the arc-minutes of the angle (from 0 to 59). * @param seconds the arc-seconds of the angle (from 0 to 59). * * @return the angle in a decimal representation. * * @throws IllegalArgumentException if degrees is outside the range 0 to 359. * @throws IllegalArgumentException if minutes is outside the range 0 to 59. * @throws IllegalArgumentException if seconds is outside the range 0 to 59. */ public static double toDegrees(int degrees, int minutes, int seconds) { if ((degrees < 0) || (degrees > 359)) { throw new IllegalArgumentException("Cannot convert from degree components to decimal degrees as the degrees value given was in valid, the value given was " + degrees); } if ((minutes < 0) || (minutes > 59)) { throw new IllegalArgumentException("Cannot convert from degree components to decimal degrees as the minutes value given was in valid, the value given was " + minutes); } if ((seconds < 0) || (seconds > 59)) { throw new IllegalArgumentException("Cannot convert from degree components to decimal degrees as the seconds value given was in valid, the value given was " + seconds); } final double arcsecondsPerDegree = 3600.0; final double arcminutesPerDegree = 60.0; double value = seconds / arcsecondsPerDegree + minutes / arcminutesPerDegree + degrees; return value; } /** * Converts an angle in degrees to a string showing the components in HTML. * * @param degrees the angle to convert into an HTML representation. * * @return a String representing the angle that is suitable for display in HTML. */ public static String toDegreeComponentsHtml(double degrees) { while (degrees < 0.0) { degrees += 360.0; } int degree = (int) Math.floor(degrees); int minutes = (int) Math.floor(degrees * 60 % 60); int seconds = (int) Math.floor(degrees * 3600 % 60); String html = String.format("%02d", degree) + "° " + String.format("%02d", minutes) + "' " + String.format("%02d", seconds) + """; return html; } /** * A description of the location (eg. "Novorossiysk"). * @return the description */ public String getDescription() { return description; } /** * A description of the location (eg. "Novorossiysk"). * @param description the description to set */ public void setDescription(String description) { this.description = description; } /** * The latitude of the location (in degrees). Latitude is measured in a * positive direction North of the Equator. * @return the latitude */ public double getLatitude() { return latitude; } /** * The latitude of the location (in degrees). Latitude is measured in a * positive direction North of the Equator. * @param latitude the latitude to set */ public void setLatitude(double latitude) { this.latitude = latitude; } /** * The east longitude of the location (in degrees). East longitude is * measured in a positive direction East of the Prime Meridian (Greenwich). * @return the longitude */ public double getLongitude() { return longitude; } /** * The east longitude of the location (in degrees). East longitude is * measured in a positive direction East of the Prime Meridian (Greenwich). * @param longitude the longitude to set */ public void setLongitude(double longitude) { this.longitude = longitude; } /** * The gnomonic projection x coordinate (corresponds to longitude) * @return the x */ public double getX() { return x; } /** * The gnomonic projection x coordinate (corresponds to longitude) * @param x the x to set */ public void setX(double x) { this.x = x; } /** * The gnomonic projection z coordinate (corresponds to latitude) * @return the z */ public double getZ() { return z; } /** * The gnomonic projection z coordinate (corresponds to latitude) * @param z the z to set */ public void setZ(double z) { this.z = z; } } Link to comment Share on other sites More sharing options...
RvEYoda Posted November 20, 2009 Author Share Posted November 20, 2009 (edited) Thank you Moa, will check it out once im home on sunday evening. Thank you! :) Edited November 20, 2009 by =RvE=Yoda S = SPARSE(m,n) abbreviates SPARSE([],[],[],m,n,0). This generates the ultimate sparse matrix, an m-by-n all zero matrix. - Matlab help on 'sparse' Link to comment Share on other sites More sharing options...
RvEYoda Posted November 20, 2009 Author Share Posted November 20, 2009 (edited) I've implemented this for a moving map I'm making. Not only have I implemented the original algorithm to get gnomonic coordinates from spherical I've also derived and implemented the inverse operation (to derive you need to use the trigonometric identity sin^2 + cos^2 = 1, I'm glad that the PhD and $60k wasn't entirely wasted). Here is the Java code (unit tests with test data and a utility class will follow) which belongs in a class called MapProjection.java. It has JavaDoc that corrects some misleading names in the original C++ implementation (where 'Grad' was used which implies angular units of gradians, when the units actually appear to be degrees): /** * Utility class for doing projection in the LockOn map coordinate system. * LockOn Flaming Clifss 1.12b uses a Gnomonic Projection to obtain * gnomonic planar coordinates (x,z) from spherical coordinates: * * For more information see: * http://forums.eagle.ru/showpost.php?p=53829&postcount=7 * http://forums.eagle.ru/showthread.php?t=44427 * http://en.wikipedia.org/wiki/Gnomonic_projection * * @author Mike "Moa" Reid */ public class MapProjection { public MapProjection() { } final static float zeroX = 5000000.0f; // Real coordinates beginning final static float zeroZ = 6600000.0f; final static float centerX = 11465000.0f - zeroX; // Circle center final static float centerZ = 6500000.0f - zeroZ; final static float pn40x24_X = 4468608.57f - zeroX; // point 40dgN : 24dgE final static float pn40x24_Z = 5730893.72f - zeroZ; final static float pn48x24_X = 5357858.31f - zeroX; // point 48dgN : 24dgE final static float pn48x24_Z = 5828649.53f - zeroZ; final static float pn40x42_X = 4468608.57f - zeroX; // point 40dgN : 42dgE final static float pn40x42_Z = 7269106.20f - zeroZ; final static float pn48x42_X = 5357858.31f - zeroX; // точка 48dgN : 42dgE final static float pn48x42_Z = 7171350.00f - zeroZ; // distances from the circle center to 48dgN and 40dgN final static double lenNorth = Math.sqrt((pn48x24_X-centerX)*(pn48x24_X-centerX) + (pn48x24_Z-centerZ)*(pn48x24_Z-centerZ)); final static double lenSouth = Math.sqrt((pn40x24_X-centerX)*(pn40x24_X-centerX) + (pn40x24_Z-centerZ)*(pn40x24_Z-centerZ)); final static double lenN_S = lenSouth - lenNorth; final static double RealAngleMaxLongitude = Math.atan (((double)pn40x24_Z - centerZ)/(pn40x24_X - centerX)) * 180.0f / Math.PI; // Map bounds. Degrees! final static float EndWest = 24.0f; final static float EndEast = 42.0f; final static float EndNorth = 48.0f; final static float EndSouth = 40.0f; final static float MiddleLongitude = (EndWest + EndEast) / 2.0f; final static float ToLengthN_S = (float)((EndNorth - EndSouth) / lenN_S); final static double ToAngleW_E = (MiddleLongitude - EndWest) / RealAngleMaxLongitude; final static double degreesPerRadian = 180.0 / Math.PI; /** * Obtain the x,z LockOn map coordinates for a given latitude and longitude. * * @param latitude the input longitude (in degrees). * @param longitude the input latitude (in degrees). * * @return an array with the (x,z) coordinates in meters in (x in element 0, z in element 1). */ public static double[] getGnomonicCoordinates(double latitude, double longitude) { double angleRadians = (longitude - MiddleLongitude) / (ToAngleW_E * degreesPerRadian); double realLen = lenSouth - (latitude - EndSouth) / ToLengthN_S; double x = centerX - realLen * Math.cos(angleRadians); double z = centerZ + realLen * Math.sin(angleRadians); double[] destination = new double[2]; destination[0] = x; destination[1] = z; return destination; } /** * Obtain the longitude,latitude LockOn map coordinates for a given x,z gnomonic * projection coordinates. * * @param x the gnomonic x coordinate (in meters). * @param z the gnomonic y coordinate (in meters). * * @return an array with the (longitude,latitde) coordinates in degrees in (longitude in element 0, latitude in element 1). */ public static double[] getSphericalCoordinates(double x, double z) { double realLength = Math.sqrt((centerX - x)*(centerX - x) + (z - centerZ)*(z - centerZ)); double angleRadians = Math.acos((centerX - x) / realLength); double longitude = angleRadians * ToAngleW_E * degreesPerRadian + MiddleLongitude; double latitude = EndSouth - ToLengthN_S * (realLength - lenSouth); double[] destination = new double[2]; destination[0] = longitude; destination[1] = latitude; return destination; } /** * Determines the range and bearing from one location to another. * * @param latitude1 the longitude of the first location (in units of degrees). * @param longitude1 the latitude of the first location (in units of degrees). * * @param latitude2 the longitude of the second location (in units of degrees). * @param longitude2 the latitude of the second location (in units of degrees). * * @return an array containing the range in meters and bearing in degrees from * the first location to the second location. */ public static double[] getRangeAndBearing(double latitude1, double longitude1, double latitude2, double longitude2) { double[] coords1 = getGnomonicCoordinates(latitude1, longitude1); double[] coords2 = getGnomonicCoordinates(latitude2, longitude2); double deltaX = coords2[0] - coords1[0]; double deltaY = coords2[1] - coords1[1]; double rangeMeters = Math.sqrt(deltaX*deltaX + deltaY*deltaY); double bearingDegrees = Math.toDegrees(Math.atan2(deltaY, deltaX)); final double degreesInCircle = 360.0; while (bearingDegrees < 0.0) { bearingDegrees += degreesInCircle; } while (bearingDegrees >= degreesInCircle) { bearingDegrees -= degreesInCircle; } double[] rangeAndBearing = new double[2]; rangeAndBearing[0] = rangeMeters; rangeAndBearing[1] = bearingDegrees; return rangeAndBearing; } } Unfortunately I think your getbearing algorithm is not consistent with ingame lockon bearings. The reason I think this is that I have already tried this method before, and while it does work in some situtations, in many situations it will be off by 5 or more degrees. At this point I require a method which is accurate with ingame bearings to less than 0.5 degrees. I need this precision to map LatLong --> desired cursor position for Radar features. I can give you my own method once I get home...it is a rather strange method but very accurate :) This small error is not that visible in moving map type programs I guess, but once you use it to align the cursor perfectly with a target or similar, it did yield problems for me. I also thought it was just atan (x,z) but it yields too high errors, i assume this is because the definition of Bearing which I have not quite figured out yet. I will implement your code in my radar to check its accuracy when I get home Maybe I made some error when i tried it before ;) Edited November 20, 2009 by =RvE=Yoda S = SPARSE(m,n) abbreviates SPARSE([],[],[],m,n,0). This generates the ultimate sparse matrix, an m-by-n all zero matrix. - Matlab help on 'sparse' Link to comment Share on other sites More sharing options...
Moa Posted November 21, 2009 Share Posted November 21, 2009 I hope it is some use to you. If you find an error I can collect test data using the LockOn mission editor tool and we can create a unit test for that (I test coordinate conversion using all the airbases but only test range and distance between Sukhumi and Krasnodar-Central). For one Black Sea Showdown last year I created a program that did bearings and distances between various targets and bases and it printed out a table of these (we were defending ships and it is very easy to get lost at sea and not defend the correct ship). This had the bearing error you mentioned as I was treating the Earth as a sphere. Not only is it an oblate spheroid (as Case pointed out) but ED use the gnomonic projection which introduces distortions away from the central projection point. By using the same projection we will have the same distortions (relative to the true Earth) but we should be consistent with the in-game distances and bearings. I'm an observer on the kenai leavu2 project now. I do have some ideas on how to make a single bundle work on all platforms (by re-organising the native libraries a bit). Eventually I'd like to be able to commit code but I don't want to tread on your toes. I'm really keen to get a moving map going (hence the coordinate conversion work). Mostly I want it for a Java Tacview replacement (so I can view Tacviews on Linux, which is where I mostly live) but I thought it would be handy for Leavu as well. I have a high-res map from LockOn files but I think Crunch might get a better one for me (from a CDDS somewhere). Initially I won't use these maps but will use the JXMapKit component with Google Maps (I've used Google Maps before with Google Web Toolkit, but not with Swing so a bit of adaption required for me). While you work on the F15C cockpit I might try hacking up equivalent stuff for the F/A-18, since I'm trying the VNAO Hornet out. I intend running your Leavu on Linux on a separate computer so might be able eventually use Leavu for my cockpit instruments (which is where leavu is brilliant) rather than just a texture-map replacement of the Su-33 cockpit. Ambitious yes, will take a while yes, but worth it (especially with FC 2.0 coming out, since everything will have a longer lease-of-life now). S! Moa Link to comment Share on other sites More sharing options...
Recommended Posts