Initial fork commit

This commit is contained in:
2025-09-06 22:03:29 -04:00
commit 2d238cd339
1748 changed files with 932506 additions and 0 deletions

View File

View 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°4246″N', '74°0021″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