Coder Social home page Coder Social logo

haversine's Introduction

Haversine

Calculate the distance (in various units) between two points on Earth using their latitude and longitude.

Installation

pip install haversine

Usage

Calculate the distance between Lyon and Paris

from haversine import haversine, Unit

lyon = (45.7597, 4.8422) # (lat, lon)
paris = (48.8567, 2.3508)

haversine(lyon, paris)
>> 392.2172595594006  # in kilometers

haversine(lyon, paris, unit=Unit.MILES)
>> 243.71250609539814  # in miles

# you can also use the string abbreviation for units:
haversine(lyon, paris, unit='mi')
>> 243.71250609539814  # in miles

haversine(lyon, paris, unit=Unit.NAUTICAL_MILES)
>> 211.78037755311516  # in nautical miles

The lat/lon values need to be provided in degrees of the ranges [-90,90] (lat) and [-180,180] (lon). If values are outside their ranges, an error will be raised. This can be avoided by automatic normalization via the normalize parameter.

The haversine.Unit enum contains all supported units:

import haversine

print(tuple(haversine.Unit))

outputs

(<Unit.KILOMETERS: 'km'>, <Unit.METERS: 'm'>, <Unit.MILES: 'mi'>,
 <Unit.NAUTICAL_MILES: 'nmi'>, <Unit.FEET: 'ft'>, <Unit.INCHES: 'in'>,
 <Unit.RADIANS: 'rad'>, <Unit.DEGREES: 'deg'>)

Note for radians and degrees

The radian and degrees returns the great circle distance between two points on a sphere.

Notes:

  • on a unit-sphere the angular distance in radians equals the distance between the two points on the sphere (definition of radians)
  • When using "degree", this angle is just converted from radians to degrees

Inverse Haversine Formula

Calculates a point from a given vector (distance and direction) and start point. Currently explicitly supports both cardinal (north, east, south, west) and intercardinal (northeast, southeast, southwest, northwest) directions. But also allows for explicit angles expressed in Radians.

Example: Finding arbitary point from Paris

from haversine import inverse_haversine, Direction
from math import pi
paris = (48.8567, 2.3508) # (lat, lon)
# Finding 32 km west of Paris
inverse_haversine(paris, 32, Direction.WEST)
# returns tuple (48.85587279023947, 1.9134085092836945)
# Finding 32 km southwest of Paris
inverse_haversine(paris, 32, pi * 1.25)
# returns tuple (48.65279552300661, 2.0427666779658806)
# Finding 50 miles north of Paris
inverse_haversine(paris, 50, Direction.NORTH, unit=Unit.MILES)
# returns tuple (49.58035791599536, 2.3508)
# Finding 10 nautical miles south of Paris
inverse_haversine(paris, 10, Direction.SOUTH, unit=Unit.NAUTICAL_MILES)
# returns tuple (48.690145868497645, 2.3508)

Performance optimisation for distances between all points in two vectors

You will need to install numpy in order to gain performance with vectors. For optimal performance, you can turn off coordinate checking by adding check=False and install the optional packages numba and icc_rt.

You can then do this:

from haversine import haversine_vector, Unit

lyon = (45.7597, 4.8422) # (lat, lon)
paris = (48.8567, 2.3508)
new_york = (40.7033962, -74.2351462)

haversine_vector([lyon, lyon], [paris, new_york], Unit.KILOMETERS)

>> array([ 392.21725956, 6163.43638211])

It is generally slower to use haversine_vector to get distance between two points, but can be really fast to compare distances between two vectors.

Combine matrix

You can generate a matrix of all combinations between coordinates in different vectors by setting comb parameter as True.

from haversine import haversine_vector, Unit

lyon = (45.7597, 4.8422) # (lat, lon)
london = (51.509865, -0.118092)
paris = (48.8567, 2.3508)
new_york = (40.7033962, -74.2351462)

haversine_vector([lyon, london], [paris, new_york], Unit.KILOMETERS, comb=True)

>> array([[ 392.21725956,  343.37455271],
 	  [6163.43638211, 5586.48447423]])

The output array from the example above returns the following table:

Paris New York
Lyon Lyon <-> Paris Lyon <-> New York
London London <-> Paris London <-> New York

