[pyorbital] 02/08: Imported Upstream version 1.0.0
Antonio Valentino
a_valentino-guest at moszumanska.debian.org
Mon Feb 15 22:21:32 UTC 2016
This is an automated email from the git hooks/post-receive script.
a_valentino-guest pushed a commit to branch master
in repository pyorbital.
commit d01aa734c12d66c9c78e6b1b1d3d96c608d77a70
Author: Antonio Valentino <antonio.valentino at tiscali.it>
Date: Mon Feb 15 22:48:42 2016 +0100
Imported Upstream version 1.0.0
---
changelog.rst | 489 +++++++++++++++++++++++++++++
doc/source/conf.py | 15 +-
etc/platforms.txt | 60 ++++
pyorbital/geoloc.py | 111 +++----
pyorbital/geoloc_instrument_definitions.py | 102 +++---
pyorbital/orbital.py | 186 ++++++-----
pyorbital/tlefile.py | 267 ++++++++++++----
pyorbital/version.py | 8 +-
setup.cfg | 5 +
setup.py | 2 +-
10 files changed, 1009 insertions(+), 236 deletions(-)
diff --git a/changelog.rst b/changelog.rst
new file mode 100644
index 0000000..0783152
--- /dev/null
+++ b/changelog.rst
@@ -0,0 +1,489 @@
+Changelog
+=========
+
+v1.0.0 (2015-08-25)
+-------------------
+
+- Update changelog. [Martin Raspaud]
+
+- Bump version: 0.3.2 → 1.0.0. [Martin Raspaud]
+
+- Cleanup. [Martin Raspaud]
+
+ Signed-off-by: Martin Raspaud <martin.raspaud at smhi.se>
+
+
+- Fix version number. [Martin Raspaud]
+
+ Signed-off-by: Martin Raspaud <martin.raspaud at smhi.se>
+
+
+- Cosmetics. [Martin Raspaud]
+
+ Signed-off-by: Martin Raspaud <martin.raspaud at smhi.se>
+
+
+- Merge pull request #2 from pnuu/feature_tle_lookup. [Martin Raspaud]
+
+ Use NORAD catalog numbers for TLE reading
+
+- Example file for mapping OSCAR platform names and NORAD catalog
+ numbers. [Panu Lahtinen]
+
+- Add setup.cfg for easy rpm generation. [Martin Raspaud]
+
+ Signed-off-by: Martin Raspaud <martin.raspaud at smhi.se>
+
+
+- Merge branch 'develop' of github.com:mraspaud/pyorbital into develop.
+ [Martin Raspaud]
+
+- Merge pull request #1 from spareeth/develop. [Martin Raspaud]
+
+ changes to avhrr_gacfunction and read_tle_decimal
+
+- Added '+' as a condition in the read_tle function. [Sajid Pareeth]
+
+- Renaming the variable scans_nb to scan_times in offset in avhrr_gac
+ function. [Sajid Pareeth]
+
+- Bugfix: eccentricity too low message formatting. [Martin Raspaud]
+
+ Signed-off-by: Martin Raspaud <martin.raspaud at smhi.se>
+
+
+- Allow reading TLE from the most recent file described by the TLES env.
+ [Martin Raspaud]
+
+ Signed-off-by: Martin Raspaud <martin.raspaud at smhi.se>
+
+
+- Change decimate to frequency in avhrr instruments. [Martin Raspaud]
+
+ Signed-off-by: Martin Raspaud <martin.raspaud at smhi.se>
+
+
+- Add the avhrr instrument, gac version. [Martin Raspaud]
+
+ Signed-off-by: Martin Raspaud <martin.raspaud at smhi.se>
+
+
+- Accept missing zeros in TLE (old noaa compatibility). [Martin Raspaud]
+
+- Add the horizon parameter to get_next_passes to get the
+ risetime/falltime at given angle. [Martin Raspaud]
+
+ Signed-off-by: Martin Raspaud <martin.raspaud at smhi.se>
+
+
+- Merge branch 'master' into develop. [Martin Raspaud]
+
+- Fix backwards numpy compatibility. [Martin Raspaud]
+
+ Signed-off-by: Martin Raspaud <martin.raspaud at smhi.se>
+
+
+v0.3.2 (2014-04-10)
+-------------------
+
+- Merge branch 'develop' [Martin Raspaud]
+
+- Bump up version number. [Martin Raspaud]
+
+ Signed-off-by: Martin Raspaud <martin.raspaud at smhi.se>
+
+
+- Merge branch 'feature-no-scipy' into develop. [Martin Raspaud]
+
+- Remove scipy dependencies. [Martin Raspaud]
+
+ Was depending on scipy.optimize, brent and brentq function.
+ Replaced by secant method root finding and successive parabolic
+ interpolation local minimum finding.
+
+ Signed-off-by: Martin Raspaud <martin.raspaud at smhi.se>
+
+
+- Correcting the travis file. [Martin Raspaud]
+
+ Signed-off-by: Martin Raspaud <martin.raspaud at smhi.se>
+
+
+v0.3.1 (2014-02-24)
+-------------------
+
+- Bugfix in travis file. [Martin Raspaud]
+
+- Bump up version number. [Martin Raspaud]
+
+ Signed-off-by: Martin Raspaud <martin.raspaud at smhi.se>
+
+
+- Fixed documentation. [Martin Raspaud]
+
+ Signed-off-by: Martin Raspaud <martin.raspaud at smhi.se>
+
+
+- Cleanup. [Martin Raspaud]
+
+ Signed-off-by: Martin Raspaud <martin.raspaud at smhi.se>
+
+
+- New nadir computations for geoloc. [Martin Raspaud]
+
+ Signed-off-by: Martin Raspaud <martin.raspaud at smhi.se>
+
+
+- More unit tests. [Martin Raspaud]
+
+ Signed-off-by: Martin Raspaud <martin.raspaud at smhi.se>
+
+
+v0.3.0 (2014-01-07)
+-------------------
+
+- Auto update version number in documentation. [Martin Raspaud]
+
+ Signed-off-by: Martin Raspaud <martin.raspaud at smhi.se>
+
+
+- Change to version file and bump up to v0.3.0. [Martin Raspaud]
+
+ Signed-off-by: Martin Raspaud <martin.raspaud at smhi.se>
+
+
+- Cleanup the testfiles. [Martin Raspaud]
+
+ Signed-off-by: Martin Raspaud <martin.raspaud at smhi.se>
+
+
+- Add a test to read tle from file. [Martin Raspaud]
+
+ Signed-off-by: Martin Raspaud <martin.raspaud at smhi.se>
+
+
+- Fix doc path in MANIFEST.in. [Martin Raspaud]
+
+ Signed-off-by: Martin Raspaud <martin.raspaud at smhi.se>
+
+
+v0.2.4 (2014-01-07)
+-------------------
+
+- Merge branch 'feature-travis' into pre-master. [Martin Raspaud]
+
+- Add test for tle reading, cleanup and make ready for travis. [Martin
+ Raspaud]
+
+ Signed-off-by: Martin Raspaud <martin.raspaud at smhi.se>
+
+
+- Cleanup. [Martin Raspaud]
+
+ Signed-off-by: Martin Raspaud <martin.raspaud at smhi.se>
+
+
+- Add function to fetch the tle files from internet manually. [Martin
+ Raspaud]
+
+ Signed-off-by: Martin Raspaud <martin.raspaud at smhi.se>
+
+
+- Adding the viirs instrument. [Martin Raspaud]
+
+ Signed-off-by: Martin Raspaud <martin.raspaud at smhi.se>
+
+
+- Change sphinx theme. [Martin Raspaud]
+
+ Signed-off-by: Martin Raspaud <martin.raspaud at smhi.se>
+
+
+- Fix doc for readthedocs. [Martin Raspaud]
+
+ Signed-off-by: Martin Raspaud <martin.raspaud at smhi.se>
+
+
+- Remove unused old file. [Martin Raspaud]
+
+ Signed-off-by: Martin Raspaud <martin.raspaud at smhi.se>
+
+
+- Merge branch 'geoloc' into pre-master. [Martin Raspaud]
+
+- Work on geolocation. [Martin Raspaud]
+
+ Signed-off-by: Martin Raspaud <martin.raspaud at smhi.se>
+
+
+- Numpyze the orbital computation. [Martin Raspaud]
+
+ Signed-off-by: Martin Raspaud <martin.raspaud at smhi.se>
+
+
+- Add some logging in tle file fetching. [Martin Raspaud]
+
+ Signed-off-by: Martin Raspaud <martin.raspaud at smhi.se>
+
+
+- Fix syntax error in doc/conf.py. [Martin Raspaud]
+
+- Make the scan angle of avhrr an argument. [Martin Raspaud]
+
+- Factorize avhrr code (geoloc definition) [Martin Raspaud]
+
+ Signed-off-by: Martin Raspaud <martin.raspaud at smhi.se>
+
+
+- Add Mikhail's definition of AMSU-A. [Martin Raspaud]
+
+- Add instrument examples for geoloc. [Martin Raspaud]
+
+ Signed-off-by: Martin Raspaud <martin.raspaud at smhi.se>
+
+
+- Merge branch 'geoloc' of github.com:mraspaud/pyorbital into geoloc.
+ [Martin Raspaud]
+
+- Try fixing nadir. [Martin Raspaud]
+
+- Fix attitude. [Martin Raspaud]
+
+- Updated doc and copyright. [Martin Raspaud]
+
+- Add geoloc example. [Martin Raspaud]
+
+ Signed-off-by: Martin Raspaud <martin.raspaud at smhi.se>
+
+
+- Merge branch 'feature-vectorize' into geoloc. [Martin Raspaud]
+
+- Vectorize the days function. [Martin Raspaud]
+
+- Merge branch 'master' into geoloc. [Martin Raspaud]
+
+- Merge branch 'pre-master' into geoloc. [Martin Raspaud]
+
+- Cosmetics. [Martin Raspaud]
+
+- Computations for true nadir. [Martin Raspaud]
+
+- Bugfix in the example and added attitude correction (roll and pitch
+ for now). [Martin Raspaud]
+
+ Signed-off-by: Martin Raspaud <martin.raspaud at smhi.se>
+
+
+- Cosmetic, be consistent in name og time argument as 'utc_time' [Lars
+ Orum Rasmussen]
+
+- Get_zenith_overpass replaced by Martin's get_next_passes. [Lars Orum
+ Rasmussen]
+
+- Add sun_earth_distance_correction function. [Martin Raspaud]
+
+v0.2.3 (2013-03-07)
+-------------------
+
+- Merge branch 'release-0.2.3' [Martin Raspaud]
+
+- Merge branch 'pre-master' into release-0.2.3. [Martin Raspaud]
+
+- Bumped up version number. [Martin Raspaud]
+
+- Corrected search for previous an_time with a substracted 10 min. dt.
+ [Esben S. Nielsen]
+
+- Merge branch 'release-0.2.2' [Martin Raspaud]
+
+- Import with_statement in test_aiaa.py for python 2.5 compliance.
+ [Esben S. Nielsen]
+
+- Made unit tests python 2.5 and 2.6 compliant. [Esben S. Nielsen]
+
+- Removed download URL from setup.py. [Esben S. Nielsen]
+
+- Bumped version number and marked as stable. [Esben S. Nielsen]
+
+- Better handling of time deltas in test_aiaa.py. [Esben S. Nielsen]
+
+- Updated equator test with position check. [Esben S. Nielsen]
+
+- Now uses nodal period for orbit number calculation instead of revs/day
+ for mean motion. [Esben S. Nielsen]
+
+- Orbit number now handles epoch AN mis-match. Made AIAA unit test path
+ agnostic. [Esben S. Nielsen]
+
+- Better __main__ [Lars Orum Rasmussen]
+
+- Adding risetime and falltime functions, and improving the
+ get_zenith_overpass function. [Adam Dybbroe]
+
+- Editorial. [Adam Dybbroe]
+
+- Cleanup. [Martin Raspaud]
+
+ Signed-off-by: Martin Raspaud <martin.raspaud at smhi.se>
+
+
+- Feature: Correcting/adding test cases from the aiaa. [Martin Raspaud]
+
+- Style: raises NotImplementedErrors instead of just Exceptions. [Martin
+ Raspaud]
+
+- Merge branch 'pre-master' of github.com:mraspaud/pyorbital into pre-
+ master. [Martin Raspaud]
+
+- Adding new function get_zenith_overpass to get the time when the
+ satellite passes over zenith relative to an observer on ground. [Adam
+ Dybbroe]
+
+- Feature: Added checksum for tle lines. [Martin Raspaud]
+
+v0.2.1 (2012-06-01)
+-------------------
+
+- Updated version number. [Martin Raspaud]
+
+- Added pyorbital path to doc/source/conf.py. [Esben S. Nielsen]
+
+- Updated docs and added license and manifest. [Esben S. Nielsen]
+
+- Merge branch 'pre-master' of https://github.com/mraspaud/pyorbital
+ into pre-master. [Adam Dybbroe]
+
+- Merge branch 'pre-master' of https://github.com/mraspaud/pyorbital
+ into pre-master. [Lars Orum Rasmussen]
+
+- Added access to line1 and line2 in a Tle instance. [Lars Orum
+ Rasmussen]
+
+ Change satellite to platform
+
+
+- Spelling error. [Adam Dybbroe]
+
+v0.2.0 (2012-05-14)
+-------------------
+
+- Prepared for pypi. [Martin Raspaud]
+
+- Merge branch 'geoloc' into pre-master. [Martin Raspaud]
+
+- Added now compute pixels on the ellipsoid, not on the sphere anymore.
+ [Martin Raspaud]
+
+- Merge branch 'master' into geoloc. [Martin Raspaud]
+
+- Updated the geoloc todo list. [Martin Raspaud]
+
+- Added the geoloc module. [Martin Raspaud]
+
+- Merge branch 'master' into pre-master. [Martin Raspaud]
+
+ Conflicts:
+ pyorbital/tlefile.py
+
+
+- Corrected handling of mean motion and orbitnumber fields in
+ tlefiles.py. [Esben S. Nielsen]
+
+- Testing getting the orbit number from the TLEs. [Adam.Dybbroe]
+
+- Fixing bug in tle file reading, so that also NPP and other satellites
+ with orbit numbers less than 9999 can be handled. [Adam.Dybbroe]
+
+- Typo. [Adam.Dybbroe]
+
+- Merge branch 'master' into pre-master. [Martin Raspaud]
+
+- Removed html submodule. [Martin Raspaud]
+
+- Fixing bug in function sun_zenith_angle. Changing interfaces so that
+ all public functions expects lon,lat in degrees. All internal
+ functions us radians. Made the lsmt and local_hour_angle functions
+ private. [Adam.Dybbroe]
+
+- Adding main. [Adam.Dybbroe]
+
+- Gathering unit tests to the tests-directory. [Adam.Dybbroe]
+
+- Added separate test-script for astronomy.py. [Adam.Dybbroe]
+
+- Collected all unit test scripts under the tests directory.
+ [Adam.Dybbroe]
+
+- Merge branch 'release-0.2.0' [Martin Raspaud]
+
+ Conflicts:
+ doc/build
+ setup.py
+
+
+- Bumped version number to 0.2.0. [Martin Raspaud]
+
+- Added html documentation. [Martin Raspaud]
+
+- Corrected sgp4's propagate in the case of array as input, and cleaned
+ up. [Martin Raspaud]
+
+- Fixed calling test_aiaa from another directory. [Martin Raspaud]
+
+- Vectorize merge. [Martin Raspaud]
+
+- Merging master branch. [Martin Raspaud]
+
+- Remove html submodule. [Martin Raspaud]
+
+- Remove html submodule. [Martin Raspaud]
+
+- Added Esben in the author field. [Martin Raspaud]
+
+- Removed unneded .pyc file. [Martin Raspaud]
+
+- Added unittests. [Esben S. Nielsen]
+
+- Corrected observer_look function and added first unittest. [Esben S.
+ Nielsen]
+
+- Corrected observer_pos in astronomy. [Esben S. Nielsen]
+
+- Setting up documentation. [Martin Raspaud]
+
+v0.1.0 (2011-10-03)
+-------------------
+
+- Merge branch 'release-0.1.0' [Martin Raspaud]
+
+- Bumped version number to 0.1.0. [Martin Raspaud]
+
+- Merge branch 'dundee_port' into pre-master. [Martin Raspaud]
+
+- Cleanup and documentation. [Martin Raspaud]
+
+- Now using unittest module for aiaa test cases. [Martin Raspaud]
+
+- Added licences, and removed prints. [Martin Raspaud]
+
+- Added basic tests to pyorbital. [Martin Raspaud]
+
+- Ported SGP4 code to Dundee implementation. [Esben S. Nielsen]
+
+- Ported sgp4 init. [Esben S. Nielsen]
+
+- Added the first unit test :) [Martin Raspaud]
+
+- New gmst function (from AIAA paper). Cleaning. [Martin Raspaud]
+
+- Merged DMI and SMHI versions. [Esben S. Nielsen]
+
+- Made the package more package-like. [Martin Raspaud]
+
+- Cleanup of astronomy file. [Martin Raspaud]
+
+- Added a readme file. [Martin Raspaud]
+
+- Added astronomy.py file. [Martin Raspaud]
+
+
diff --git a/doc/source/conf.py b/doc/source/conf.py
index 5a15784..b753217 100644
--- a/doc/source/conf.py
+++ b/doc/source/conf.py
@@ -11,7 +11,8 @@
# All configuration values have a default; values that are commented out
# serve to show the default.
-import sys, os
+import sys
+import os
# If extensions (or modules to document with autodoc) are in another directory,
# add these directories to sys.path here. If the directory is relative to the
@@ -19,7 +20,7 @@ import sys, os
#sys.path.insert(0, os.path.abspath('.'))
sys.path.insert(0, os.path.abspath('../../'))
sys.path.insert(0, os.path.abspath('../../pyorbital'))
-from pyorbital.version import __version__, __major__, __minor__
+from pyorbital.version import __version__
# -- General configuration -----------------------------------------------------
@@ -44,16 +45,16 @@ master_doc = 'index'
# General information about the project.
project = u'pyorbital'
-copyright = u'2012-2014, The Pytroll crew'
+copyright = u'2012-2015, The Pytroll crew'
# The version info for the project you're documenting, acts as replacement for
# |version| and |release|, also used in various other places throughout the
# built documents.
#
-# The short X.Y version.
-version = "v" + ".".join([__major__, __minor__])
# The full version, including alpha/beta/rc tags.
release = __version__
+# The short X.Y version.
+version = ".".join(release.split(".")[:2])
# The language for content autogenerated by Sphinx. Refer to documentation
# for a list of supported languages.
@@ -181,8 +182,8 @@ htmlhelp_basename = 'pyorbitaldoc'
# Grouping the document tree into LaTeX files. List of tuples
# (source start file, target name, title, author, documentclass [howto/manual]).
latex_documents = [
- ('index', 'pyorbital.tex', u'pyorbital Documentation',
- u'The Pytroll crew', 'manual'),
+ ('index', 'pyorbital.tex', u'pyorbital Documentation',
+ u'The Pytroll crew', 'manual'),
]
# The name of an image file (relative to this directory) to place at the top of
diff --git a/etc/platforms.txt b/etc/platforms.txt
new file mode 100644
index 0000000..6fc5250
--- /dev/null
+++ b/etc/platforms.txt
@@ -0,0 +1,60 @@
+# The platform numbers are given in a file $PPP_CONFIG_DIR/platforms.txt
+# in the following format. Copy this file to $PPP_CONFIG_DIR
+#
+# Mappings between satellite catalogue numbers and corresponding
+# platform names from OSCAR.
+ALOS-2 39766
+CloudSat 29107
+CryoSat-2 36508
+CSK-1 31598
+CSK-2 32376
+CSK-3 33412
+CSK-4 37216
+DMSP-F15 25991
+DMSP-F16 28054
+DMSP-F17 29522
+DMSP-F18 35951
+DMSP-F19 39630
+EOS-Aqua 27424
+EOS-Aura 28376
+EOS-Terra 25994
+FY-2D 29640
+FY-2E 33463
+FY-2F 38049
+FY-2G 40367
+FY-3A 32958
+FY-3B 37214
+FY-3C 39260
+GOES-13 29155
+GOES-14 35491
+GOES-15 36411
+Himawari-6 28622
+Himawari-7 28937
+Himawari-8 40267
+INSAT-3A 27714
+INSAT-3C 27298
+INSAT-3D 39216
+JASON-2 33105
+Kalpana-1 27525
+Landsat-7 25682
+Landsat-8 39084
+Meteosat-7 24932
+Meteosat-8 27509
+Meteosat-9 28912
+Meteosat-10 38552
+Metop-A 29499
+Metop-B 38771
+NOAA-15 25338
+NOAA-16 26536
+NOAA-17 27453
+NOAA-18 28654
+NOAA-19 33591
+RadarSat-2 32382
+Sentinel-1A 39634
+SMOS 36036
+SPOT-5 27421
+SPOT-6 38755
+SPOT-7 40053
+Suomi-NPP 37849
+TanDEM-X 36605
+TerraSAR-X 31698
diff --git a/pyorbital/geoloc.py b/pyorbital/geoloc.py
index c543e6a..7ec4932 100644
--- a/pyorbital/geoloc.py
+++ b/pyorbital/geoloc.py
@@ -1,7 +1,7 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
-# Copyright (c) 2011, 2012, 2013, 2014.
+# Copyright (c) 2011, 2012, 2013, 2014, 2015.
# Author(s):
@@ -34,44 +34,46 @@ from numpy import cos, sin, sqrt
from datetime import timedelta
from pyorbital.orbital import Orbital
-a = 6378.137 # km
-b = 6356.75231414 # km, GRS80
-#b = 6356.752314245 # km, WGS84
+a = 6378.137 # km
+b = 6356.75231414 # km, GRS80
+# b = 6356.752314245 # km, WGS84
+
def geodetic_lat(point, a=a, b=b):
x, y, z = point
- r = np.sqrt(x*x + y*y)
+ r = np.sqrt(x * x + y * y)
geoc_lat = np.arctan2(z, r)
geod_lat = geoc_lat
- e2 = (a*a - b*b) / (a*a)
+ e2 = (a * a - b * b) / (a * a)
while True:
phi = geod_lat
- C = 1 / sqrt(1 - e2 * sin(phi)**2)
- geod_lat = np.arctan2(z + a*C*e2 * sin(phi), r)
+ C = 1 / sqrt(1 - e2 * sin(phi)**2)
+ geod_lat = np.arctan2(z + a * C * e2 * sin(phi), r)
if np.allclose(geod_lat, phi):
return geod_lat
-
+
def subpoint(query_point, a=a, b=b):
"""Get the point on the ellipsoid under the *query_point*.
"""
x, y, z = query_point
- r = sqrt(x*x + y*y)
+ r = sqrt(x * x + y * y)
lat = geodetic_lat(query_point)
lon = np.arctan2(y, x)
- e2_ = (a*a - b*b) / (a*a)
+ e2_ = (a * a - b * b) / (a * a)
n__ = a / sqrt(1 - e2_ * sin(lat)**2)
nx_ = n__ * cos(lat) * cos(lon)
ny_ = n__ * cos(lat) * sin(lon)
- nz_ = (1-e2_) * n__ * sin(lat)
+ nz_ = (1 - e2_) * n__ * sin(lat)
return np.vstack([nx_, ny_, nz_])
-
+
class ScanGeometry(object):
+
"""Description of the geometry of an instrument.
*fovs* is the x and y viewing angles of the instrument. y is zero if the we
@@ -95,8 +97,7 @@ class ScanGeometry(object):
vectors. Returns vectors as stacked rows.
"""
# TODO: yaw steering mode !
-
-
+
# Fake nadir: This is the intersection point between the satellite
# looking down at the centre of the ellipsoid and the surface of the
# ellipsoid. Nadir on the other hand is the point which vertical goes
@@ -105,7 +106,7 @@ class ScanGeometry(object):
nadir = subpoint(-pos)
nadir /= vnorm(nadir)
-
+
# x is along track (roll)
x = vel / vnorm(vel)
@@ -124,6 +125,7 @@ class ScanGeometry(object):
tds = [timedelta(seconds=i) for i in self._times]
return np.array(tds) + start_of_scan
+
class Quaternion(object):
def __init__(self, scalar, vector):
@@ -135,19 +137,20 @@ class Quaternion(object):
zero = np.zeros_like(x)
return np.array(
((w**2 + x**2 - y**2 - z**2,
- 2*x*y + 2*z*w,
- 2*x*z - 2*y*w,
+ 2 * x * y + 2 * z * w,
+ 2 * x * z - 2 * y * w,
zero),
- (2*x*y - 2*z*w,
+ (2 * x * y - 2 * z * w,
w**2 - x**2 + y**2 - z**2,
- 2*y*z + 2*x*w,
+ 2 * y * z + 2 * x * w,
zero),
- (2*x*z + 2*y*w,
- 2*y*z - 2*x*w,
+ (2 * x * z + 2 * y * w,
+ 2 * y * z - 2 * x * w,
w**2 - x**2 - y**2 + z**2,
zero),
(zero, zero, zero, w**2 + x**2 + y**2 + z**2)))
+
def qrotate(vector, axis, angle):
"""Rotate *vector* around *axis* by *angle* (in radians).
@@ -155,25 +158,25 @@ def qrotate(vector, axis, angle):
This function uses quaternion rotation.
"""
n_axis = axis / vnorm(axis)
- sin_angle = np.expand_dims(sin(angle/2), 0)
- if np.rank(n_axis)==1:
+ sin_angle = np.expand_dims(sin(angle / 2), 0)
+ if np.rank(n_axis) == 1:
n_axis = np.expand_dims(n_axis, 1)
p__ = np.dot(n_axis, sin_angle)[:, np.newaxis]
else:
p__ = n_axis * sin_angle
- q__ = Quaternion(cos(angle/2), p__)
+ q__ = Quaternion(cos(angle / 2), p__)
return np.einsum("kj, ikj->ij",
vector,
q__.rotation_matrix()[:3, :3])
-
-### DIRTY STUFF. Needed the get_lonlatalt function to work on pos directly if
-### we want to print out lonlats in the end.
+# DIRTY STUFF. Needed the get_lonlatalt function to work on pos directly if
+# we want to print out lonlats in the end.
from pyorbital import astronomy
from pyorbital.orbital import *
+
def get_lonlatalt(pos, utc_time):
"""Calculate sublon, sublat and altitude of satellite, considering the
earth an ellipsoid.
@@ -185,7 +188,7 @@ def get_lonlatalt(pos, utc_time):
% (2 * np.pi))
lon = np.where(lon > np.pi, lon - np.pi * 2, lon)
- lon = np.where(lon <= -np.pi, lon + np.pi *2, lon)
+ lon = np.where(lon <= -np.pi, lon + np.pi * 2, lon)
r = np.sqrt(pos_x ** 2 + pos_y ** 2)
lat = np.arctan2(pos_z, r)
@@ -193,15 +196,17 @@ def get_lonlatalt(pos, utc_time):
while True:
lat2 = lat
- c = 1/(np.sqrt(1 - e2 * (np.sin(lat2) ** 2)))
- lat = np.arctan2(pos_z + c * e2 *np.sin(lat2), r)
+ c = 1 / (np.sqrt(1 - e2 * (np.sin(lat2) ** 2)))
+ lat = np.arctan2(pos_z + c * e2 * np.sin(lat2), r)
if np.all(abs(lat - lat2) < 1e-10):
break
- alt = r / np.cos(lat)- c
+ alt = r / np.cos(lat) - c
alt *= A
return np.rad2deg(lon), np.rad2deg(lat), alt
-### END OF DIRTY STUFF
+# END OF DIRTY STUFF
+
+
def compute_pixels((tle1, tle2), sgeom, times, rpy=(0.0, 0.0, 0.0)):
"""Compute cartesian coordinates of the pixels in instrument scan.
"""
@@ -213,18 +218,17 @@ def compute_pixels((tle1, tle2), sgeom, times, rpy=(0.0, 0.0, 0.0)):
# now, get the vectors pointing to each pixel
vectors = sgeom.vectors(pos, vel, *rpy)
- ## compute intersection of lines (directed by vectors and passing through
- ## (0, 0, 0)) and ellipsoid. Derived from:
- ## http://en.wikipedia.org/wiki/Line%E2%80%93sphere_intersection
-
+ # compute intersection of lines (directed by vectors and passing through
+ # (0, 0, 0)) and ellipsoid. Derived from:
+ # http://en.wikipedia.org/wiki/Line%E2%80%93sphere_intersection
# do the computation between line and ellipsoid (WGS 84)
# NB: AAPP uses GRS 80...
centre = -pos
- a__ = 6378.137 # km
- #b__ = 6356.75231414 # km, GRS80
- b__ = 6356.752314245 # km, WGS84
- radius = np.array([[1/a__, 1/a__, 1/b__]]).T
+ a__ = 6378.137 # km
+ # b__ = 6356.75231414 # km, GRS80
+ b__ = 6356.752314245 # km, WGS84
+ radius = np.array([[1 / a__, 1 / a__, 1 / b__]]).T
xr_ = vectors * radius
cr_ = centre * radius
ldotc = np.einsum("ij,ij->j", xr_, cr_)
@@ -233,14 +237,14 @@ def compute_pixels((tle1, tle2), sgeom, times, rpy=(0.0, 0.0, 0.0)):
d1_ = (ldotc - np.sqrt(ldotc ** 2 - csq * lsq + lsq)) / lsq
-
# return the actual pixel positions
return vectors * d1_ - centre
-
+
def norm(v):
return np.sqrt(np.dot(v, v.conj()))
+
def mnorm(m, axis=None):
"""norm of a matrix of vectors stacked along the *axis* dimension.
"""
@@ -248,35 +252,36 @@ def mnorm(m, axis=None):
axis = np.rank(m) - 1
return np.sqrt((m**2).sum(axis))
+
def vnorm(m):
"""norms of a matrix of column vectors.
"""
return np.sqrt((m**2).sum(0))
+
+
def hnorm(m):
"""norms of a matrix of row vectors.
"""
return np.sqrt((m**2).sum(1))
if __name__ == '__main__':
- #NOAA 18 (from the 2011-10-12, 16:55 utc)
- #1 28654U 05018A 11284.35271227 .00000478 00000-0 28778-3 0 9246
- #2 28654 99.0096 235.8581 0014859 135.4286 224.8087 14.11526826329313
-
-
+ # NOAA 18 (from the 2011-10-12, 16:55 utc)
+ # 1 28654U 05018A 11284.35271227 .00000478 00000-0 28778-3 0 9246
+ # 2 28654 99.0096 235.8581 0014859 135.4286 224.8087 14.11526826329313
+
noaa18_tle1 = "1 28654U 05018A 11284.35271227 .00000478 00000-0 28778-3 0 9246"
noaa18_tle2 = "2 28654 99.0096 235.8581 0014859 135.4286 224.8087 14.11526826329313"
from datetime import datetime
t = datetime(2011, 10, 12, 13, 45)
- ## edge and centre of an avhrr scanline
- #sgeom = ScanGeometry([(-0.9664123687741623, 0),
+ # edge and centre of an avhrr scanline
+ # sgeom = ScanGeometry([(-0.9664123687741623, 0),
# (0, 0)],
# [0, 0.0, ])
- #print compute_pixels((noaa18_tle1, noaa18_tle2), sgeom, t)
-
+ # print compute_pixels((noaa18_tle1, noaa18_tle2), sgeom, t)
- ## avhrr swath
+ # avhrr swath
scanline_nb = 1
# building the avhrr angles, 2048 pixels from +55.37 to -55.37 degrees
diff --git a/pyorbital/geoloc_instrument_definitions.py b/pyorbital/geoloc_instrument_definitions.py
index c7070c4..60bac0d 100644
--- a/pyorbital/geoloc_instrument_definitions.py
+++ b/pyorbital/geoloc_instrument_definitions.py
@@ -1,7 +1,7 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
-# Copyright (c) 2013, 2014 Martin Raspaud
+# Copyright (c) 2013, 2014, 2015 Martin Raspaud
# Author(s):
@@ -39,16 +39,15 @@ Both scan angles and scan times are then combined into a ScanGeometry object.
import numpy as np
from pyorbital.geoloc import ScanGeometry
-# number of instrument scans to use.
-scans_nb = 10
-
################################################################
#
# AVHRR
#
################################################################
-def avhrr(scans_nb, scan_points, scan_angle=55.37, decimate=1):
+
+def avhrr(scans_nb, scan_points,
+ scan_angle=55.37, frequency=1 / 6.0):
"""Definition of the avhrr instrument.
Source: NOAA KLM User's Guide, Appendix J
@@ -61,41 +60,71 @@ def avhrr(scans_nb, scan_points, scan_angle=55.37, decimate=1):
avhrr_inst = np.tile(avhrr_inst, [scans_nb, 1])
# building the corresponding times array
- offset = np.arange(scans_nb) * decimate / 6.0
- #times = (np.tile(scan_points * 0.000025 + 0.0025415, [scans_nb, 1])
+ offset = np.arange(scans_nb) * frequency
+ # times = (np.tile(scan_points * 0.000025 + 0.0025415, [scans_nb, 1])
# + np.expand_dims(offset, 1))
times = (np.tile(scan_points * 0.000025, [scans_nb, 1])
+ np.expand_dims(offset, 1))
return ScanGeometry(avhrr_inst, times.ravel())
-################################################################
-#### avhrr, all pixels
-# we take all pixels
-scan_points = np.arange(2048)
+def avhrr_gac(scan_times, scan_points,
+ scan_angle=55.37, frequency=0.5):
+ """Definition of the avhrr instrument, gac version
-# build the scan geometry object
-avhrr_all_geom = avhrr(scans_nb, scan_points)
+ Source: NOAA KLM User's Guide, Appendix J
+ http://www.ncdc.noaa.gov/oa/pod-guide/ncdc/docs/klm/html/j/app-j.htm
+ """
+ try:
+ offset = np.array([(t - scan_times[0]).seconds +
+ (t - scan_times[0]).microseconds / 1000000.0 for t in scan_times])
+ except TypeError:
+ offset = np.arange(scan_times) * frequency
+ scans_nb = len(offset)
+ # build the avhrr instrument (scan angles)
+ avhrr_inst = np.vstack(((scan_points / 1023.5 - 1)
+ * np.deg2rad(-scan_angle),
+ np.zeros((len(scan_points),)))).transpose()
+ avhrr_inst = np.tile(avhrr_inst, [scans_nb, 1])
+
+ # building the corresponding times array
+ times = (np.tile(scan_points * 0.000025, [scans_nb, 1])
+ + np.expand_dims(offset, 1))
+ return ScanGeometry(avhrr_inst, times.ravel())
################################################################
-#### avhrr, edge pixels
+# avhrr, all pixels
-# we take only edge pixels
-scan_points = np.array([0, 2047])
+# build the scan geometry object
-# build the scan geometry object
-avhrr_edge_geom = avhrr(scans_nb, scan_points)
+def avhrr_all_geom(scans_nb):
+ # we take all pixels
+ scan_points = np.arange(2048)
+ return avhrr(scans_nb, scan_points)
################################################################
-#### avhrr, every 40th pixel from the 24th (aapp style)
+# avhrr, edge pixels
+
+# build the scan geometry object
+
-# we take only every 40th pixel
-scan_points = np.arange(24, 2048, 40)
+def avhrr_edge_geom(scans_nb):
+ # we take only edge pixels
+ scan_points = np.array([0, 2047])
+ return avhrr(scans_nb, scan_points)
+
+################################################################
+# avhrr, every 40th pixel from the 24th (aapp style)
# build the scan geometry object
-avhrr_40_geom = avhrr(scans_nb, scan_points)
+
+
+def avhrr_40_geom(scans_nb):
+ # we take only every 40th pixel
+ scan_points = np.arange(24, 2048, 40)
+ return avhrr(scans_nb, scan_points)
################################################################
#
@@ -103,6 +132,7 @@ avhrr_40_geom = avhrr(scans_nb, scan_points)
#
################################################################
+
def viirs(scans_nb, scan_indices=slice(0, None)):
"""Describe VIIRS instrument geometry, I-band.
@@ -110,18 +140,18 @@ def viirs(scans_nb, scan_indices=slice(0, None)):
entire_width = np.arange(6400)
scan_points = entire_width[scan_indices]
-
+
across_track = (scan_points / 3199.5 - 1) * np.deg2rad(-55.84)
- y_max_angle = np.arctan2(11.87/2, 824.0)
+ y_max_angle = np.arctan2(11.87 / 2, 824.0)
along_track = np.array([-y_max_angle, 0, y_max_angle])
scan_pixels = len(scan_points)
scan = np.vstack((np.tile(across_track, scan_pixels),
np.repeat(along_track, 6400))).T
-
+
npp = np.tile(scan, [scans_nb, 1])
-
+
# from the timestamp in the filenames, a granule takes 1:25.400 to record
# (85.4 seconds) so 1.779166667 would be the duration of 1 scanline
# dividing the duration of a single scan by a width of 6400 pixels results
@@ -149,23 +179,23 @@ def viirs(scans_nb, scan_indices=slice(0, None)):
def amsua(scans_nb, edges_only=False):
""" Describe AMSU-A instrument geometry
-
+
Parameters:
scans_nb | int - number of scan lines
-
+
Keywords:
* edges_only - use only edge pixels
Returns:
pyorbital.geoloc.ScanGeometry object
-
+
"""
- scan_len = 30 # 30 samples per scan
- scan_rate = 8 # single scan, seconds
- scan_angle = -48.3 # swath, degrees
- sampling_interval = 0.2 # single view, seconds
- sync_time = 0.00355 # delay before the actual scan starts
+ scan_len = 30 # 30 samples per scan
+ scan_rate = 8 # single scan, seconds
+ scan_angle = -48.3 # swath, degrees
+ sampling_interval = 0.2 # single view, seconds
+ sync_time = 0.00355 # delay before the actual scan starts
if edges_only:
scan_points = np.array([0, scan_len - 1])
@@ -173,7 +203,7 @@ def amsua(scans_nb, edges_only=False):
scan_points = np.arange(0, scan_len)
# build the instrument (scan angles)
- samples = np.vstack(((scan_points / (scan_len*0.5-0.5) - 1)
+ samples = np.vstack(((scan_points / (scan_len * 0.5 - 0.5) - 1)
* np.deg2rad(scan_angle),
np.zeros((len(scan_points),)))).transpose()
samples = np.tile(samples, [scans_nb, 1])
@@ -181,7 +211,7 @@ def amsua(scans_nb, edges_only=False):
# building the corresponding times array
offset = np.arange(scans_nb) * scan_rate
times = (np.tile(scan_points * sampling_interval + sync_time, [scans_nb, 1])
- + np.expand_dims(offset, 1))
+ + np.expand_dims(offset, 1))
# build the scan geometry object
return ScanGeometry(samples, times.ravel())
diff --git a/pyorbital/orbital.py b/pyorbital/orbital.py
index 5409579..41b0201 100644
--- a/pyorbital/orbital.py
+++ b/pyorbital/orbital.py
@@ -31,9 +31,9 @@ from pyorbital import tlefile
from pyorbital import astronomy
import warnings
-ECC_EPS = 1.0e-6 # Too low for computing further drops.
+ECC_EPS = 1.0e-6 # Too low for computing further drops.
ECC_LIMIT_LOW = -1.0e-3
-ECC_LIMIT_HIGH = 1.0 - ECC_EPS # Too close to 1
+ECC_LIMIT_HIGH = 1.0 - ECC_EPS # Too close to 1
ECC_ALL = 1.0e-4
EPS_COS = 1.5e-12
@@ -54,8 +54,8 @@ XMNPDA = 1440.0
AE = 1.0
SECDAY = 8.6400E4
-F = 1 / 298.257223563 # Earth flattening WGS-84
-A = 6378.137 # WGS84 Equatorial radius
+F = 1 / 298.257223563 # Earth flattening WGS-84
+A = 6378.137 # WGS84 Equatorial radius
SGDP4_ZERO_ECC = 0
@@ -66,11 +66,13 @@ SGDP4_NEAR_NORM = 3
KS = AE * (1.0 + S0 / XKMPER)
A3OVK2 = (-XJ3 / CK2) * AE**3
+
class OrbitalError(Exception):
pass
class Orbital(object):
+
"""Class for orbital computations.
The *satellite* parameter is the name of the satellite to work on and is
@@ -138,29 +140,29 @@ class Orbital(object):
return pos, vel
-
def get_lonlatalt(self, utc_time):
"""Calculate sublon, sublat and altitude of satellite.
http://celestrak.com/columns/v02n03/
"""
- (pos_x, pos_y, pos_z), (vel_x, vel_y, vel_z) = self.get_position(utc_time, normalize=True)
+ (pos_x, pos_y, pos_z), (vel_x, vel_y, vel_z) = self.get_position(
+ utc_time, normalize=True)
lon = ((np.arctan2(pos_y * XKMPER, pos_x * XKMPER) - astronomy.gmst(utc_time))
% (2 * np.pi))
lon = np.where(lon > np.pi, lon - np.pi * 2, lon)
- lon = np.where(lon <= -np.pi, lon + np.pi *2, lon)
+ lon = np.where(lon <= -np.pi, lon + np.pi * 2, lon)
r = np.sqrt(pos_x ** 2 + pos_y ** 2)
lat = np.arctan2(pos_z, r)
e2 = F * (2 - F)
while True:
lat2 = lat
- c = 1/(np.sqrt(1 - e2 * (np.sin(lat2) ** 2)))
- lat = np.arctan2(pos_z + c * e2 *np.sin(lat2), r)
+ c = 1 / (np.sqrt(1 - e2 * (np.sin(lat2) ** 2)))
+ lat = np.arctan2(pos_z + c * e2 * np.sin(lat2), r)
if np.all(abs(lat - lat2) < 1e-10):
break
- alt = r / np.cos(lat)- c;
+ alt = r / np.cos(lat) - c
alt *= A
return np.rad2deg(lon), np.rad2deg(lat), alt
@@ -181,9 +183,10 @@ class Orbital(object):
Return: (Azimuth, Elevation)
"""
- (pos_x, pos_y, pos_z), (vel_x, vel_y, vel_z) = self.get_position(utc_time, normalize=False)
+ (pos_x, pos_y, pos_z), (vel_x, vel_y, vel_z) = self.get_position(
+ utc_time, normalize=False)
(opos_x, opos_y, opos_z), (ovel_x, ovel_y, ovel_z) = \
- astronomy.observer_position(utc_time, lon, lat, alt)
+ astronomy.observer_position(utc_time, lon, lat, alt)
lon = np.deg2rad(lon)
lat = np.deg2rad(lat)
@@ -199,9 +202,11 @@ class Orbital(object):
sin_theta = np.sin(theta)
cos_theta = np.cos(theta)
- top_s = sin_lat * cos_theta * rx + sin_lat * sin_theta * ry - cos_lat * rz
+ top_s = sin_lat * cos_theta * rx + \
+ sin_lat * sin_theta * ry - cos_lat * rz
top_e = -sin_theta * rx + cos_theta * ry
- top_z = cos_lat * cos_theta * rx + cos_lat * sin_theta * ry + sin_lat * rz
+ top_z = cos_lat * cos_theta * rx + \
+ cos_lat * sin_theta * ry + sin_lat * rz
az_ = np.arctan(-top_e / top_s)
@@ -222,31 +227,31 @@ class Orbital(object):
orbit_period = astronomy._days(self.orbit_elements.an_period)
except AttributeError:
pos_epoch, vel_epoch = self.get_position(self.tle.epoch,
- normalize=False)
+ normalize=False)
if np.abs(pos_epoch[2]) > 1 or not vel_epoch[2] > 0:
# Epoch not at ascending node
- self.orbit_elements.an_time = self.get_last_an_time(self.tle.epoch)
+ self.orbit_elements.an_time = self.get_last_an_time(
+ self.tle.epoch)
else:
# Epoch at ascending node (z < 1 km) and positive v_z
self.orbit_elements.an_time = self.tle.epoch
self.orbit_elements.an_period = self.orbit_elements.an_time - \
- self.get_last_an_time(self.orbit_elements.an_time
- - timedelta(minutes=10))
+ self.get_last_an_time(self.orbit_elements.an_time
+ - timedelta(minutes=10))
dt = astronomy._days(utc_time - self.orbit_elements.an_time)
orbit_period = astronomy._days(self.orbit_elements.an_period)
-
orbit = int(self.tle.orbit + dt / orbit_period +
- self.tle.mean_motion_derivative * dt**2 +
- self.tle.mean_motion_sec_derivative * dt**3)
+ self.tle.mean_motion_derivative * dt**2 +
+ self.tle.mean_motion_sec_derivative * dt**3)
if tbus_style:
orbit += 1
return orbit
- def get_next_passes(self, utc_time, length, lon, lat, alt, tol=0.001):
+ def get_next_passes(self, utc_time, length, lon, lat, alt, tol=0.001, horizon=0):
"""Calculate passes for the next hours for a given start time and a
given observer.
@@ -258,6 +263,7 @@ class Orbital(object):
lat: Latitude of observer position on ground (float)
alt: Altitude above sea-level (geoid) of observer position on ground (float)
tol: precision of the result in seconds
+ horizon: the elevation of horizon to compute risetime and falltime.
Return: [(rise-time, fall-time, max-elevation-time), ...]
"""
@@ -266,8 +272,10 @@ class Orbital(object):
"""elevation
"""
return self.get_observer_look(utc_time +
- timedelta(minutes=minutes),
- lon, lat, alt)[1]
+ timedelta(
+ minutes=np.float64(minutes)),
+ lon, lat, alt)[1] - horizon
+
def elevation_inv(minutes):
"""inverse of elevation
"""
@@ -305,7 +313,7 @@ class Orbital(object):
while abs(c - a) > tol:
x = b - 0.5 * (((b - a) ** 2 * (f_b - f_c)
- (b - c) ** 2 * (f_b - f_a)) /
- ((b - a) * (f_b - f_c) - (b - c) * (f_b - f_a)))
+ ((b - a) * (f_b - f_c) - (b - c) * (f_b - f_a)))
f_x = fun(x)
if x > b:
a, b, c = b, x, c
@@ -317,15 +325,16 @@ class Orbital(object):
return x
times = utc_time + np.array([timedelta(minutes=minutes)
- for minutes in range(length * 60)])
- elev = self.get_observer_look(times, lon, lat, alt)[1]
+ for minutes in range(length * 60)])
+ elev = self.get_observer_look(times, lon, lat, alt)[1] - horizon
zcs = np.where(np.diff(np.sign(elev)))[0]
res = []
risetime = None
falltime = None
for guess in zcs:
- horizon_mins = get_root_secant(elevation, guess, guess + 1.0, tol=tol/60.0)
+ horizon_mins = get_root_secant(
+ elevation, guess, guess + 1.0, tol=tol / 60.0)
horizon_time = utc_time + timedelta(minutes=horizon_mins)
if elev[guess] < 0:
risetime = horizon_time
@@ -338,9 +347,9 @@ class Orbital(object):
middle = (risemins + fallmins) / 2.0
highest = utc_time + \
timedelta(minutes=get_max_parab(
- elevation_inv,
- middle - 0.1, middle + 0.1,
- tol=tol/60.0
+ elevation_inv,
+ middle - 0.1, middle + 0.1,
+ tol=tol / 60.0
))
res += [(risetime, falltime, highest)]
risetime = None
@@ -366,7 +375,7 @@ class Orbital(object):
nmax_iter = 100
sec_step = 0.5
- t_step = timedelta(seconds=sec_step/2.0)
+ t_step = timedelta(seconds=sec_step / 2.0)
# Local derivative:
def fprime(timex):
@@ -389,9 +398,9 @@ class Orbital(object):
#var_scale = np.abs(np.sin(fpr[0] * np.pi/180.))
#var_scale = np.sqrt(var_scale)
var_scale = np.abs(fpr[0])
- tx1 = tx0 - timedelta(seconds = (eps * var_scale * fpr[1]))
+ tx1 = tx0 - timedelta(seconds=(eps * var_scale * fpr[1]))
idx = idx + 1
- #print idx, tx0, tx1, var_scale, fpr
+ # print idx, tx0, tx1, var_scale, fpr
if abs(tx1 - utc_time) < precision and idx < 2:
tx1 = tx1 + timedelta(seconds=1.0)
@@ -400,7 +409,9 @@ class Orbital(object):
else:
return None
+
class OrbitElements(object):
+
"""Class holding the orbital elements.
"""
@@ -413,8 +424,10 @@ class OrbitElements(object):
self.mean_anomaly = np.deg2rad(tle.mean_anomaly)
self.mean_motion = tle.mean_motion * (np.pi * 2 / XMNPDA)
- self.mean_motion_derivative = tle.mean_motion_derivative * np.pi * 2 / XMNPDA ** 2
- self.mean_motion_sec_derivative = tle.mean_motion_sec_derivative * np.pi * 2 / XMNPDA ** 3
+ self.mean_motion_derivative = tle.mean_motion_derivative * \
+ np.pi * 2 / XMNPDA ** 2
+ self.mean_motion_sec_derivative = tle.mean_motion_sec_derivative * \
+ np.pi * 2 / XMNPDA ** 3
self.bstar = tle.bstar * AE
n_0 = self.mean_motion
@@ -423,14 +436,14 @@ class OrbitElements(object):
i_0 = self.inclination
e_0 = self.excentricity
- a_1 = (k_e / n_0) ** (2.0/3)
- delta_1 = ((3/2.0) * (k_2 / a_1**2) * ((3 * np.cos(i_0)**2 - 1) /
- (1 - e_0**2)**(2.0/3)))
+ a_1 = (k_e / n_0) ** (2.0 / 3)
+ delta_1 = ((3 / 2.0) * (k_2 / a_1**2) * ((3 * np.cos(i_0)**2 - 1) /
+ (1 - e_0**2)**(2.0 / 3)))
- a_0 = a_1 * (1 - delta_1/3 - delta_1**2 - (134.0/81) * delta_1**3)
+ a_0 = a_1 * (1 - delta_1 / 3 - delta_1**2 - (134.0 / 81) * delta_1**3)
- delta_0 = ((3/2.0) * (k_2 / a_0**2) * ((3 * np.cos(i_0)**2 - 1) /
- (1 - e_0**2)**(2.0/3)))
+ delta_0 = ((3 / 2.0) * (k_2 / a_0**2) * ((3 * np.cos(i_0)**2 - 1) /
+ (1 - e_0**2)**(2.0 / 3)))
# original mean motion
n_0pp = n_0 / (1 + delta_0)
@@ -445,13 +458,14 @@ class OrbitElements(object):
self.perigee = (a_0pp * (1 - e_0) / AE - AE) * XKMPER
self.right_ascension_lon = (self.right_ascension
- - astronomy.gmst(self.epoch))
+ - astronomy.gmst(self.epoch))
if self.right_ascension_lon > np.pi:
self.right_ascension_lon -= 2 * np.pi
class _SGDP4(object):
+
"""Class for the SGDP4 computations.
"""
@@ -497,7 +511,8 @@ class _SGDP4(object):
betao = np.sqrt(betao2)
temp0 = 1.5 * CK2 * self.x3thm1 / (betao * betao2)
del1 = temp0 / (a1**2)
- a0 = a1 * (1.0 - del1 * (1.0 / 3.0 + del1 * (1.0 + del1 * 134.0 / 81.0)))
+ a0 = a1 * \
+ (1.0 - del1 * (1.0 / 3.0 + del1 * (1.0 + del1 * 134.0 / 81.0)))
del0 = temp0 / (a0**2)
self.xnodp = self.xn_0 / (1.0 + del0)
self.aodp = (a0 / (1.0 - del0))
@@ -536,27 +551,27 @@ class _SGDP4(object):
coef_1 = coef / psisq**3.5
self.c2 = (coef_1 * self.xnodp * (self.aodp *
- (1.0 + 1.5 * etasq + eeta * (4.0 + etasq)) +
- (0.75 * CK2) * tsi / psisq * self.x3thm1 *
- (8.0 + 3.0 * etasq * (8.0 + etasq))))
+ (1.0 + 1.5 * etasq + eeta * (4.0 + etasq)) +
+ (0.75 * CK2) * tsi / psisq * self.x3thm1 *
+ (8.0 + 3.0 * etasq * (8.0 + etasq))))
self.c1 = self.bstar * self.c2
self.c4 = (2.0 * self.xnodp * coef_1 * self.aodp * betao2 * (self.eta *
- (2.0 + 0.5 * etasq) + self.eo * (0.5 + 2.0 *
- etasq) - (2.0 * CK2) * tsi / (self.aodp * psisq) * (-3.0 *
- self.x3thm1 * (1.0 - 2.0 * eeta + etasq *
- (1.5 - 0.5 * eeta)) + 0.75 * self.x1mth2 * (2.0 *
- etasq - eeta * (1.0 + etasq)) * np.cos(2.0 * self.omegao))))
+ (2.0 + 0.5 * etasq) + self.eo * (0.5 + 2.0 *
+ etasq) - (2.0 * CK2) * tsi / (self.aodp * psisq) * (-3.0 *
+ self.x3thm1 * (1.0 - 2.0 * eeta + etasq *
+ (1.5 - 0.5 * eeta)) + 0.75 * self.x1mth2 * (2.0 *
+ etasq - eeta * (1.0 + etasq)) * np.cos(2.0 * self.omegao))))
self.c5, self.c3, self.omgcof = 0.0, 0.0, 0.0
-
if self.mode == SGDP4_NEAR_NORM:
self.c5 = (2.0 * coef_1 * self.aodp * betao2 *
- (1.0 + 2.75 * (etasq + eeta) + eeta * etasq))
+ (1.0 + 2.75 * (etasq + eeta) + eeta * etasq))
if self.eo > ECC_ALL:
- self.c3 = coef * tsi * A3OVK2 * self.xnodp * AE * self.sinIO / self.eo
+ self.c3 = coef * tsi * A3OVK2 * \
+ self.xnodp * AE * self.sinIO / self.eo
self.omgcof = self.bstar * self.c3 * np.cos(self.omegao)
temp1 = 3.0 * CK2 * pinvsq * self.xnodp
@@ -564,18 +579,18 @@ class _SGDP4(object):
temp3 = 1.25 * CK4 * pinvsq**2 * self.xnodp
self.xmdot = (self.xnodp + (0.5 * temp1 * betao * self.x3thm1 + 0.0625 *
- temp2 * betao * (13.0 - 78.0 * theta2 +
- 137.0 * theta4)))
+ temp2 * betao * (13.0 - 78.0 * theta2 +
+ 137.0 * theta4)))
x1m5th = 1.0 - 5.0 * theta2
self.omgdot = (-0.5 * temp1 * x1m5th + 0.0625 * temp2 *
- (7.0 - 114.0 * theta2 + 395.0 * theta4) +
- temp3 * (3.0 - 36.0 * theta2 + 49.0 * theta4))
+ (7.0 - 114.0 * theta2 + 395.0 * theta4) +
+ temp3 * (3.0 - 36.0 * theta2 + 49.0 * theta4))
xhdot1 = -temp1 * self.cosIO
self.xnodot = (xhdot1 + (0.5 * temp2 * (4.0 - 19.0 * theta2) +
- 2.0 * temp3 * (3.0 - 7.0 * theta2)) * self.cosIO)
+ 2.0 * temp3 * (3.0 - 7.0 * theta2)) * self.cosIO)
if self.eo > ECC_ALL:
self.xmcof = (-(2. / 3) * AE) * coef * self.bstar / eeta
@@ -585,12 +600,14 @@ class _SGDP4(object):
self.xnodcf = 3.5 * betao2 * xhdot1 * self.c1
self.t2cof = 1.5 * self.c1
- # Check for possible divide-by-zero for X/(1+cos(xincl)) when calculating xlcof */
- temp0 = 1.0 + self.cosIO
- if np.abs(temp0) < EPS_COS:
- temp0 = np.sign(temp0) * EPS_COS
+ # Check for possible divide-by-zero for X/(1+cos(xincl)) when
+ # calculating xlcof */
+ temp0 = 1.0 + self.cosIO
+ if np.abs(temp0) < EPS_COS:
+ temp0 = np.sign(temp0) * EPS_COS
- self.xlcof = 0.125 * A3OVK2 * self.sinIO * (3.0 + 5.0 * self.cosIO) / temp0
+ self.xlcof = 0.125 * A3OVK2 * self.sinIO * \
+ (3.0 + 5.0 * self.cosIO) / temp0
self.aycof = 0.25 * A3OVK2 * self.sinIO
@@ -603,11 +620,13 @@ class _SGDP4(object):
self.d2 = 4.0 * self.aodp * tsi * c1sq
temp0 = self.d2 * tsi * self.c1 / 3.0
self.d3 = (17.0 * self.aodp + s4) * temp0
- self.d4 = 0.5 * temp0 * self.aodp * tsi * (221.0 * self.aodp + 31.0 * s4) * self.c1
+ self.d4 = 0.5 * temp0 * self.aodp * tsi * \
+ (221.0 * self.aodp + 31.0 * s4) * self.c1
self.t3cof = self.d2 + 2.0 * c1sq
- self.t4cof = 0.25 * (3.0 * self.d3 + self.c1 * (12.0 * self.d2 + 10.0 * c1sq))
+ self.t4cof = 0.25 * \
+ (3.0 * self.d3 + self.c1 * (12.0 * self.d2 + 10.0 * c1sq))
self.t5cof = (0.2 * (3.0 * self.d4 + 12.0 * self.c1 * self.d3 + 6.0 * self.d2**2 +
- 15.0 * c1sq * (2.0 * self.d2 + c1sq)))
+ 15.0 * c1sq * (2.0 * self.d2 + c1sq)))
elif self.mode == SGDP4_DEEP_NORM:
raise NotImplementedError('Deep space calculations not supported')
@@ -620,7 +639,7 @@ class _SGDP4(object):
em = self.eo
xinc = self.xincl
- xmp = self.xmo + self.xmdot * ts
+ xmp = self.xmo + self.xmdot * ts
xnode = self.xnodeo + ts * (self.xnodot + ts * self.xnodcf)
omega = self.omegao + self.omgdot * ts
@@ -630,25 +649,31 @@ class _SGDP4(object):
raise NotImplementedError('Mode "Near-space, simplified equations"'
' not implemented')
elif self.mode == SGDP4_NEAR_NORM:
- delm = self.xmcof * ((1.0 + self.eta * np.cos(xmp))**3 - self.delmo)
+ delm = self.xmcof * \
+ ((1.0 + self.eta * np.cos(xmp))**3 - self.delmo)
temp0 = ts * self.omgcof + delm
xmp += temp0
omega -= temp0
- tempa = 1.0 - (ts * (self.c1 + ts * (self.d2 + ts * (self.d3 + ts * self.d4))))
- tempe = self.bstar * (self.c4 * ts + self.c5 * (np.sin(xmp) - self.sinXMO))
- templ = ts * ts * (self.t2cof + ts * (self.t3cof + ts * (self.t4cof + ts * self.t5cof)))
+ tempa = 1.0 - \
+ (ts *
+ (self.c1 + ts * (self.d2 + ts * (self.d3 + ts * self.d4))))
+ tempe = self.bstar * \
+ (self.c4 * ts + self.c5 * (np.sin(xmp) - self.sinXMO))
+ templ = ts * ts * \
+ (self.t2cof + ts *
+ (self.t3cof + ts * (self.t4cof + ts * self.t5cof)))
a = self.aodp * tempa**2
e = em - tempe
xl = xmp + omega + xnode + self.xnodp * templ
else:
- raise NotImplementedError('Deep space calculations not supported')
+ raise NotImplementedError('Deep space calculations not supported')
if np.any(a < 1):
raise Exception('Satellite crased at time %s', utc_time)
elif np.any(e < ECC_LIMIT_LOW):
- raise ValueError('Satellite modified eccentricity to low: %e < %e'
- % (e, ECC_LIMIT_LOW))
+ raise ValueError('Satellite modified eccentricity to low: %s < %e'
+ % (str(e[e < ECC_LIMIT_LOW]), ECC_LIMIT_LOW))
e = np.where(e < ECC_EPS, ECC_EPS, e)
e = np.where(e > ECC_LIMIT_HIGH, ECC_LIMIT_HIGH, e)
@@ -693,7 +718,7 @@ class _SGDP4(object):
# 2nd order Newton-Raphson correction.
nr = np.where(np.logical_and(i == 0, np.abs(nr) > 1.25 * maxnr),
np.sign(nr) * maxnr,
- f / (df + 0.5*esinE*nr))
+ f / (df + 0.5 * esinE * nr))
epw += nr
# Short period preliminary quantities
@@ -715,7 +740,8 @@ class _SGDP4(object):
# Update for short term periodics to position terms.
- rk = r * (1.0 - 1.5 * temp2 * betal * self.x3thm1) + 0.5 * temp1 * self.x1mth2 * cos2u
+ rk = r * (1.0 - 1.5 * temp2 * betal * self.x3thm1) + \
+ 0.5 * temp1 * self.x1mth2 * cos2u
uk = u - 0.25 * temp2 * self.x7thm1 * sin2u
xnodek = xnode + 1.5 * temp2 * self.cosIO * sin2u
xinck = xinc + 1.5 * temp2 * self.cosIO * self.sinIO * cos2u
@@ -725,10 +751,10 @@ class _SGDP4(object):
temp0 = np.sqrt(a)
temp2 = XKE / (a * temp0)
- rdotk = ((XKE * temp0 * esinE * invR -temp2 * temp1 * self.x1mth2 * sin2u) *
+ rdotk = ((XKE * temp0 * esinE * invR - temp2 * temp1 * self.x1mth2 * sin2u) *
(XKMPER / AE * XMNPDA / 86400.0))
rfdotk = ((XKE * np.sqrt(pl) * invR + temp2 * temp1 *
- (self.x1mth2 * cos2u + 1.5 * self.x3thm1)) *
+ (self.x1mth2 * cos2u + 1.5 * self.x3thm1)) *
(XKMPER / AE * XMNPDA / 86400.0))
kep['radius'] = rk * XKMPER / AE
diff --git a/pyorbital/tlefile.py b/pyorbital/tlefile.py
index a939c62..1d57775 100644
--- a/pyorbital/tlefile.py
+++ b/pyorbital/tlefile.py
@@ -1,12 +1,13 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
-# Copyright (c) 2011, 2012, 2013, 2014.
+# Copyright (c) 2011, 2012, 2013, 2014, 2015.
# Author(s):
# Esben S. Nielsen <esn at dmi.dk>
# Martin Raspaud <martin.raspaud at smhi.se>
+# Panu Lahtinen <panu.lahtinen at fmi.fi>
# 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
@@ -25,84 +26,184 @@
import logging
import datetime
import urllib2
+import os
+import glob
-tle_urls = ('http://celestrak.com/NORAD/elements/weather.txt',
+TLE_URLS = ('http://celestrak.com/NORAD/elements/weather.txt',
'http://celestrak.com/NORAD/elements/resource.txt')
-logger = logging.getLogger(__name__)
+LOGGER = logging.getLogger(__name__)
+
+
+def read_platform_numbers(in_upper=False, num_as_int=False):
+ '''Read platform numbers from $PPP_CONFIG_DIR/platforms.txt if available.
+ '''
+
+ out_dict = {}
+ if "PPP_CONFIG_DIR" in os.environ:
+ platform_file = os.path.join(os.environ["PPP_CONFIG_DIR"],
+ "platforms.txt")
+ try:
+ fid = open(platform_file, 'r')
+ except IOError:
+ LOGGER.error("Platform file %s not found.", platform_file)
+ return out_dict
+ for row in fid:
+ # skip comment lines
+ if not row.startswith('#'):
+ parts = row.split()
+ if in_upper:
+ parts[0] = parts[0].upper()
+ if num_as_int:
+ parts[1] = int(parts[1])
+ out_dict[parts[0]] = parts[1]
+ fid.close()
+
+ return out_dict
+
+
+SATELLITES = read_platform_numbers(in_upper=True, num_as_int=False)
+'''
+The platform numbers are given in a file $PPP_CONFIG/platforms.txt
+in the following format:
+
+# Mappings between satellite catalogue numbers and corresponding
+# platform names from OSCAR.
+ALOS-2 39766
+CloudSat 29107
+CryoSat-2 36508
+CSK-1 31598
+CSK-2 32376
+CSK-3 33412
+CSK-4 37216
+DMSP-F15 25991
+DMSP-F16 28054
+DMSP-F17 29522
+DMSP-F18 35951
+DMSP-F19 39630
+EOS-Aqua 27424
+EOS-Aura 28376
+EOS-Terra 25994
+FY-2D 29640
+FY-2E 33463
+FY-2F 38049
+FY-2G 40367
+FY-3A 32958
+FY-3B 37214
+FY-3C 39260
+GOES-13 29155
+GOES-14 35491
+GOES-15 36411
+Himawari-6 28622
+Himawari-7 28937
+Himawari-8 40267
+INSAT-3A 27714
+INSAT-3C 27298
+INSAT-3D 39216
+JASON-2 33105
+Kalpana-1 27525
+Landsat-7 25682
+Landsat-8 39084
+Meteosat-7 24932
+Meteosat-8 27509
+Meteosat-9 28912
+Meteosat-10 38552
+Metop-A 29499
+Metop-B 38771
+NOAA-15 25338
+NOAA-16 26536
+NOAA-17 27453
+NOAA-18 28654
+NOAA-19 33591
+RadarSat-2 32382
+Sentinel-1A 39634
+SMOS 36036
+SPOT-5 27421
+SPOT-6 38755
+SPOT-7 40053
+Suomi-NPP 37849
+TanDEM-X 36605
+TerraSAR-X 31698
+'''
+
def read(platform, tle_file=None, line1=None, line2=None):
- """Read TLE for *satellite* from *tle_file*, from *line1* and *line2*, or
- from internet if none is provided.
- """
+ """Read TLE for *satellite* from *tle_file*, from *line1* and *line2*, from
+ the newest file provided in the TLES pattern, or from internet if none is
+ provided.
+ """
return Tle(platform, tle_file=tle_file, line1=line1, line2=line2)
+
def fetch(destination):
"""fetch TLE from internet and save it to *destination*.
- """
+ """
with open(destination, "w") as dest:
- for url in tle_urls:
+ for url in TLE_URLS:
response = urllib2.urlopen(url)
dest.write(response.read())
+
class ChecksumError(Exception):
+ '''ChecksumError.
+ '''
pass
class Tle(object):
"""Class holding TLE objects.
- """
+ """
def __init__(self, platform, tle_file=None, line1=None, line2=None):
- platform = platform.strip().upper()
+ self._platform = platform.strip().upper()
+ self._tle_file = tle_file
+ self._line1 = line1
+ self._line2 = line2
- if line1 is not None and line2 is not None:
- tle = line1.strip() + "\n" + line2.strip()
- else:
- if tle_file:
- urls = (tle_file,)
- open_func = open
- else:
- logger.debug("Fetch tle from the internet.")
- urls = tle_urls
- open_func = urllib2.urlopen
-
- tle = ""
- for url in urls:
- fp = open_func(url)
- for l0 in fp:
- l1, l2 = fp.next(), fp.next()
- if l0.strip() == platform:
- tle = l1.strip() + "\n" + l2.strip()
- break
- fp.close()
- if tle:
- break
-
- if not tle:
- raise AttributeError, "Found no TLE entry for '%s'" % platform
+ self.satnumber = None
+ self.classification = None
+ self.id_launch_year = None
+ self.id_launch_number = None
+ self.id_launch_piece = None
+ self.epoch_year = None
+ self.epoch_day = None
+ self.epoch = None
+ self.mean_motion_derivative = None
+ self.mean_motion_sec_derivative = None
+ self.bstar = None
+ self.ephemeris_type = None
+ self.element_number = None
+ self.inclination = None
+ self.right_ascension = None
+ self.excentricity = None
+ self.arg_perigee = None
+ self.mean_anomaly = None
+ self.mean_motion = None
+ self.orbit = None
- self._platform = platform
- self._line1, self._line2 = tle.split('\n')
- self._checksum()
self._read_tle()
+ self._checksum()
+ self._parse_tle()
@property
def line1(self):
+ '''Return first TLE line.'''
return self._line1
@property
def line2(self):
+ '''Return second TLE line.'''
return self._line2
@property
def platform(self):
+ '''Return satellite platform name.'''
return self._platform
def _checksum(self):
"""Performs the checksum for the current TLE.
"""
- for line in [self.line1, self.line2]:
+ for line in [self._line1, self._line2]:
check = 0
for char in line[:-1]:
if char.isdigit():
@@ -114,15 +215,69 @@ class Tle(object):
raise ChecksumError(self._platform + " " + line)
def _read_tle(self):
+ '''Read TLE data.
+ '''
+ if self._line1 is not None and self._line2 is not None:
+ tle = self._line1.strip() + "\n" + self._line2.strip()
+ else:
+ if self._tle_file:
+ urls = (self._tle_file,)
+ open_func = open
+ elif "TLES" in os.environ:
+ # TODO: get the TLE file closest in time to the actual satellite
+ # overpass, NOT the latest!
+ urls = (max(glob.glob(os.environ["TLES"]),
+ key=os.path.getctime), )
+ LOGGER.debug("Reading TLE from %s", urls[0])
+ open_func = open
+ else:
+ LOGGER.debug("Fetch TLE from the internet.")
+ urls = TLE_URLS
+ open_func = urllib2.urlopen
+
+ tle = ""
+ designator = "1 " + SATELLITES.get(self._platform, '')
+ for url in urls:
+ fid = open_func(url)
+ for l_0 in fid:
+ if l_0.strip() == self._platform:
+ l_1, l_2 = fid.next(), fid.next()
+ tle = l_1.strip() + "\n" + l_2.strip()
+ break
+ if(self._platform in SATELLITES and
+ l_0.strip().startswith(designator)):
+ l_1 = l_0
+ l_2 = fid.next()
+ tle = l_1.strip() + "\n" + l_2.strip()
+ LOGGER.debug("Found platform %s, ID: %s",
+ self._platform,
+ SATELLITES[self._platform])
+ break
+ fid.close()
+ if tle:
+ break
+
+ if not tle:
+ raise KeyError("Found no TLE entry for '%s'" % self._platform)
+
+ self._line1, self._line2 = tle.split('\n')
+
+ def _parse_tle(self):
+ '''Parse values from TLE data.
+ '''
def _read_tle_decimal(rep):
- if rep[0] in ["-", " "]:
- val = rep[0] + "." + rep[1:-2] + "e" + rep[-2:]
+ '''Convert *rep* to decimal value.
+ '''
+ if rep[0] in ["-", " ", "+"]:
+ digits = rep[1:-2].strip()
+ val = rep[0] + "." + digits + "e" + rep[-2:]
else:
- val = "." + rep[:-2] + "e" + rep[-2:]
+ digits = rep[:-2].strip()
+ val = "." + digits + "e" + rep[-2:]
return float(val)
-
+
self.satnumber = self._line1[2:7]
self.classification = self._line1[7]
self.id_launch_year = self._line1[9:11]
@@ -131,11 +286,10 @@ class Tle(object):
self.epoch_year = self._line1[18:20]
self.epoch_day = float(self._line1[20:32])
self.epoch = (datetime.datetime.strptime(self.epoch_year, "%y") +
- datetime.timedelta(days=self.epoch_day - 1))
+ datetime.timedelta(days=self.epoch_day - 1))
self.mean_motion_derivative = float(self._line1[33:43])
self.mean_motion_sec_derivative = _read_tle_decimal(self._line1[44:52])
- self.bstar = float(self._line1[53] + "." + self._line1[54:59] + "e" + self._line1[59:61])
- _read_tle_decimal(self._line1[53:61])
+ self.bstar = _read_tle_decimal(self._line1[53:61])
try:
self.ephemeris_type = int(self._line1[62])
except ValueError:
@@ -151,12 +305,19 @@ class Tle(object):
self.orbit = int(self._line2[63:68])
def __str__(self):
- import pprint, StringIO
- s = StringIO.StringIO()
- d = dict(([(k, v) for k, v in self.__dict__.items() if k[0] != '_']))
- pprint.pprint(d, s)
- return s.getvalue()[:-1]
+ import pprint
+ import StringIO
+ s_var = StringIO.StringIO()
+ d_var = dict(([(k, v) for k, v in
+ self.__dict__.items() if k[0] != '_']))
+ pprint.pprint(d_var, s_var)
+ return s_var.getvalue()[:-1]
+
+def main():
+ '''Main for testing TLE reading.
+ '''
+ tle_data = read('Noaa-19')
+ print tle_data
if __name__ == '__main__':
- tle = read('noaa 19')
- print tle
+ main()
diff --git a/pyorbital/version.py b/pyorbital/version.py
index db58857..f6bb660 100644
--- a/pyorbital/version.py
+++ b/pyorbital/version.py
@@ -1,7 +1,7 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
-# Copyright (c) 2014 Martin Raspaud
+# Copyright (c) 2014, 2015 Martin Raspaud
# Author(s):
@@ -23,8 +23,4 @@
"""Version file.
"""
-__major__ = "0"
-__minor__ = "3"
-__patch__ = "2"
-
-__version__ = "v" + ".".join([__major__, __minor__, __patch__])
+__version__ = "v1.0.0"
diff --git a/setup.cfg b/setup.cfg
new file mode 100644
index 0000000..1ce39b9
--- /dev/null
+++ b/setup.cfg
@@ -0,0 +1,5 @@
+[bdist_rpm]
+requires=numpy
+release=1
+doc_files = doc/Makefile doc/source/*.rst
+
diff --git a/setup.py b/setup.py
index 3ce9449..6a8232d 100644
--- a/setup.py
+++ b/setup.py
@@ -42,7 +42,7 @@ setup(name='pyorbital',
test_suite='pyorbital.tests.suite',
package_dir = {'pyorbital': 'pyorbital'},
packages = ['pyorbital'],
- install_requires=['numpy'],
+ install_requires=['numpy>=1.6.0'],
zip_safe=False,
)
--
Alioth's /usr/local/bin/git-commit-notice on /srv/git.debian.org/git/pkg-grass/pyorbital.git
More information about the Pkg-grass-devel
mailing list