First, let's look at example of latitude and longitude in DMSH format, space delimited:
52 22 33.47N 015 42 33.44E
We can write is in general form as:
DD MM SS.ss H DDD MM SS.ss H
and the following must be true:
- DD is between <-90, 90> for latitude and <-180, 180> for longitude
- MM is between <0, 59>
- SS.ss is between equal or grater than 0 and less then 60
ddsddsdd.ddL dddsddsdd.ddL
where d is digit, s is space and L is hemisphere letter (N, S, E, W).
We will take into account that latitude can be without 'leading zeros' in our further deliberations:
1 1 1.44N instead of 01 01 01.44N
There are two options here:
1. To write quite complex regular expression that will take into account all combinations of deg, min, sec e. g.:
- for latitude: if deg is equal 90, min, sec must be equal 0 (90 05 05.55N is not valid latitude)
- for latitude: if deg is between <-90, 90> min might take integer values <0, 59>, sec integer values <0, 59> or float values <0, 60)
Regular expression to match coordinate in DMSH format, space separated looks like:
regex_dmsh = re.compile(r'''(?P<deg>^\d{1,3}) # Degrees (\s) (?P<min>\d{1,2}) # Minutes (\s) (?P<sec>\d{1,2}(\.\d+)?) # Seconds (?P<hem>[NSEW]$) ''', re.VERBOSE)
I used groups to get easy access to deg, min, sec and hemisphere letter parts.
Now we can write function that converts coordinate given in dms format into dd format.
Function will take two arguments:
- regex_pattern: regular expression, pattern of dms format
- dms: string, latitude or longitude to be converted into dd format
def dms2dd(regex_pattern, dms):
The first step is to check if input dms matches pattern and get hemisphere, deg, min, sec values:
if regex_pattern.match(dms): groups = regex_pattern.search(dms) h = groups.group('hem') d = float(groups.group('deg')) m = float(groups.group('min')) s = float(groups.group('sec')) # Check h, d, m, s conditions here else: return None
Next step is to check conditions for latitude and longitude discussed above:
For latitude:
if h in ['N', 'S']: if d > 90: # Latitude is in range <-90, 90> return None elif d == 90 and (m > 0 or s > 0): return None else: if m >= 60 or s >= 60: return None else: dd = d + m / 60 + s / 3600 if h == 'S': dd = -dd return dd
For longitude:
elif h in ['E', 'W']: if d > 180: # Longitude is in range <-180, 180> return None elif d == 180 and (m > 0 or s > 0): return None else: if m >= 60 or s >= 60: return None else: dd = d + m / 60 + s / 3600 if h == 'W': dd = -dd return dd
All source code can be found here:https://github.com/strpaw/python_examples/blob/master/latlon_basic_regex_parser.py
Let's test if our code is correct:
Some test data (list of tuples, one tuple is coordinate lat, lon pair):
test_coordinates = [('77 01 01.11N', '015 15 17.43E'), ('77 01 01N', '015 15 17E'), ('77-01-01.11N', '015-15-17.43E'), (77.43333, 15.3336), ('77 50 47S', '166 40 06W'), ('77 5 7.1S', '166 4 6.45555W'), ('7 5 17.1S', '001 4 06.45555W'), ('77 5 7.1', '166 4 6.45555'), ('0 0 0N', '000 00 00.000E'), ('97 5 7.1S', '180 4 6.45555W'), ('89 59 59.999S', '179 59 59.999E')]
And a bit of code to check if dms2dd() can convert input into dd if coordinate is DMSH space separated format:
print('Test dms2dd - input DMS space delimited, e.g. 78 12 24.56N') print('-' * 60) print('{:^15} {:^15} | {:^24}'.format('Lat test', 'Lon test', 'dms2dd result')) print('-' * 60) for test_coord in test_coordinates: lat_dd = dms2dd(regex_dmsh, str(test_coord[0])) lon_dd = dms2dd(regex_dmsh, str(test_coord[1])) if lat_dd is None: lat_result = 'None' else: lat_result = '{:12.7f}'.format(lat_dd) if lon_dd is None: lon_result = 'None' else: lon_result = '{:12.7f}'.format(lon_dd) print('{:>15} {:>15} | {:>12} {:>12}'.format(test_coord[0], test_coord[1], lat_result, lon_result))
the output is:
Test dms2dd - input DMS space delimited, e.g. 78 12 24.56N------------------------------------------------------------
Lat test Lon test | dms2dd result
------------------------------------------------------------
77 01 01.11N 015 15 17.43E | 77.0169750 15.2548417
77 01 01N 015 15 17E | 77.0169444 15.2547222
77-01-01.11N 015-15-17.43E | None None
77.43333 15.3336 | None None
77 50 47S 166 40 06W | -77.8463889 -166.6683333
77 5 7.1S 166 4 6.45555W | -77.0853056 -166.0684599
7 5 17.1S 001 4 06.45555W | -7.0880833 -1.0684599
77 5 7.1 166 4 6.45555 | None None
0 0 0N 000 00 00.000E | 0.0000000 0.0000000
97 5 7.1S 180 4 6.45555W | None None
89 59 59.999S 179 59 59.999E | -89.9999997 179.9999997
and seems that function works fine.
No comments:
Post a Comment