diff options
author | Damien Picard <dam.pic@free.fr> | 2019-12-09 14:02:34 +0300 |
---|---|---|
committer | Damien Picard <dam.pic@free.fr> | 2019-12-09 14:03:58 +0300 |
commit | efbc5e5db7c73ae43ddc11abf8db8bc8f98c9945 (patch) | |
tree | 2f5a3fce49e89a35264abf0d2576deaf932ae446 /sun_position/geo.py | |
parent | c5f0bbde29a9406c33ac84a04aa1ab6b2a27ff35 (diff) |
sun_position: move to release: T69936
Diffstat (limited to 'sun_position/geo.py')
-rw-r--r-- | sun_position/geo.py | 192 |
1 files changed, 192 insertions, 0 deletions
diff --git a/sun_position/geo.py b/sun_position/geo.py new file mode 100644 index 00000000..6d49f2ad --- /dev/null +++ b/sun_position/geo.py @@ -0,0 +1,192 @@ +#!/usr/bin/env python +# +# geo.py is a python module with no dependencies on extra packages, +# providing some convenience functions for working with geographic +# coordinates +# +# Copyright (C) 2010 Maximilian Hoegner <hp.maxi@hoegners.de> +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. +# + +### Part one - Functions for dealing with points on a sphere ### + +### Part two - A tolerant parser for position strings ### +import re + + +class Parser: + """ A parser class using regular expressions. """ + + def __init__(self): + self.patterns = {} + self.raw_patterns = {} + self.virtual = {} + + def add(self, name, pattern, virtual=False): + """ Adds a new named pattern (regular expression) that can reference previously added patterns by %(pattern_name)s. + Virtual patterns can be used to make expressions more compact but don't show up in the parse tree. """ + self.raw_patterns[name] = "(?:" + pattern + ")" + self.virtual[name] = virtual + + try: + self.patterns[name] = ("(?:" + pattern + ")") % self.patterns + except KeyError as e: + raise (Exception, "Unknown pattern name: %s" % str(e)) + + def parse(self, pattern_name, text): + """ Parses 'text' with pattern 'pattern_name' and returns parse tree """ + + # build pattern with subgroups + sub_dict = {} + subpattern_names = [] + for s in re.finditer("%\(.*?\)s", self.raw_patterns[pattern_name]): + subpattern_name = s.group()[2:-2] + if not self.virtual[subpattern_name]: + sub_dict[subpattern_name] = "(" + self.patterns[ + subpattern_name] + ")" + subpattern_names.append(subpattern_name) + else: + sub_dict[subpattern_name] = self.patterns[subpattern_name] + + pattern = "^" + (self.raw_patterns[pattern_name] % sub_dict) + "$" + + # do matching + m = re.match(pattern, text) + + if m == None: + return None + + # build tree recursively by parsing subgroups + tree = {"TEXT": text} + + for i in range(len(subpattern_names)): + text_part = m.group(i + 1) + if not text_part == None: + subpattern = subpattern_names[i] + tree[subpattern] = self.parse(subpattern, text_part) + + return tree + + +position_parser = Parser() +position_parser.add("direction_ns", r"[NSns]") +position_parser.add("direction_ew", r"[EOWeow]") +position_parser.add("decimal_separator", r"[\.,]", True) +position_parser.add("sign", r"[+-]") + +position_parser.add("nmea_style_degrees", r"[0-9]{2,}") +position_parser.add("nmea_style_minutes", + r"[0-9]{2}(?:%(decimal_separator)s[0-9]*)?") +position_parser.add( + "nmea_style", r"%(sign)s?\s*%(nmea_style_degrees)s%(nmea_style_minutes)s") + +position_parser.add( + "number", + r"[0-9]+(?:%(decimal_separator)s[0-9]*)?|%(decimal_separator)s[0-9]+") + +position_parser.add("plain_degrees", r"(?:%(sign)s\s*)?%(number)s") + +position_parser.add("degree_symbol", r"°", True) +position_parser.add("minutes_symbol", r"'|′|`|´", True) +position_parser.add("seconds_symbol", + r"%(minutes_symbol)s%(minutes_symbol)s|″|\"", + True) +position_parser.add("degrees", r"%(number)s\s*%(degree_symbol)s") +position_parser.add("minutes", r"%(number)s\s*%(minutes_symbol)s") +position_parser.add("seconds", r"%(number)s\s*%(seconds_symbol)s") +position_parser.add( + "degree_coordinates", + "(?:%(sign)s\s*)?%(degrees)s(?:[+\s]*%(minutes)s)?(?:[+\s]*%(seconds)s)?|(?:%(sign)s\s*)%(minutes)s(?:[+\s]*%(seconds)s)?|(?:%(sign)s\s*)%(seconds)s" +) + +position_parser.add( + "coordinates_ns", + r"%(nmea_style)s|%(plain_degrees)s|%(degree_coordinates)s") +position_parser.add( + "coordinates_ew", + r"%(nmea_style)s|%(plain_degrees)s|%(degree_coordinates)s") + +position_parser.add( + "position", """\ +\s*%(direction_ns)s\s*%(coordinates_ns)s[,;\s]*%(direction_ew)s\s*%(coordinates_ew)s\s*|\ +\s*%(direction_ew)s\s*%(coordinates_ew)s[,;\s]*%(direction_ns)s\s*%(coordinates_ns)s\s*|\ +\s*%(coordinates_ns)s\s*%(direction_ns)s[,;\s]*%(coordinates_ew)s\s*%(direction_ew)s\s*|\ +\s*%(coordinates_ew)s\s*%(direction_ew)s[,;\s]*%(coordinates_ns)s\s*%(direction_ns)s\s*|\ +\s*%(coordinates_ns)s[,;\s]+%(coordinates_ew)s\s*\ +""") + + +def get_number(b): + """ Takes appropriate branch of parse tree and returns float. """ + s = b["TEXT"].replace(",", ".") + return float(s) + + +def get_coordinate(b): + """ Takes appropriate branch of the parse tree and returns degrees as a float. """ + + r = 0. + + if b.get("nmea_style"): + if b["nmea_style"].get("nmea_style_degrees"): + r += get_number(b["nmea_style"]["nmea_style_degrees"]) + if b["nmea_style"].get("nmea_style_minutes"): + r += get_number(b["nmea_style"]["nmea_style_minutes"]) / 60. + if b["nmea_style"].get( + "sign") and b["nmea_style"]["sign"]["TEXT"] == "-": + r *= -1. + elif b.get("plain_degrees"): + r += get_number(b["plain_degrees"]["number"]) + if b["plain_degrees"].get( + "sign") and b["plain_degrees"]["sign"]["TEXT"] == "-": + r *= -1. + elif b.get("degree_coordinates"): + if b["degree_coordinates"].get("degrees"): + r += get_number(b["degree_coordinates"]["degrees"]["number"]) + if b["degree_coordinates"].get("minutes"): + r += get_number(b["degree_coordinates"]["minutes"]["number"]) / 60. + if b["degree_coordinates"].get("seconds"): + r += get_number( + b["degree_coordinates"]["seconds"]["number"]) / 3600. + if b["degree_coordinates"].get( + "sign") and b["degree_coordinates"]["sign"]["TEXT"] == "-": + r *= -1. + + return r + + +def parse_position(s): + """ Takes a (utf8-encoded) string describing a position and returns a tuple of floats for latitude and longitude in degrees. + Tries to be as tolerant as possible with input. Returns None if parsing doesn't succeed. """ + + parse_tree = position_parser.parse("position", s) + if parse_tree == None: return None + + lat_sign = +1. + if parse_tree.get( + "direction_ns") and parse_tree["direction_ns"]["TEXT"] in ("S", + "s"): + lat_sign = -1. + + lon_sign = +1. + if parse_tree.get( + "direction_ew") and parse_tree["direction_ew"]["TEXT"] in ("W", + "w"): + lon_sign = -1. + + lat = lat_sign * get_coordinate(parse_tree["coordinates_ns"]) + lon = lon_sign * get_coordinate(parse_tree["coordinates_ew"]) + + return lat, lon |