Pythonic geographiclib
package reimplements geodesic math in Python. Using Cython bindings for C++ version of geographiclib
allows for 60-90x speedup for geodesic functions.
Warning: only Inverse()
, Direct()
, and InverseLine()
for Geodesic.WGS84
are implemented. For GeodesicLine
, only Position()
is implemented.
# for Ubuntu
sudo apt-get install -y libgeographic-dev
# for macOS:
brew install geographiclib
# and then:
pip install geographiclib-cython-bindings
For instructions on how to build and distribute, read on.
The following instructions are tested for Ubuntu 16.04, Amazon Linux, and macOS Sierra 10.12.
You will need the C++ geographiclib
library (for compile-time and for run-time, too).
-
Option 1 (recommended for Ubuntu and macOS): Install
libgeographic-dev
package, however it looks like the built-from-sources version is fresher.Ubuntu:
sudo apt-get install -y libgeographic-dev
macOS:
brew install geographiclib
-
Option 2 (recommended for Ubuntu and Amazon Linux): Compile and install it from sources. This creates bunch of
*.hpp
in/usr/local/include/GeographicLib/
and/usr/local/lib/libGeographic.so
:git clone --depth=1 git://git.code.sourceforge.net/p/geographiclib/code geographiclib cd geographiclib mkdir BUILD cd BUILD cmake .. make sudo make install
See installation manual in case of problems.
For development, you will also need Cython:
pip install cython
There are two Cython files: cgeographiclib.pxd
describing the C++ API of the libGeographic
library, and geographiclib_cython.pyx
describing classes that will be visible from Python user code.
The .pyx
imports .pxd
to learn about C++ classes and functions available to be called.
We wrap C++ classes Geodesic
and GeodesicLine
into Cython classes PyGeodesic
and PyGeodesicLine
.
Additionally, we define a pure Python class Geodesic
with a single field WGS84
to mimic the behavior of the official geographiclib
package.
There is also setup.py
file.
This file describes how to build the extension module, using distutils
.
In there, we specify the library to link with as libraries=['Geographic']
. The Geographic
stands for libGeographic.so
that we previously installed.
There are two options to build the package:
- One, use Cython's
cythonize()
function to generate a.cpp
file from the.pyx
one, and then compile it against thelibGeographic.so
library. - Two, if the
.cpp
is already provided, just compile it - no Cython required!
For development, use option 1 by providing --cython
flag:
python setup.py build_ext --inplace --cython
The result will be a .so
shared library named like geographiclib_cython.cpython-35m-x86_64-linux-gnu.so
.
build_ext
means we're building a C++ extension. --inplace
means to put it in the current directory.
If you run python
from current directory, you'll be able to import geographiclib_cython
.
To distribute, call sdist
with --cython
flag to create source distribution (unbuilt):
python setup.py sdist --cython
The result will be a dist/
directory with a distribution named like geographiclib-cython-bindings-1.0.0.tar.gz
inside.
The archive contains setup.py
and geographiclib_cython.cpp
, so users can build and install it without having Cython!
To publish to PyPI, run:
twine upload -r pypi dist/*
To install, locate the .tar.gz
distribution and run:
pip install geographiclib-cython-bindings-1.0.0.tar.gz
For conda, you might need to activate your environment first:
$ which pip
/home/vagrant/.local/bin/pip
$ source activate root
(root) $ which pip
/home/vagrant/anaconda3/bin/pip
Let's try to import and use it!
$ ipython
Python 3.5.2 |Anaconda custom (64-bit)| (default, Jul 2 2016, 17:53:06)
Type 'copyright', 'credits' or 'license' for more information
IPython 6.0.0 -- An enhanced Interactive Python. Type '?' for help.
In [1]: from geographiclib_cython import Geodesic
In [2]: Geodesic.WGS84.Inverse(10, 20, 30, 40)
Out[2]:
{'azi1': 40.319640222045905,
'azi2': 47.328994793150066,
'lat1': 10.0,
'lat2': 30.0,
'lon1': 20.0,
'lon2': 40.0,
's12': 3035728.956905633}
In [3]: Geodesic.WGS84.Direct(10, 20, 40.319640222045905, 3035728.956905633)
Out[3]:
{'azi1': 40.319640222045905,
'azi2': 47.328994793150066,
'lat1': 10.0,
'lat2': 29.999999999999996,
'lon1': 20.0,
'lon2': 40.00000000000001,
's12': 3035728.956905633}
>>> import timeit
>>> timeit.timeit('Geodesic.WGS84.Inverse(10, 20, 30, 40)', setup='from geographiclib.geodesic import Geodesic', number=10000)
1.7752603970002383
>>> timeit.timeit('Geodesic.WGS84.Inverse(10, 20, 30, 40)', setup='from geographiclib_cython import Geodesic', number=10000)
0.024111284990794957
>>> timeit.timeit('Geodesic.WGS84.Direct(10, 20, 30, 40000)', setup='from geographiclib.geodesic import Geodesic', number=100000)
7.022558598022442
>>> timeit.timeit('Geodesic.WGS84.Direct(10, 20, 30, 40000)', setup='from geographiclib_cython import Geodesic', number=100000)
0.12127103898092173
- If you get this error when doing
import Geodesic
:
ImportError: libGeographic.so.17: cannot open shared object file: No such file or directory
This means that Python interpreter can't find the shared library. For some reason, /usr/local/lib
is not searched by default. We need to provide it in the LD_LIBRARY_PATH
. If you have installed libGeographic.so
somewhere else, provide that directory instead.
export LD_LIBRARY_PATH=/usr/local/lib:${LD_LIBRARY_PATH}
- If you get this error when doing
import Geodesic
:
ImportError: /opt/anaconda/lib/libstdc++.so.6: version `GLIBCXX_3.4.20' not found (required by /usr/local/lib/libGeographic.so.17)
Do this (solution found here):
conda install libgcc
- If you get this error when doing
import Geodesic
:
ImportError: /opt/anaconda/lib/python3.5/site-packages/geographiclib_cython.cpython-35m-x86_64-linux-gnu.so: undefined symbol: _ZNK13GeographicLib8Geodesic11Invej
Then you have an incompatible version of libGeographic installed. Check where it's coming from:
ldd /opt/anaconda/lib/python3.5/site-packages/geographiclib_cython.cpython-35m-x86_64-linux-gnu.so
linux-vdso.so.1 => (0x00007fffac2ea000)
libGeographic.so.9 => /usr/lib/libGeographic.so.9 (0x00007f86ff6bb000)
...
Remove it and remove the .whl
from the pip
cache (do find ~ -name "*.whl"
to find out where the cache is):
rm /usr/lib/libGeographic*
rm ~/.cache/pip/wheels/0e/68/e7/7cadf8180052771a12c112ac3cda44363135ecb14cfd57a500/geographiclib_cython_bindings-1.0.0-cp35m-x86_64.whl
Then do pip install
again.