By definition, if you have a vector a with n elements, and a vector b with m elements. The result matrix M would be $n x m$ and a element M[i,j] from the matrix would be the distance between the ith coordinate from vector a and jth coordinate with vector b.

Contributing

Clone the project.

Install pipenv.

Run pipenv install --dev

Launch test with pipenv run pytest

haversine's People

Contributors

adamchainz avatar brouberol avatar carlosloslas avatar ccforgy avatar crapsjeroen avatar dependabot[bot] avatar enagorny avatar fdms-3741 avatar jankatins avatar jdeniau avatar jobh avatar juandes avatar kraj avatar kukiel avatar leforestier avatar maurycyp avatar merschformann avatar noezor avatar nonsignificantp avatar ocefpaf avatar plammens avatar superamin avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

haversine's Issues

haversine_vector is no longer fast

Following commit b949e32, which introduces element-wise limits check, the vectorized version is 10 times or more slower than it used to be.

There should probably be vectorized versions of the check, or a flag to indicate that limits check is not needed.

haversine raises an exception above 180 degrees latitude

Hi,

I defined London as (51.5072, -0.1276), and then I had 3 methods to define the other side of the earth from London:

(51.5072 * -1.0, -0.1276 + 180.0)
(51.5072 - 180.0, -0.1276)
(51.5072 + 180.0, -0.1276)

Then I tried to check the distance between each point, and the distance to London. When I defined the other side of the earth from London as (51.5072 + 180.0, -0.1276), haversine raises an exception when I check its distance to (51.5072 * -1.0, -0.1276 + 180.0) (which should be 0). You have a problem with angles above 180 degrees. I know latitude and longitude shouldn't usually be above 180 degrees (actually latitude should be from -90 to 90 degrees), but it would be nice if haversine would be able to handle other degrees too while converting them to the relevant degree (51.5072 - 180.0 and 51.5072 + 180.0 should be the same degree).

Notice that 51.5072 - 180.0 is -128.4928 and it works, even though it's below -90 degrees (it's just a way to define the point on the other side of the earth).

Update PyPI version

It's a pity that the version on PyPI is outdated, especially with regards to speed optimization (C code).

Request: accept shapely geometries as inputs

Hello,

Thank you for this package. It has been very helpful!

If it is not too much trouble, it would be a great help if you would add compatibility for shapely geometries (at least points, but linestrings could be useful too) as inputs into the function.

Thanks, and great work!
Chris

Possible vectorization bug

In haversine 2.8.0 at lines 173-174 you have:

 if has_numpy:
        _haversine_kernel_vector = numba.vectorize(fastmath=True)(_haversine_kernel)

Shouldn't the second line read instead

        _haversine_kernel_vector = numba.vectorize(fastmath=True)(_haversine_kernel_vector)

last release is breaking my CI

I'm using haversine for a while now and I really like it's simplicity.
My today's latest build failed because you introduced something new in the lib:
https://github.com/12rambau/sepal_ui/runs/7278765322?check_suite_focus=true

def_ensure_lat_lon(lat: float, lon: float):
"""
    Ensure that the given latitude and longitude have proper values. An exception is raised if they are not.
    """
if lat < -90or lat > 90:
>           raiseValueError(f"Latitude {lat} is out of range [-90, 90]")

it should be <= and >= right ?

Vectorizing haversine for performance boost

Right now, it seems like haversine is only able to be applied one lat1/lon1, lat2/lon2 collection at a time, which in many applications would correspond to one or two pandas DataFrame rows. As such, the only quick implementation of using haversine in such a data structure is to apply it in a row-wise fashion, but this typically is very slow and memory intensive. It seems as though a vectorized version of haversine should be possible, in which each value (lat1, lat2, lon1, and lon2) are vectors unto themselves. The simple solution for this would be to apply numpy's np.vectorize(haversine), but it results in TypeError: 'numpy.float64' object is not iterable when it hits lines 62 and 63 in haversine.py.

Are there any plans to vectorize haversine in the near future? Or would this be something that requires significant modification?

Reverse haversine

I was wondering if it's possible to implement a reverse haversine function.

Imagine there is a circle, we know the center point (latitude, longitude), and the radius. Return a set of maximum latitude and longitude that a random point can have and still fall within the circle.

