Fix google import and add stats for countries and cities visited and distance traveled by year

This commit is contained in:
Eugene Burmakin 2024-03-28 15:11:59 +01:00
parent 2398b83b2e
commit b6769676c3
13 changed files with 95 additions and 23 deletions

View file

@ -34,6 +34,7 @@ RUN apk -U add --no-cache \
&& rm -rf /var/cache/apk/* \
&& mkdir -p $APP_PATH
RUN gem update --system
RUN gem install bundler --version "$BUNDLE_VERSION" \
&& rm -rf $GEM_HOME/cache/*

View file

@ -3,3 +3,5 @@ build_and_push:
docker build . -t dawarich:$(version) --platform=linux/amd64
docker tag dawarich:$(version) registry.chibi.rodeo/dawarich:$(version)
docker push registry.chibi.rodeo/dawarich:$(version)
docker tag dawarich:$(version) freikin/dawarich:$(version)
docker push freikin/dawarich:$(version)

View file

@ -1,23 +1,27 @@
# Dawarich
This is a Rails app that receives location updates from Owntracks and stores them in a database. It also provides a web interface to view the location history.
Dawarich is a self-hosted web application to replace Google Timeline (aka Google Location History). It allows you to import your location history from Google Maps Timeline and Owntracks, view it on a map and see some statistics, such as the number of countries and cities visited, and distance traveled.
## Usage
To track your location, install the Owntracks [app](https://owntracks.org/booklet/guide/apps/) on your phone and configure it to send location updates to your Dawarich instance. Currently, the app only supports HTTP mode. The url to send the location updates to is `http://<your-dawarich-instance>/api/v1/points`.
To track your location, install the [Owntracks app](https://owntracks.org/booklet/guide/apps/) on your phone and configure it to send location updates to your Dawarich instance. Currently, the app only supports [HTTP mode](https://owntracks.org/booklet/tech/http/). The url to send the location updates to is `http://<your-dawarich-instance>/api/v1/points`.
To import your Google Maps Timeline data, download your location history from [Google Takeout](https://takeout.google.com/) and upload it to Dawarich.
## Features
### Import
You can import your Google Maps Timeline data into Dawarich as well as Owntracks data.
### Location history
You can view your location history on a map.
### Statistics
You can see the number of countries and cities visited, the distance traveled, and the time spent in each country, splitted by years and months.
### Import
You can import your Google Maps Timeline data into Dawarich as well as Owntracks data.
## How to start the app locally
`docker-compose up` to start the app. The app will be available at `http://localhost:3000`.

View file

@ -7,7 +7,7 @@ class StatsController < ApplicationController
def show
@year = params[:year].to_i
@stats = current_user.stats.where(year: @year)
@stats = current_user.stats.where(year: @year).order(:month)
end
def update

View file

@ -26,4 +26,16 @@ module ApplicationHelper
def header_colors
%w[info success warning error accent secondary primary]
end
def countries_and_cities_stat(year)
data = Stat.year_cities_and_countries(year)
countries = data[:countries]
cities = data[:cities]
"#{countries} countries, #{cities} cities"
end
def year_distance_stat_in_km(year)
Stat.year_distance(year).sum { _1[1] }
end
end

View file

@ -29,8 +29,7 @@ export default class extends Controller {
<b>Longitude:</b> ${marker[1]}<br>
<b>Altitude:</b> ${marker[3]}m<br>
<b>Velocity:</b> ${marker[5]}km/h<br>
<b>Battery:</b> ${marker[2]}%<br>
id: ${marker[6]}<br>
<b>Battery:</b> ${marker[2]}%
`;
}

View file

@ -34,6 +34,21 @@ class Stat < ApplicationRecord
def self.year_distance(year)
stats = where(year: year).order(:month)
stats.map { |stat| [Date::MONTHNAMES[stat.month], stat.distance] }
(1..12).to_a.map do |month|
month_stat = stats.select { |stat| stat.month == month }.first
month_name = Date::MONTHNAMES[month]
distance = month_stat&.distance || 0
[month_name, distance]
end
end
def self.year_cities_and_countries(year)
points = Point.where(timestamp: DateTime.new(year).beginning_of_year..DateTime.new(year).end_of_year)
data = CountriesAndCities.new(points).call
{ countries: data.count, cities: data.sum { |country| country[:cities].count } }
end
end

View file

@ -23,7 +23,7 @@ class User < ApplicationRecord
end
def total_countries
Stat.where(user: self).pluck(:toponyms).flatten.uniq.size
Stat.where(user: self).pluck(:toponyms).flatten.map { _1['country'] }.uniq.size
end
def total_cities

View file

@ -60,12 +60,28 @@ class GoogleMaps::TimelineParser
}
end
elsif timeline_object['placeVisit'].present?
if timeline_object['placeVisit']['location']['latitudeE7'].present? &&
timeline_object['placeVisit']['location']['longitudeE7'].present?
{
latitude: timeline_object['placeVisit']['location']['latitudeE7'].to_f / 10**7,
longitude: timeline_object['placeVisit']['location']['longitudeE7'].to_f / 10**7,
timestamp: DateTime.parse(timeline_object['placeVisit']['duration']['startTimestamp']),
raw_data: timeline_object
}
elsif timeline_object['placeVisit']['otherCandidateLocations'].any?
point = timeline_object['placeVisit']['otherCandidateLocations'][0]
next unless point['latitudeE7'].present? && point['longitudeE7'].present?
{
latitude: point['latitudeE7'].to_f / 10**7,
longitude: point['longitudeE7'].to_f / 10**7,
timestamp: DateTime.parse(timeline_object['placeVisit']['duration']['startTimestamp']),
raw_data: timeline_object
}
else
next
end
end
end.reject(&:blank?)
end

View file

@ -5,9 +5,9 @@
<svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5" fill="none" viewBox="0 0 24 24" stroke="currentColor"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 6h16M4 12h8m-8 6h16" /></svg>
</label>
<ul tabindex="0" class="menu menu-sm dropdown-content mt-3 z-[1] p-2 shadow bg-base-100 rounded-box w-52">
<li><%= link_to 'Imports', imports_url %></li>
<li><%= link_to 'Points', points_url %></li>
<li><%= link_to 'Stats', stats_url %></li>
<li><%= link_to 'Imports', imports_url %></li>
</ul>
</div>
<%= link_to 'DaWarIch', root_path, class: 'btn btn-ghost normal-case text-xl mr-10'%>
@ -19,9 +19,9 @@
</div>
<div class="navbar-center hidden lg:flex">
<ul class="menu menu-horizontal px-1">
<li><%= link_to 'Imports', imports_url %></li>
<li><%= link_to 'Points', points_url %></li>
<li><%= link_to 'Stats', stats_url %></li>
<li><%= link_to 'Imports', imports_url %></li>
</ul>
</div>
<div class="navbar-end">

View file

@ -1,5 +1,5 @@
<h2 class='text-3xl font-bold mt-10'>
<%= link_to year, "/stats/#{year}", class: 'underline hover:no-underline' %>
<%= link_to year, "/stats/#{year}", class: "underline hover:no-underline text-#{header_colors.sample}" %>
<%= link_to '[Map]', points_url(year_timespan(year)), class: 'underline hover:no-underline' %>
</h2>
<div class='my-10'>

View file

@ -40,7 +40,29 @@
<%= link_to 'Update stats', stats_path, data: { 'turbo-method' => :post }, class: 'btn btn-primary mt-5' %>
<div class="mt-5 grid grid-cols-1 sm:grid-cols-1 md:grid-cols-2 lg:grid-cols-2 gap-6 p-4">
<% @stats.each do |year, stats| %>
<%= render partial: 'stats/year', locals: { year: year, stats: stats } %>
<div class="card w-full bg-base-200 shadow-xl">
<div class="card-body">
<h2 class="card-title text-<%= header_colors.sample %>">
<%= link_to year, "/stats/#{year}", class: 'underline hover:no-underline' %>
<%= link_to '[Map]', points_url(year_timespan(year)), class: 'underline hover:no-underline' %>
</h2>
<p><%= number_with_delimiter year_distance_stat_in_km(year) %>km</p>
<% if REVERSE_GEOCODING_ENABLED %>
<div class="card-actions justify-end">
<%= countries_and_cities_stat(year) %>
</div>
<% end %>
<%= column_chart(
Stat.year_distance(year),
height: '200px',
suffix: ' km',
xtitle: 'Days',
ytitle: 'Distance'
) %>
</div>
</div>
<% end %>
</div>
</div>

View file

@ -65,6 +65,7 @@ services:
depends_on:
- dawarich_db
- dawarich_redis
- dawarich_app
volumes:
db_data: