Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Publish to PyPi #42

Open
wants to merge 11 commits into
base: master
Choose a base branch
from
29 changes: 29 additions & 0 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
name: Build and Release

on:
release:
types: [created]

jobs:
build:
name: build
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@master
- name: Install
uses: abatilo/actions-poetry@v1.1.0
with:
python_version: 3.8.0
poetry_version: 1.0.0
args: install
- name: Run build
uses: abatilo/actions-poetry@v1.1.0
with:
python_version: 3.8.0
poetry_version: 1.0.0
args: build
- name: Publish package to PyPI
uses: pypa/gh-action-pypi-publish@master
with:
user: __token__
password: ${{ secrets.pypi_password }}
32 changes: 32 additions & 0 deletions .github/workflows/tagbuild.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
name: Test build on tag

on:
push:
# Sequence of patterns matched against refs/tags
tags:
- '*' # Push events to matching anything, suggested format: 0.1.5, X.X.X etc.

jobs:
build:
name: build
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@master
- name: Install
uses: abatilo/actions-poetry@v1.1.0
with:
python_version: 3.8.0
poetry_version: 1.0.0
args: install
- name: Run build
uses: abatilo/actions-poetry@v1.1.0
with:
python_version: 3.8.0
poetry_version: 1.0.0
args: build
- name: Publish package to TestPyPI
uses: pypa/gh-action-pypi-publish@master
with:
user: __token__
password: ${{ secrets.test_pypi_password }}
repository_url: https://test.pypi.org/legacy/
59 changes: 40 additions & 19 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# Geo Heatmap