I do not understand the math behind this to implement it myself. Happy to help if anyone has the math for it.

`inverse_haversine` precision for cardinal directions

If I run
inverse_haversine((45.7597, 0.0), -300, 0.5*pi)
the output is
(45.69452167473055, -3.864098251954592)
while I expect that the first number remains the same as before since I am moving to the east direction (45.69452167473055 vs 45.7597).
Is this behavior correct?

Examples on README.md are incorrect

Hi,

It's written on README.md:

paris = (48.8567, 2.3508) # (lat, lon)
# Finding 32 km west of Paris
inverse_haversine(paris, 32, Direction.WEST)
# returns tuple (49.1444, 2.3508)

I ran the code:

paris = (48.8567, 2.3508) # (lat, lon)
print(inverse_haversine(paris, 32, Direction.WEST))

And I received (48.85587279023947, 1.9134085092836945). Also, notice that the latitude is not exactly 48.8567 as I expected, but it's different than (49.1444, 2.3508) in the README.md.

I didn't check all the examples.

Cannot pip install version 1.0.x with latest (56.2.0+) version of setuptools

Simple example with a Dockerfile:

FROM python:3.6-buster
RUN pip install haversine==1.0.2

At time of writing this will install Python 3.6.13 and pip 21.1.1 (this hash on Docker Hub). When this is attempted, you get the following error:

#5 1.940   Downloading haversine-1.0.2.tar.gz (2.2 kB)
#5 2.313     ERROR: Command errored out with exit status 1:
#5 2.313      command: /usr/local/bin/python -c 'import io, os, sys, setuptools, tokenize; sys.argv[0] = '"'"'/tmp/pip-install-v4a64w95/haversine_e7df858ff483476cb0f5ea6864a61364/setup.py'"'"'; __file__='"'"'/tmp/pip-install-v4a64w95/haversine_e7df858ff483476cb0f5ea6864a61364/setup.py'"'"';f = getattr(tokenize, '"'"'open'"'"', open)(__file__) if os.path.exists(__file__) else io.StringIO('"'"'from setuptools import setup; setup()'"'"');code = f.read().replace('"'"'\r\n'"'"', '"'"'\n'"'"');f.close();exec(compile(code, __file__, '"'"'exec'"'"'))' egg_info --egg-base /tmp/pip-pip-egg-info-zhi880hu
#5 2.313          cwd: /tmp/pip-install-v4a64w95/haversine_e7df858ff483476cb0f5ea6864a61364/
#5 2.313     Complete output (26 lines):
#5 2.313     running egg_info
#5 2.313     creating /tmp/pip-pip-egg-info-zhi880hu/haversine.egg-info
#5 2.313     writing /tmp/pip-pip-egg-info-zhi880hu/haversine.egg-info/PKG-INFO
#5 2.313     Traceback (most recent call last):
#5 2.313       File "<string>", line 1, in <module>
#5 2.313       File "/tmp/pip-install-v4a64w95/haversine_e7df858ff483476cb0f5ea6864a61364/setup.py", line 29, in <module>
#5 2.313         'Topic :: Scientific/Engineering :: Mathematics'
#5 2.313       File "/usr/local/lib/python3.6/site-packages/setuptools/__init__.py", line 153, in setup
#5 2.313         return distutils.core.setup(**attrs)
#5 2.313       File "/usr/local/lib/python3.6/distutils/core.py", line 148, in setup
#5 2.313         dist.run_commands()
#5 2.313       File "/usr/local/lib/python3.6/distutils/dist.py", line 955, in run_commands
#5 2.313         self.run_command(cmd)
#5 2.313       File "/usr/local/lib/python3.6/distutils/dist.py", line 974, in run_command
#5 2.313         cmd_obj.run()
#5 2.313       File "/usr/local/lib/python3.6/site-packages/setuptools/command/egg_info.py", line 292, in run
#5 2.313         writer(self, ep.name, os.path.join(self.egg_info, ep.name))
#5 2.313       File "/usr/local/lib/python3.6/site-packages/setuptools/command/egg_info.py", line 628, in write_pkg_info
#5 2.313         metadata.write_pkg_info(cmd.egg_info)
#5 2.313       File "/usr/local/lib/python3.6/distutils/dist.py", line 1106, in write_pkg_info
#5 2.313         self.write_pkg_file(pkg_info)
#5 2.313       File "/usr/local/lib/python3.6/site-packages/setuptools/dist.py", line 172, in write_pkg_file
#5 2.313         license = rfc822_escape(self.get_license())
#5 2.313       File "/usr/local/lib/python3.6/distutils/util.py", line 474, in rfc822_escape
#5 2.313         lines = header.split('\n')
#5 2.313     AttributeError: 'list' object has no attribute 'split'
#5 2.313     ----------------------------------------
#5 2.313 WARNING: Discarding https://files.pythonhosted.org/packages/92/1c/de387b6399070587970fe2007f8b1064d7f948cbb78c07fb61c65cbff560/haversine-1.0.2.tar.gz#sha256=63b9ceb3d2992314fe74948f57afd7d0fb623663ca32f2973565156aa34b9c52 (from https://pypi.org/simple/haversine/). Command errored out with exit status 1: python setup.py egg_info Check the logs for full command output.
#5 2.314 ERROR: Could not find a version that satisfies the requirement haversine==1.0.2 (from versions: 0.1, 0.3, 0.4.0, 0.4.1, 0.4.2, 0.4.3, 0.4.4, 0.4.5, 0.5.0, 1.0.0, 1.0.1, 1.0.2, 2.0.0, 2.1.0, 2.1.1, 2.1.2, 2.2.0, 2.3.0)
#5 2.314 ERROR: No matching distribution found for haversine==1.0.2

