Skip to content

Commit

Permalink
Add Soviet SK-42 6° Gauss-Krüger Coordinates (#16)
Browse files Browse the repository at this point in the history
**LGTM:**

* reference code from stack overflow

* Add SK42 Gauss Krüger Grid Ref output 🇺🇦🗺️

* update documentation and fix Autel new metadata parse bug

* fix typo Geodetic DMS (° " ') to (° ' ")
  • Loading branch information
mkrupczak3 authored Jun 28, 2022
1 parent 3253124 commit 1a94db5
Show file tree
Hide file tree
Showing 8 changed files with 398 additions and 76 deletions.
35 changes: 25 additions & 10 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -192,7 +192,7 @@ Please enter aircraft latitude in (+/-) decimal form:
The preceeding numbers are provided for the user as debug information, but are not necessary during normal operation


Next, enter the latitude, then longitude, then altitude of the aircraft:
Next, enter the latitude, then longitude, then altitude of the aircraft (standard WGS84):


```bash
Expand All @@ -213,20 +213,25 @@ The accuracy of the positional resolution is better at steep angles (high theta)
Please enter camera azimuth (0 is north) in decimal form (degrees): 315
Please enter angle of declanation (degrees down from forward) in decimal form: 20

Approximate range to target: 1035.03
Approximate range to target: 1031

Approximate alt (constructed): 146.01
Approximate alt (terrain): 146.5
Approximate WGS84 alt (constructed): 148
Approximate alt (terrain): 148

Target:
WGS84 (lat, lon): 41.8071606, 12.6400355
WGS84 (lat, lon): 41.807161, 12.640036 Alt: 148
Google Maps: https://maps.google.com/?q=41.807161,12.640036

NATO MGRS: 33TUG0395831058
NATO MGRS: 33TUG0395831058 Alt: 148
MGRS 10m: 33TUG03953105
MGRS 100m: 33TUG039310

SK42: 41.807634, 12.641757
SK42:
Geodetic (°): 41.807634, 12.641757 Alt: 98
Geodetic (° ' "):
41° 48' 27.48" N
12° 38' 30.33" E
Gauss-Krüger (meters): ZONE: 3 X: 46 33042 Y: 3 4021 Alt: 98

```
Expand All @@ -236,7 +241,7 @@ The distance of each iterative step, in meters, is defined by the `increment` va
The information in the following output lines represents the final positional resolution obtained by the approximate intersection of the constructed line emitted from the aircraft's camera and the ground as represented by the terrain data
The values should be tested for correctness and not totally relied upon in the current version of this program.
The values should be tested for correctness
`Approximate range to target:` represents the direct-line distance in meters from the aircraft to the target. This may be useful for an operator to determine if the target match is in the expected place. To obtain the horizontal distance, multiply this number times the cosine of theta
Expand All @@ -252,10 +257,20 @@ The values should be tested for correctness and not totally relied upon in the c
`Google Maps:` a link to the previous lat/lon on Google Maps. Each rounded to 6 decimal places
`NATO MGRS:` represents the target location in the [NATO Military Grid Reference System (MGRS)](https://en.wikipedia.org/wiki/Military_Grid_Reference_System), which is simmilar to [UTM](https://en.wikipedia.org/wiki/Universal_Transverse_Mercator_coordinate_system). This coordinate system does not include the altitude of the Target
`NATO MGRS:` represents the target location in the [NATO Military Grid Reference System (MGRS)](https://en.wikipedia.org/wiki/Military_Grid_Reference_System), which is simmilar to [UTM](https://en.wikipedia.org/wiki/Universal_Transverse_Mercator_coordinate_system). The first 4 (or 5) digits defines the Grid Zone Designator (GZD). The next 10 digits represent the Grid Ref (1 meter square) of the target. The 10 digits of the grid, as well as the Altitude, are underlined in the output for easy reference.
`SK42:` represents the target latitude and longitude (in degrees) according to the [SK-42 (A.K.A. CK-42) coordinate system](https://en.wikipedia.org/wiki/SK-42_reference_system) (Russian: Система координат 1942 года). This system is commonly used in old Soviet and post-Soviet style maps, and is based on a differend ellipsoidal projection than WGS84
`MGRS 10m:` same as `NATO MGRS:` except as an 8 digit (10m square) containing the target
`MGRS 100m:` same as `MGRS 10m:` except as a 6 digit (100m square) containing th target
`SK42 (истема координат 1942 года):` represents the target location according to the [SK-42 (A.K.A. CK-42) coordinate system](https://en.wikipedia.org/wiki/SK-42_reference_system) (Russian: Система координат 1942 года). This system is commonly used in old Soviet and post-Soviet style maps, and is based on a different ellipsoidal projection than WGS84 (called the Krassowsky 1940 ellipsoid)
` Geodetic (°)` represents the target location as latitude and longitude degrees outward from the center of the Krassowsky ellipsoid.
` Geodetic (° ' ")` The same value, split up into degrees, minutes, and seconds. Old Soviet style maps are commonly marked by degree (°) and minute (') on the axes as subscript.
` Gauss-Krüger (meters):` The same value, as a offest value (in meters)on a [Gauss-Krüger](https://desktop.arcgis.com/en/arcmap/latest/map/projections/gauss-kruger.htm) projection. The last 5 digits of the offset Northing and Easting values are commonly used as grid labels on old Soviet style maps. "Northing" refers to a vertical line defined by an `X` value. "Easting" refers to a horizontal line defined by a `Y` value. `ZONE` specifies the GK longitudinal-zone (in 6° increments, possible values 1-60 inclusive). The smallest 5 digits of the Northing and Easting values, as well as the Altitude, are underlined in the output for easy reference.
The program `getTarget.py` will then exit
Expand Down
Binary file modified assets/parseImage_interactive_example3.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
14 changes: 9 additions & 5 deletions drone_sensor_data_blurb.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ Next, choose an image for which to resolve the location of its subject. For exam

Finally, run `parseImage.py`. To save time, the geoTiff file can be provided as the first argument after `python parseImage.py` (or `python3 parseImage.py` on Macs for example):
```bash
python parseImage.py bartow.tif
python parseImage.py cobb.tif
```
(Note: the file must end in `.tif`. If you do not provide a geoTiff file, you will be prompted for input of a filename)

Expand All @@ -34,10 +34,16 @@ Multiple images can be processed at once in **interactive** mode. When you're fi
![image of the processed location in text in Thompson Park, GA](./assets/parseImage_interactive_example3.png)

Let's copy that `NATO MGRS` into Google Maps:




![zoomed image of thompson park drone photo, compared side by side with Google Maps of resolved location. The center point of the drone photo on the left is marked with a small red circle](./assets/pretty_good.jpg)

Pretty good!

(note: the NATO 10-digit grid without GZD is underlined for easy reference. We need to include the full MGRS with GZD to give the precise location. An alternate, Warsaw pact style SK42 Gauss-Krüger grid ref is similarly underlined)

#### headless

If `parseImage.py` is given any additional arguments after a geoTiff file, it will be run in **headless** mode. It will try to read each argument (past the geoTiff file) as an image filename. It will try to resolve a target and put a resolved location in a file IMAGENAME.JPG.ATHENA for each image.
Expand All @@ -52,14 +58,12 @@ The openathena Docker image is convenient because it comes with all pre-requisit

For running with your own GeoTIFF DEM and drone images, mount a directory on your host system your desired files to a directory inside the `/home/user` in the container. Then pass as arguments a GeoTIFF file and one or more drone images using relative file paths like `./recon-photos/Altis.tif` and `./recon-photos/DJI_0666.JPG`

NOTE: relative file paths for drone images will only work on versions > 1.25-pre-alpha. Relative file path for a GeoTIFF file will work on 1.25-pre-alpha however. This notice will be removed in v0.1.26-pre-alpha

It is _**highly recommended**_ to choose a particular tag from the [releases page](https://github.com/mkrupczak3/OpenAthena/releases) rather than using the `latest` tag whenever openathena is to be used in a automated production environment

E.g (with [Docker already installed](https://docs.docker.com/engine/install/)):
```bash
docker pull t3l3tubie/openathena:latest
docker run -v ~/Desktop/recon-photos/:/home/user/recon-photos openathena:latest ./recon-photos/Altis.tif ./recon-photos/DJI_0666.JPG
docker pull t3l3tubie/openathena:v0.1.26-pre-alpha
docker run -v ~/Desktop/recon-photos/:/home/user/recon-photos openathena:v0.1.26-pre-alpha ./recon-photos/Altis.tif ./recon-photos/DJI_0666.JPG
```

The container will run and perform its task, then exit. A file like `~/Desktop/recon-photos/DJI_0666.JPG.ATHENA` will be available on the host filesystem, containing the data of the target calculated by OpenAthena
Expand Down
64 changes: 64 additions & 0 deletions src/SK42_Gauss_Kruger.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
# coding=utf=8
#!/usr/bin/env python

# Code adapted from https://gis.stackexchange.com/a/418152/205005 user Nickname Nick
# converted to Python by mkrupczak3
import math
import numpy as np

from WGS84_SK42_Translator import Translator as Translator # rafasaurus' SK42 coord translator

class Projector(object):

# Параметры эллипсоида Красовского #Parameters of the Krasovsky ellipsoid
a = Translator.aP # Большая (экваториальная) полуось # Large (equatorial) semi-axis
b = np.float64(6356863.019) # Малая (полярная) полуось # Small (polar) semi-axis
e2 = (math.pow(a,2)-math.pow(b,2))/math.pow(a,2) # Эксцентриситет # Eccentricity
n = (a-b)/(a+b) # Приплюснутость # Flatness

# Параметры зоны Гаусса-Крюгера # Parameters of the Gauss-Kruger zone
F = np.float64(1.0) # Масштабный коэффициент # Scale factor
Lat0 = np.float64(0.0) # Начальная параллель (в радианах) # Initial parallel (in radians)

@classmethod
def SK42_Gauss_Kruger(cls, SK42_LatDegrees, SK42_LongDegrees):
if SK42_LongDegrees < 0:
SK42_LongDegrees = 360 + SK42_LongDegrees
zone = int(SK42_LongDegrees/6.0 + 1)

Lon0 = (zone*6-3)* math.pi/180 # Центральный меридиан (в радианах) # Central Meridian (in radians)
N0 = np.float64(0.0) # Условное северное смещение для начальной параллели # Conditional north offset for the initial parallel
E0 = zone*1e6+500000.0; # Условное восточное смещение для центрального меридиана#Conditional eastern offset for the central meridian

# Перевод широты и долготы в радианы # Converting latitude and longitude to radians
Lat = SK42_LatDegrees*math.pi/180.0
Lon = SK42_LongDegrees*math.pi/180.0

# Вычисление переменных для преобразования # Calculating variables for conversion
sinLat = math.sin(Lat)
cosLat = math.cos(Lat)
tanLat = math.tan(Lat)

v = cls.a * cls.F * math.pow(1-cls.e2* math.pow(sinLat,2),-0.5)
p = cls.a*cls.F*(1-cls.e2) * math.pow(1-cls.e2*math.pow(sinLat,2),-1.5)
n2 = v/p-1
M1 = (1+cls.n+5.0/4.0* math.pow(cls.n,2) +5.0/4.0* math.pow(cls.n,3)) * (Lat-cls.Lat0)
M2 = (3*cls.n+3* math.pow(cls.n,2) +21.0/8.0* math.pow(cls.n,3)) * math.sin(Lat - cls.Lat0) * math.cos(Lat + cls.Lat0)
M3 = (15.0/8.0* math.pow(cls.n,2) +15.0/8.0* math.pow(cls.n,3))*math.sin(2 * (Lat - cls.Lat0))*math.cos(2 * (Lat + cls.Lat0))
M4 = 35.0/24.0* math.pow(cls.n,3) *math.sin(3 * (Lat - cls.Lat0)) * math.cos(3 * (Lat + cls.Lat0))
M = cls.b*cls.F*(M1-M2+M3-M4)
I = M+N0
II = v/2 * sinLat * cosLat
III = v/24 * sinLat * math.pow(cosLat,3) * (5-math.pow(tanLat,2)+9*n2)
IIIA = v/720 * sinLat * math.pow(cosLat,5) * (61-58*math.pow(tanLat,2)+math.pow(tanLat,4))
IV = v * cosLat
V = v/6 * math.pow(cosLat,3) * (v/p-math.pow(tanLat,2))
VI = v/120 * math.pow(cosLat,5) * (5-18*math.pow(tanLat,2)+math.pow(tanLat,4)+14*n2-58*math.pow(tanLat,2)*n2)

# Вычисление северного и восточного смещения (в метрах) # Calculation of the north and east offset (in meters)
N = I+II* math.pow(Lon-Lon0,2)+III* math.pow(Lon-Lon0,4)+IIIA* math.pow(Lon-Lon0,6)
E = E0+IV*(Lon-Lon0)+V* math.pow(Lon-Lon0,3)+VI* math.pow(Lon-Lon0,5)

return(zone, N, E)

# end def SK42_Gauss_Kruger
99 changes: 99 additions & 0 deletions src/WGS84ToSK42Meters.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
// from https://gis.stackexchange.com/a/418152/205005 user Nickname Nick:
public double[] WGS84ToSK42Meters(double latWgs84, double longWgs84, double heightWgs84)
{
//Часть 1: Перевод Wgs84 географических координат(долготы и широты в градусах) в СК42 географические координаты (долготу и широту в градусах)//Part 1: Converting Wgs84 geographical coordinates(longitude and latitude in degrees) to SK42 geographical coordinates(longitude and latitude in degrees)
double ro = 206264.8062;//Число угловых секунд в радиане//The number of angular seconds in radians
double aP = 6378245; // Большая полуось//Large semi - axis
double alP = 1 / 298.3; // Сжатие//Compression
double e2P = 2 * alP - Math.pow(alP,2); // Квадрат эксцентриситета//Eccentricity square

// Эллипсоид WGS84 (GRS80, эти два эллипсоида сходны по большинству параметров)//Ellipsoid WGS84 (GRS80, these two ellipsoids are similar in most parameters)
double aW = 6378137; // Большая полуось//Large semi - axis
double alW = 1 / 298.257223563; // Сжатие//Compression
double e2W = 2 * alW - Math.pow(alW, 2); // Квадрат эксцентриситета//Eccentricity square

// Вспомогательные значения для преобразования эллипсоидов
//Auxiliary values for converting ellipsoids
double a1 = (aP + aW) / 2;
double e21 = (e2P + e2W) / 2;
double da = aW - aP;
double de2 = e2W - e2P;

// Линейные элементы трансформирования, в метрах//Linear transformation elements, in meters
double dx = 23.92;
double dy = -141.27;
double dz = -80.9;

// Угловые элементы трансформирования, в секундах//Angular transformation elements, in seconds
double wx = 0;
double wy = 0;
double wz = 0;

// Дифференциальное различие масштабов//Differential difference of scales
double ms = 0;

double B, L, M11, N1;
B = latWgs84 * Math.PI / 180;
L = longWgs84 * Math.PI / 180;
M11 = a1 * (1 - e21) / Math.pow((1 - e21 * Math.pow(Math.sin(B),2)),1.5);
N1 = a1 * Math.pow((1 - e21 * Math.pow(Math.sin(B),2)),-0.5);
double dB = ro / (M11 + heightWgs84) * (N1 / a1 * e21 * Math.sin(B) * Math.cos(B) * da + (Math.pow(N1,2) / Math.pow(a1,2) + 1) * N1 * Math.sin(B) * Math.cos(B) * de2 / 2 - (dx * Math.cos(L) + dy * Math.sin(L)) * Math.sin(B) + dz * Math.cos(B)) - wx * Math.sin(L) * (1 + e21 * Math.cos(2 * B)) + wy * Math.cos(L) * (1 + e21 * Math.cos(2 * B)) - ro * ms * e21 * Math.sin(B) * Math.cos(B);

double SK42_LatDegrees = latWgs84 - dB / 3600;//широта в ск42 в градусах//latitude in sk42 in degrees

B = latWgs84 * Math.PI / 180;
L = longWgs84 * Math.PI / 180;
N1 = a1 * Math.pow((1 - e21 * Math.pow(Math.sin(B),2)), -0.5);
double dL = ro / ((N1 + heightWgs84) * Math.cos(B)) * (-dx * Math.sin(L) + dy * Math.cos(L)) + Math.tan(B) * (1 - e21) * (wx * Math.cos(L) + wy * Math.sin(L)) - wz;

double SK42_LongDegrees = longWgs84 - dL / 3600;//долгота в ск42 в градусах//longitude in sk42 in degrees

// Часть 2: Перевод СК42 географических координат (широты и долготы в градусах) в СК42 прямоугольные координаты (северное и восточное смещения в метрах)//Part 2: Converting of SK42 geographical coordinates (latitude and longitude in degrees) into SK42 rectangular coordinates(easting and northing in meters)
// Номер зоны Гаусса-Крюгера//Number of the Gauss-Kruger zone
int zone = (int) (SK42_LongDegrees/6.0+1);

// Параметры эллипсоида Красовского//Parameters of the Krasovsky ellipsoid
double a = 6378245.0; // Большая (экваториальная) полуось//Large (equatorial) semi-axis
double b = 6356863.019; // Малая (полярная) полуось//Small (polar) semi-axis
double e2 = (Math.pow(a,2)-Math.pow(b,2))/Math.pow(a,2); // Эксцентриситет//Eccentricity
double n = (a-b)/(a+b); // Приплюснутость//Flatness


// Параметры зоны Гаусса-Крюгера//Parameters of the Gauss-Kruger zone
double F = 1.0; // Масштабный коэффициент//Scale factor
double Lat0 = 0.0; // Начальная параллель (в радианах)//Initial parallel (in radians)
double Lon0 = (zone*6-3)* Math.PI/180; // Центральный меридиан (в радианах)//Central Meridian (in radians)
double N0 = 0.0; // Условное северное смещение для начальной параллели//Conditional north offset for the initial parallel
double E0 = zone*1e6+500000.0; // Условное восточное смещение для центрального меридиана//Conditional eastern offset for the central meridian

// Перевод широты и долготы в радианы//Converting latitude and longitude to radians
double Lat = SK42_LatDegrees*Math.PI/180.0;
double Lon = SK42_LongDegrees*Math.PI/180.0;

// Вычисление переменных для преобразования//Calculating variables for conversion
double sinLat = Math.sin(Lat);
double cosLat = Math.cos(Lat);
double tanLat = Math.tan(Lat);

double v = a * F * Math.pow(1-e2* Math.pow(sinLat,2),-0.5);
double p = a*F*(1-e2) * Math.pow(1-e2*Math.pow(sinLat,2),-1.5);
double n2 = v/p-1;
double M1 = (1+n+5.0/4.0* Math.pow(n,2) +5.0/4.0* Math.pow(n,3)) * (Lat-Lat0);
double M2 = (3*n+3* Math.pow(n,2) +21.0/8.0* Math.pow(n,3)) * Math.sin(Lat - Lat0) * Math.cos(Lat + Lat0);
double M3 = (15.0/8.0* Math.pow(n,2) +15.0/8.0* Math.pow(n,3))*Math.sin(2 * (Lat - Lat0))*Math.cos(2 * (Lat + Lat0));
double M4 = 35.0/24.0* Math.pow(n,3) *Math.sin(3 * (Lat - Lat0)) * Math.cos(3 * (Lat + Lat0));
double M = b*F*(M1-M2+M3-M4);
double I = M+N0;
double II = v/2 * sinLat * cosLat;
double III = v/24 * sinLat * Math.pow(cosLat,3) * (5-Math.pow(tanLat,2)+9*n2);
double IIIA = v/720 * sinLat * Math.pow(cosLat,5) * (61-58*Math.pow(tanLat,2)+Math.pow(tanLat,4));
double IV = v * cosLat;
double V = v/6 * Math.pow(cosLat,3) * (v/p-Math.pow(tanLat,2));
double VI = v/120 * Math.pow(cosLat,5) * (5-18*Math.pow(tanLat,2)+Math.pow(tanLat,4)+14*n2-58*Math.pow(tanLat,2)*n2);

// Вычисление северного и восточного смещения (в метрах)//Calculation of the north and east offset (in meters)
double N = I+II* Math.pow(Lon-Lon0,2)+III* Math.pow(Lon-Lon0,4)+IIIA* Math.pow(Lon-Lon0,6);
double E = E0+IV*(Lon-Lon0)+V* Math.pow(Lon-Lon0,3)+VI* Math.pow(Lon-Lon0,5);

return new double[] {N, E};
}
Loading

0 comments on commit 1a94db5

Please sign in to comment.