<p align="center"><img src="https://user-images.githubusercontent.com/45404400/63515170-7a9cd280-c4ea-11e9-8875-e693622ac26e.png" alt="screenshot" width="400"></p>
![screenshot](https://user-images.githubusercontent.com/45404400/63515170-7a9cd280-c4ea-11e9-8875-e693622ac26e.png)

This is a script that generates an interactive geo heatmap from your Google location history data using Python, Folium and OpenStreetMap.

Expand All @@ -10,31 +10,29 @@ This is a script that generates an interactive geo heatmap from your Google loca

If you don't already have Python 3+ installed, grab it from <https://www.python.org/downloads/>. You'll want to download install the latest version of **Python 3.x**. As of 2019-11-22, that is Version 3.8.

For ease of python version handling, I'd recommend installing it through [pyenv](https://github.com/pyenv/pyenv#installation).

### 2. Get Your Location Data

Here you can find out how to download your Google data: <https://support.google.com/accounts/answer/3024190?hl=en></br>
Here you can download all of the data that Google has stored on you: <https://takeout.google.com/>

To use this script, you only need to select and download your "Location History", which Google will provide to you as a JSON file by default. KML is also an output option and is accepted for this program.

### 3. Clone This Repository

On <https://github.com/luka1199/geo-heatmap>, click the green "Clone or Download" button at the top right of the page. If you want to get started with this script more quickly, click the "Download ZIP" button, and extract the ZIP somewhere on your computer.
To use this script, you only need to select and download your "Location History", which Google will provide to you as a JSON file by default. KML is also an output option and is accepted for this program.

### 4. Install Dependencies
### 3. Install the script

In a [command prompt or Terminal window](https://tutorial.djangogirls.org/en/intro_to_command_line/#what-is-the-command-line), [navigate to the directory](https://tutorial.djangogirls.org/en/intro_to_command_line/#change-current-directory) containing this repository's files. Then, type the following, and press enter:
In a [command prompt or Terminal window](https://tutorial.djangogirls.org/en/intro_to_command_line/#what-is-the-command-line), [navigate to the directory](https://tutorial.djangogirls.org/en/intro_to_command_line/#change-current-directory) containing the location data files. Then, type the following, and press enter:

```shell
pip install -r requirements.txt
pip install geo-heatmap
```

### 5. Run the Script
### 4. Run the Script

In the same command prompt or Terminal window, type the following, and press enter:

```shell
python geo_heatmap.py <file> [<file> ...]
geo-heatmap <file> [<file> ...]
```

Replace the string `<file>` from above with the path to any of the following files:
Expand All @@ -48,39 +46,39 @@ Replace the string `<file>` from above with the path to any of the following fil
Single file:

```shell
python geo_heatmap.py C:\Users\Testuser\Desktop\locations.json
geo-heatmap C:\Users\Testuser\Desktop\locations.json
```

```shell
python geo_heatmap.py "C:\Users\Testuser\Desktop\Location History.json"
geo-heatmap "C:\Users\Testuser\Desktop\Location History.json"
```

```shell
python geo_heatmap.py locations.json
geo-heatmap locations.json
```

Multiple files:

```shell
python geo_heatmap.py locations.json locations.kml takeout.zip
geo-heatmap locations.json locations.kml takeout.zip
```

Using the stream option (for users with Memory Errors):

```shell
python geo_heatmap.py -s locations.json
geo-heatmap -s locations.json
```

Set a date range:

```shell
python geo_heatmap.py --min-date 2017-01-02 --max-date 2018-12-30 locations.json
geo-heatmap --min-date 2017-01-02 --max-date 2018-12-30 locations.json
```

#### Usage:

```
usage: geo_heatmap.py [-h] [-o] [--min-date YYYY-MM-DD]
usage: geo-heatmap [-h] [-o] [--min-date YYYY-MM-DD]
[--max-date YYYY-MM-DD] [-s] [--map MAP]
file [file ...]

Expand Down Expand Up @@ -125,7 +123,7 @@ To fix this, download and install the 64-bit version of Python. To do this:
If this does not fix the issue you can use the stream option:

```shell
python geo_heatmap.py -s <file>
geo-heatmap -s <file>
```

This will be slower but will use much less memory to load your location data.
Expand All @@ -144,3 +142,26 @@ pip install progressbar2
```

You probably have progressbar installed, which uses `maxval` as an argument for `__init__`. Progressbar2 uses `max_value`.

## Development

This project is using [Poetry](https://python-poetry.org/) to manage dependencies. You can install it by following their guide.

If you would like to develop on this further, after cloning this repository and navigating into it you can get up and running with the following:

```shell
poetry install
poetry run geo-heatmap
```

To add a new dependency, run:

```shell
poetry add X
```

and for those who want to still use the old `requirements.txt` way of installing dependencies, you can regenerate this with:

```shell
poetry export -f requirements.txt > requirements.txt
```
1 change: 1 addition & 0 deletions geo_heatmap/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
__version__ = '0.1.5'
46 changes: 46 additions & 0 deletions geo_heatmap/console.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
import os
import webbrowser
from argparse import ArgumentParser, RawTextHelpFormatter

from .generator import Generator
from .utils import isTextBasedBrowser


def run():
parser = ArgumentParser(formatter_class=RawTextHelpFormatter)
parser.add_argument(
"files", metavar="file", type=str, nargs='+', help="Any of the following files:\n"
"1. Your location history JSON file from Google Takeout\n"
"2. Your location history KML file from Google Takeout\n"
"3. The takeout-*.zip raw download from Google Takeout \nthat contains either of the above files")
parser.add_argument("-o", "--output", dest="output", metavar="", type=str, required=False,
help="Path of heatmap HTML output file.", default="heatmap.html")
parser.add_argument("--min-date", dest="min_date", metavar="YYYY-MM-DD", type=str, required=False,
help="The earliest date from which you want to see data in the heatmap.")
parser.add_argument("--max-date", dest="max_date", metavar="YYYY-MM-DD", type=str, required=False,
help="The latest date from which you want to see data in the heatmap.")
parser.add_argument("-s", "--stream", dest="stream",
action="store_true", help="Option to iteratively load data.")
parser.add_argument("--map", "-m", dest="map", metavar="MAP", type=str, required=False, default="OpenStreetMap",
help="The name of the map tiles you want to use.\n"
"(e.g. 'OpenStreetMap', 'StamenTerrain', 'StamenToner', 'StamenWatercolor')")

args = parser.parse_args()
data_file = args.files
output_file = args.output
date_range = args.min_date, args.max_date
tiles = args.map
stream_data = args.stream

generator = Generator()
generator.run(data_file, output_file, date_range, stream_data, tiles)
# Check if browser is text-based
if not isTextBasedBrowser(webbrowser.get()):
try:
print("[info] Opening {} in browser".format(output_file))
webbrowser.open("file://" + os.path.realpath(output_file))
except webbrowser.Error:
print("[info] No runnable browser found. Open {} manually.".format(
output_file))
print("[info] Path to heatmap file: \"{}\"".format(
os.path.abspath(output_file)))
76 changes: 18 additions & 58 deletions geo_heatmap.py → geo_heatmap/generator.py
Original file line number Diff line number Diff line change
@@ -1,19 +1,17 @@
#!/usr/bin/env python3

from argparse import ArgumentParser, RawTextHelpFormatter
import collections
import fnmatch
import folium
from folium.plugins import HeatMap
import ijson
import json
import os
from progressbar import ProgressBar, Bar, ETA, Percentage
from utils import *
import webbrowser
from xml.etree import ElementTree
from xml.dom import minidom
import zipfile
from xml.dom import minidom

import folium
import ijson
from folium.plugins import HeatMap
from progressbar import ETA, Bar, Percentage, ProgressBar

from .utils import *


class Generator:
Expand All @@ -38,15 +36,15 @@ def loadJSONData(self, json_file, date_range):
if "latitudeE7" not in loc or "longitudeE7" not in loc:
continue
coords = (round(loc["latitudeE7"] / 1e7, 6),
round(loc["longitudeE7"] / 1e7, 6))
round(loc["longitudeE7"] / 1e7, 6))

if timestampInRange(loc['timestampMs'], date_range):
self.updateCoord(coords)
pb.update(i)

def streamJSONData(self, json_file, date_range):
"""Stream the Google location data from the given json file.

Arguments:
json_file {file} -- An open file-like object with JSON-encoded
Google location data.
Expand All @@ -56,19 +54,19 @@ def streamJSONData(self, json_file, date_range):
# Estimate location amount
max_value_est = sum(1 for line in json_file) / 13
json_file.seek(0)

locations = ijson.items(json_file, "locations.item")
w = [Bar(), Percentage(), " ", ETA()]
with ProgressBar(max_value=max_value_est, widgets=w) as pb:
for i, loc in enumerate(locations):
if "latitudeE7" not in loc or "longitudeE7" not in loc:
continue
coords = (round(loc["latitudeE7"] / 1e7, 6),
round(loc["longitudeE7"] / 1e7, 6))
round(loc["longitudeE7"] / 1e7, 6))

if timestampInRange(loc['timestampMs'], date_range):
self.updateCoord(coords)

if i > max_value_est:
max_value_est = i
pb.max_value = i
Expand Down Expand Up @@ -131,7 +129,7 @@ def loadZIPData(self, file_name, date_range):
self.loadKMLData(read_file, date_range)
else:
raise ValueError("unsupported extension for {!r}: only .json and .kml supported"
.format(file_name))
.format(file_name))

def updateCoord(self, coords):
self.coordinates[coords] += 1
Expand Down Expand Up @@ -171,8 +169,8 @@ def run(self, data_files, output_file, date_range, stream_data, tiles):
"""
for i, data_file in enumerate(data_files):
print("\n({}/{}) Loading data from {}".format(
i + 1,
len(data_files) + 2,
i + 1,
len(data_files) + 2,
data_file))
if data_file.endswith(".zip"):
self.loadZIPData(data_file, date_range)
Expand All @@ -187,51 +185,13 @@ def run(self, data_files, output_file, date_range, stream_data, tiles):
else:
raise NotImplementedError(
"Unsupported file extension for {!r}".format(data_file))

print("\n({}/{}) Generating heatmap".format(
len(data_files) + 1,
len(data_files) + 1,
len(data_files) + 2))
m = self.generateMap(tiles)
print("\n({}/{}) Saving map to {}\n".format(
len(data_files) + 2,
len(data_files) + 2,
output_file))
m.save(output_file)


if __name__ == "__main__":
parser = ArgumentParser(formatter_class=RawTextHelpFormatter)
parser.add_argument(
"files", metavar="file", type=str, nargs='+', help="Any of the following files:\n"
"1. Your location history JSON file from Google Takeout\n"
"2. Your location history KML file from Google Takeout\n"
"3. The takeout-*.zip raw download from Google Takeout \nthat contains either of the above files")
parser.add_argument("-o", "--output", dest="output", metavar="", type=str, required=False,
help="Path of heatmap HTML output file.", default="heatmap.html")
parser.add_argument("--min-date", dest="min_date", metavar="YYYY-MM-DD", type=str, required=False,
help="The earliest date from which you want to see data in the heatmap.")
parser.add_argument("--max-date", dest="max_date", metavar="YYYY-MM-DD", type=str, required=False,
help="The latest date from which you want to see data in the heatmap.")
parser.add_argument("-s", "--stream", dest="stream", action="store_true", help="Option to iteratively load data.")
parser.add_argument("--map", "-m", dest="map", metavar="MAP", type=str, required=False, default="OpenStreetMap",
help="The name of the map tiles you want to use.\n" \
"(e.g. 'OpenStreetMap', 'StamenTerrain', 'StamenToner', 'StamenWatercolor')")

args = parser.parse_args()
data_file = args.files
output_file = args.output
date_range = args.min_date, args.max_date
tiles = args.map
stream_data = args.stream

generator = Generator()
generator.run(data_file, output_file, date_range, stream_data, tiles)
# Check if browser is text-based
if not isTextBasedBrowser(webbrowser.get()):
try:
print("[info] Opening {} in browser".format(output_file))
webbrowser.open("file://" + os.path.realpath(output_file))
except webbrowser.Error:
print("[info] No runnable browser found. Open {} manually.".format(
output_file))
print("[info] Path to heatmap file: \"{}\"".format(os.path.abspath(output_file)))
1 change: 1 addition & 0 deletions utils.py → geo_heatmap/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

TEXT_BASED_BROWSERS = [webbrowser.GenericBrowser, webbrowser.Elinks]


def isTextBasedBrowser(browser):
"""Returns if browser is a text-based browser.

Expand Down
Loading