This looks to be an unintended consequence of this change in setuptools in the 56.2.0 release notes

Haversine 0.4 not building on windows

Building haversine on Windows 32 fails: https://ci.appveyor.com/project/fgregg/dedupe/build/1.0.117#L132

Searching for haversine
Reading https://pypi.python.org/simple/haversine/
Best match: haversine 0.4.0
Downloading https://pypi.python.org/packages/source/h/haversine/haversine-0.4.0.tar.gz#md5=f36df31d32a128e3bd89afc88844ca61
Processing haversine-0.4.0.tar.gz
Writing c:\users\appveyor\appdata\local\temp\easy_install-x1z2r7\haversine-0.4.0\setup.cfg
Running haversine-0.4.0\setup.py -q bdist_egg --dist-dir c:\users\appveyor\appdata\local\temp\easy_install-x1z2r7\haversine-0.4.0\egg-dist-tmp-fim5km
haversine.c
haversine/haversine.c(9) : error C2065: 'M_PI' : undeclared identifier 
haversine/haversine.c(9) : warning C4244: 'return' : conversion from 'double' to 'float', possible loss of data 
haversine/haversine.c(25) : warning C4244: '=' : conversion from 'double' to 'float', possible loss of data 
haversine/haversine.c(26) : warning C4244: '=' : conversion from 'double' to 'float', possible loss of data 
haversine/haversine.c(28) : warning C4244: 'return' : conversion from 'double' to 'float', possible loss of data 
error: Setup script exited with error: command '"C:\Program Files (x86)\Common Files\Microsoft\Visual C++ for Python\9.0\VC\Bin\cl.exe"' failed with exit status 2

Haversine 0.1 worked fine https://ci.appveyor.com/project/fgregg/dedupe/build/1.0.110#L131

Haversine will not import with Ubuntu 20.04 and Python 3.8.2

Currently running Python 3.8.2 on Ubuntu 20.04 and receive the following error when attempting to use haversine:

 line 1, in <module>
    from haversine import haversine, Unit
