Initial fork commit
This commit is contained in:
165
scripts/geodata/coordinates/conversion.py
Normal file
165
scripts/geodata/coordinates/conversion.py
Normal file
@@ -0,0 +1,165 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
'''
|
||||
geodata.coordinates.conversion
|
||||
------------------------------
|
||||
|
||||
Geographic coordinates typically come in two flavors: decimal and
|
||||
DMS (degree-minute-second). This module parses a coordinate string
|
||||
in just about any format. This was originally created for parsing
|
||||
lat/lons found on the web.
|
||||
|
||||
Usage:
|
||||
>>> latlon_to_decimal('40°42′46″N', '74°00′21″W') # returns (40.71277777777778, 74.00583333333333)
|
||||
>>> latlon_to_decimal('40,74 N', '74,001 W') # returns (40.74, -74.001)
|
||||
>>> to_valid_longitude(360.0)
|
||||
>>> latitude_is_valid(90.0)
|
||||
'''
|
||||
|
||||
import math
|
||||
import re
|
||||
|
||||
from geodata.encoding import safe_decode
|
||||
from geodata.math.floats import isclose
|
||||
|
||||
beginning_re = re.compile('^[^0-9\-]+', re.UNICODE)
|
||||
end_re = re.compile('[^0-9]+$', re.UNICODE)
|
||||
|
||||
latitude_dms_regex = re.compile(ur'^(-?[0-9]{1,2})[ ]*[ :°ºd][ ]*([0-5]?[0-9])?[ ]*[:\'\u2032m]?[ ]*([0-5]?[0-9](?:\.\d+)?)?[ ]*[:\?\"\u2033s]?[ ]*(N|n|S|s)?$', re.I | re.UNICODE)
|
||||
longitude_dms_regex = re.compile(ur'^(-?1[0-8][0-9]|0?[0-9]{1,2})[ ]*[ :°ºd][ ]*([0-5]?[0-9])?[ ]*[:\'\u2032m]?[ ]*([0-5]?[0-9](?:\.\d+)?)?[ ]*[:\?\"\u2033s]?[ ]*(E|e|W|w)?$', re.I | re.UNICODE)
|
||||
|
||||
latitude_decimal_with_direction_regex = re.compile('^(-?[0-9][0-9](?:\.[0-9]+))[ ]*[ :°ºd]?[ ]*(N|n|S|s)$', re.I)
|
||||
longitude_decimal_with_direction_regex = re.compile('^(-?1[0-8][0-9]|0?[0-9][0-9](?:\.[0-9]+))[ ]*[ :°ºd]?[ ]*(E|e|W|w)$', re.I)
|
||||
|
||||
direction_sign_map = {'n': 1, 's': -1, 'e': 1, 'w': -1}
|
||||
|
||||
|
||||
def direction_sign(d):
|
||||
if d is None:
|
||||
return 1
|
||||
d = d.lower().strip()
|
||||
if d in direction_sign_map:
|
||||
return direction_sign_map[d]
|
||||
else:
|
||||
raise ValueError('Invalid direction: {}'.format(d))
|
||||
|
||||
|
||||
def int_or_float(d):
|
||||
try:
|
||||
return int(d)
|
||||
except ValueError:
|
||||
return float(d)
|
||||
|
||||
|
||||
def degrees_to_decimal(degrees, minutes, seconds):
|
||||
degrees = int_or_float(degrees)
|
||||
minutes = int_or_float(minutes)
|
||||
seconds = int_or_float(seconds)
|
||||
|
||||
return degrees + (minutes / 60.0) + (seconds / 3600.0)
|
||||
|
||||
|
||||
def is_valid_latitude(latitude):
|
||||
'''Latitude must be real number between -90.0 and 90.0'''
|
||||
try:
|
||||
latitude = float(latitude)
|
||||
except (ValueError, TypeError):
|
||||
return False
|
||||
|
||||
if latitude > 90.0 or latitude < -90.0 or math.isinf(latitude) or math.isnan(latitude):
|
||||
return False
|
||||
return True
|
||||
|
||||
|
||||
def is_valid_longitude(longitude):
|
||||
'''Allow any valid real number to be a longitude'''
|
||||
try:
|
||||
longitude = float(longitude)
|
||||
except (ValueError, TypeError):
|
||||
return False
|
||||
return not math.isinf(longitude) and not math.isnan(longitude)
|
||||
|
||||
|
||||
def to_valid_latitude(latitude):
|
||||
'''Convert longitude into the -180 to 180 scale'''
|
||||
if not is_valid_latitude(latitude):
|
||||
raise ValueError('Invalid latitude {}'.format(latitude))
|
||||
|
||||
if isclose(latitude, 90.0):
|
||||
latitude = 89.9999
|
||||
elif isclose(latitude, -90.0):
|
||||
latitude = -89.9999
|
||||
|
||||
return latitude
|
||||
|
||||
|
||||
def to_valid_longitude(longitude):
|
||||
'''Convert longitude into the -180 to 180 scale'''
|
||||
if not is_valid_longitude(longitude):
|
||||
raise ValueError('Invalid longitude {}'.format(longitude))
|
||||
|
||||
while longitude <= -180.0:
|
||||
longitude += 360.0
|
||||
|
||||
while longitude > 180.0:
|
||||
longitude -= 360.0
|
||||
|
||||
return longitude
|
||||
|
||||
|
||||
def latlon_to_decimal(latitude, longitude):
|
||||
have_lat = False
|
||||
have_lon = False
|
||||
|
||||
latitude = safe_decode(latitude).strip(u' ,;|')
|
||||
longitude = safe_decode(longitude).strip(u' ,;|')
|
||||
|
||||
latitude = latitude.replace(u',', u'.')
|
||||
longitude = longitude.replace(u',', u'.')
|
||||
|
||||
lat_dms = latitude_dms_regex.match(latitude)
|
||||
lat_dir = latitude_decimal_with_direction_regex.match(latitude)
|
||||
|
||||
if lat_dms:
|
||||
d, m, s, c = lat_dms.groups()
|
||||
sign = direction_sign(c)
|
||||
latitude = degrees_to_decimal(d or 0, m or 0, s or 0)
|
||||
have_lat = True
|
||||
elif lat_dir:
|
||||
d, c = lat_dir.groups()
|
||||
sign = direction_sign(c)
|
||||
latitude = return_type(d) * sign
|
||||
have_lat = True
|
||||
else:
|
||||
latitude = re.sub(beginning_re, u'', latitude)
|
||||
latitude = re.sub(end_re, u'', latitude)
|
||||
|
||||
lon_dms = longitude_dms_regex.match(longitude)
|
||||
lon_dir = longitude_decimal_with_direction_regex.match(longitude)
|
||||
|
||||
if lon_dms:
|
||||
d, m, s, c = lon_dms.groups()
|
||||
sign = direction_sign(c)
|
||||
longitude = degrees_to_decimal(d or 0, m or 0, s or 0)
|
||||
have_lon = True
|
||||
elif lon_dir:
|
||||
d, c = lon_dir.groups()
|
||||
sign = direction_sign(c)
|
||||
longitude = return_type(d) * sign
|
||||
have_lon = True
|
||||
else:
|
||||
longitude = re.sub(beginning_re, u'', longitude)
|
||||
longitude = re.sub(end_re, u'', longitude)
|
||||
|
||||
latitude = float(latitude)
|
||||
longitude = float(longitude)
|
||||
|
||||
if not is_valid_latitude(latitude):
|
||||
raise ValueError('Invalid latitude: {}'.format(latitude))
|
||||
|
||||
if not is_valid_longitude(longitude):
|
||||
raise ValueError('Invalid longitude: {}'.format(longitude))
|
||||
|
||||
latitude = to_valid_latitude(latitude)
|
||||
longitude = to_valid_longitude(longitude)
|
||||
|
||||
return latitude, longitude
|
||||
Reference in New Issue
Block a user