Google Location Data – Timeline API?

I was hoping to query an API for the google maps Timeline information they store about my phones whereabouts.  For example, I was hoping to query an API for average travel time between two locations in that history.

Unfortunately, Google doesn’t appear to have such an API… but you can download the data with Google Takeout.  I went here, which allowed me to download this data.  I asked for JSON data and it provided me a 1.2MB zip file… it was surprising how small the data was.

Once extracted it was a 10MB JSON file with 400k lines,  a little more substantial.

Now to work out how to calculate the average time to get to work.  (Wishing there was an API for this)

Testing changes when contributing to Ansible

This is by no means a detailed post about testing Ansible.  The main reason I’m writing this down is because I know I’ll forget by a few months time.

Goal:

  • Create an Ansible plugin filter to calculate the distance between two coordinates (haversine)
  • Write and test the required unit tests (and integration?) for the code to be accepted into Ansible
  • Raise a PR to the main project

Background

I was writing an Ansible playbook to check the local traffic site for accidents.  The site returns a list of incidents state wide, which, is great, but I was only interested in a 10km radius.  Eg:

 .....
 with_items: “{{ traffic.json.features }}”

 when: item | distance(myLong,myLat, item.geometry.coordinates.1, item.geometry.coordinates.0)|int < 10

This looped over a variable with_items and then passed some coordinates through the a custom plugin filter called distance.  (Note: Distance was a custom plugin filter which I stored in the plugin_filters directory.  I figured I’d go a little further and try to submit to Ansible core.)

Fork

  • I’ve forked ansible/ansible from github into my own repo.
  • I’ve cloned the repo to my development machine
  • Changed directory into the freshly cloned ansible directory and ran `source ./hacking/env-setup`.

Finding where to add the filter code

Haversine (wikipedia) is a maths formula, so, it’s probably best to hang out with other Ansible filters such as power, logarithm, min, max and friends.

Grepping through the source for above brought up this:

./lib/ansible/plugins/filter/mathstuff.py

Perfect!  Hopefully.

The code…

Added a function to this file:

def haversine(measurement, lat1, lon1, lat2, lon2):

    from math import radians, sin, cos, sqrt, asin

    diameter = {

        ‘m’: 7917.5,

        ‘km’: 12742}

    try:

        dlat = radians(float(lat2) – float(lat1))

        dlon = radians(float(lon2) – float(lon1))

        lat1 = radians(float(lat1))

        lat2 = radians(float(lat2))

        a = sin(dlat / 2) ** 2 + cos(lat1) * cos(lat2) * sin(dlon / 2) ** 2

        c = 2 * asin(sqrt(a))

    except (ValueError, TypeError) as e:

        raise errors.AnsibleFilterError(‘haversine() only accepts floats: %s’ % str(e))

    if measurement in diameter:

        return round(diameter[measurement] / 2 * c, 2)

    else:

        raise errors.AnsibleFilterError(‘haversine() can only be called with km or m’)

Testing

Ansible has a great test setup.  I started with the unit tests.

Once again, needed to find out where the math stuff testing hung out.  Grep’ing through the test directory found:  test/units/plugins/filter/test_mathstuff.py

Copying the format of existing tests I added a few tests:

class TestHaversine:

    def test_haversine_non_number(self):

        with pytest.raises(AnsibleFilterError, message=’haversine() only accepts floats’):

            ms.haversine(‘km’, ‘a’, ‘b’, ‘c’, ‘d’)

        with pytest.raises(AnsibleFilterError, message=’haversine() only accepts floats’):

            ms.haversine(‘m’, ‘a’, ‘b’, ‘c’, ‘d’)

        with pytest.raises(AnsibleFilterError, message=’haversine() can only be called with km or m’):

            ms.haversine(‘z’, ‘35.9914928’,’-78.907046′, ‘-33.8523063’, ‘151.2085984’)

    def test_km(self):

        assert ms.haversine(‘km’, ‘35.9914928’, ‘-78.907046’, ‘-33.8523063’, ‘151.2085984’) == 15490.46

    def test_m(self):

        assert ms.haversine(‘m’, ‘35.9914928’, ‘-78.907046’, ‘-33.8523063’, ‘151.2085984’) == 9625.31

This was to test:

  • That proper float values were passed through to the function
  • That a proper measure was used (km or m)
  • That two test calculations calculated and rounded nicely

The quickest way to test after making the change was:

ansible-test units --tox --python 2.7 test/units/plugins/filter/test_mathstuff.py

Other types of testing in Ansible were here.

Test playbook

Of course, better test to ensure it’s actually usable in a playbook:

– hosts: localhost

  tasks:

    – name: Haversine distance between two lon/lat co-ordinates

      debug:

        msg: “{{ ‘km’|haversine(‘35.9914928′,’-78.907046′, ‘-33.8523063’, ‘151.2085984’) }}”

    – name: Haversine distance between two lon/lat co-ordinates

      debug:

        msg: “{{ ‘m’|haversine(‘35.9914928′,’-78.907046′, ‘-33.8523063’, ‘151.2085984’) }}”

    – name: Haversine distance between two lon/lat co-ordinates

      debug:

        msg: “{{ ‘m’|haversine(‘a35.9914928′,’-78.907046′, ‘-33.8523063’, ‘151.2085984’) }}”

Which resulted in:

$ ansible-playbook only_filter.yml

PLAY [localhost] *********************************************************************************************************************************************

TASK [Gathering Facts] ***************************************************************************************************************************************

ok: [localhost]

TASK [Haversine distance between two lon/lat co-ordinates] ***************************************************************************************************

ok: [localhost] => {

    “msg”: “15490.46”

}

TASK [Haversine distance between two lon/lat co-ordinates] ***************************************************************************************************

ok: [localhost] => {

    “msg”: “9625.31”

}

TASK [Haversine distance between two lon/lat co-ordinates] ***************************************************************************************************

fatal: [localhost]: FAILED! => {“msg”: “haversine() only accepts floats: could not convert string to float: a35.9914928”}

to retry, use: –limit @/home/johni/ansible/hacking/ansible/only_filter.retry

PLAY RECAP ***************************************************************************************************************************************************

localhost                  : ok=3    changed=0    unreachable=0    failed=1