ImportError: cannot import name 'haversine' from 'haversine' (unknown location)```

Missing python 3 wheels / source

Hey there, this new version 1.0.2 was uploaded only with wheels for python2 making it unable to install on python 3. I checked the previous versions and they had python2 but they also shipped with sdist versions which made them available for python3. Can you upload it at least with sdist?
Thank you!

Unable to install with pyton 3.10

Hello!

Thanks for creating this module. It helped a lot to make the distance calculations. Unfortunately, we can't use it in latest Python version.

Right now this lib only supports Python max 3.9 version. So there are dependency conflicts when we want to use latest Python==3.10 and haversine==2.5.1 library.

[tests] failures against 32bit arches

Tests suite of haversine 2.8.1 fails on 32bit systems with:

=================================== FAILURES ===================================
________________________________ test_pair[km] _________________________________

unit = <Unit.KILOMETERS: 'km'>

    @pytest.mark.parametrize(
        'unit', [Unit.KILOMETERS, Unit.METERS, Unit.INCHES]
    )
    def test_pair(unit):
        def test_lyon_paris(unit):
            expected_lyon_paris = EXPECTED_LYON_PARIS[unit]
            assert haversine_vector(LYON, PARIS, unit=unit) == expected_lyon_paris
            assert isinstance(unit.value, str)
            assert haversine_vector(
                LYON, PARIS, unit=unit.value) == expected_lyon_paris
    
>       return test_lyon_paris(unit)

tests/test_haversine_vector.py:19: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

unit = <Unit.KILOMETERS: 'km'>

    def test_lyon_paris(unit):
        expected_lyon_paris = EXPECTED_LYON_PARIS[unit]
>       assert haversine_vector(LYON, PARIS, unit=unit) == expected_lyon_paris
E       AssertionError: assert array([392.21725956]) == 392.2172595594006
E        +  where array([392.21725956]) = haversine_vector((45.7597, 4.8422), (48.8567, 2.3508), unit=<Unit.KILOMETERS: 'km'>)

tests/test_haversine_vector.py:14: AssertionError
_________________________________ test_pair[m] _________________________________

unit = <Unit.METERS: 'm'>

    @pytest.mark.parametrize(
        'unit', [Unit.KILOMETERS, Unit.METERS, Unit.INCHES]
    )
    def test_pair(unit):
        def test_lyon_paris(unit):
            expected_lyon_paris = EXPECTED_LYON_PARIS[unit]
            assert haversine_vector(LYON, PARIS, unit=unit) == expected_lyon_paris
            assert isinstance(unit.value, str)
            assert haversine_vector(
                LYON, PARIS, unit=unit.value) == expected_lyon_paris
    
>       return test_lyon_paris(unit)

tests/test_haversine_vector.py:19: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

unit = <Unit.METERS: 'm'>

    def test_lyon_paris(unit):
        expected_lyon_paris = EXPECTED_LYON_PARIS[unit]
>       assert haversine_vector(LYON, PARIS, unit=unit) == expected_lyon_paris
E       AssertionError: assert array([392217.2595594]) == 392217.2595594006
E        +  where array([392217.2595594]) = haversine_vector((45.7597, 4.8422), (48.8567, 2.3508), unit=<Unit.METERS: 'm'>)

tests/test_haversine_vector.py:14: AssertionError
________________________________ test_pair[in] _________________________________

unit = <Unit.INCHES: 'in'>

    @pytest.mark.parametrize(
        'unit', [Unit.KILOMETERS, Unit.METERS, Unit.INCHES]
    )
    def test_pair(unit):
        def test_lyon_paris(unit):
            expected_lyon_paris = EXPECTED_LYON_PARIS[unit]
            assert haversine_vector(LYON, PARIS, unit=unit) == expected_lyon_paris
            assert isinstance(unit.value, str)
            assert haversine_vector(
                LYON, PARIS, unit=unit.value) == expected_lyon_paris
    
>       return test_lyon_paris(unit)

tests/test_haversine_vector.py:19: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

unit = <Unit.INCHES: 'in'>

    def test_lyon_paris(unit):
        expected_lyon_paris = EXPECTED_LYON_PARIS[unit]
>       assert haversine_vector(LYON, PARIS, unit=unit) == expected_lyon_paris
E       AssertionError: assert array([15441624.39210257]) == 15441624.392102592
E        +  where array([15441624.39210257]) = haversine_vector((45.7597, 4.8422), (48.8567, 2.3508), unit=<Unit.INCHES: 'in'>)

tests/test_haversine_vector.py:14: AssertionError

...

FAILED tests/test_haversine_vector.py::test_pair[km] - AssertionError: assert...
FAILED tests/test_haversine_vector.py::test_pair[m] - AssertionError: assert ...
FAILED tests/test_haversine_vector.py::test_pair[in] - AssertionError: assert...

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.