mirror of
https://github.com/Freika/dawarich.git
synced 2026-01-10 17:21:38 -05:00
Fix google import and add stats for countries and cities visited and distance traveled by year
This commit is contained in:
parent
2398b83b2e
commit
b6769676c3
13 changed files with 95 additions and 23 deletions
|
|
@ -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/*
|
||||
|
||||
|
|
|
|||
2
Makefile
2
Makefile
|
|
@ -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)
|
||||
|
|
|
|||
16
README.md
16
README.md
|
|
@ -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`.
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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]}%
|
||||
`;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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">
|
||||
|
|
|
|||
|
|
@ -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'>
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -65,6 +65,7 @@ services:
|
|||
depends_on:
|
||||
- dawarich_db
|
||||
- dawarich_redis
|
||||
- dawarich_app
|
||||
|
||||
volumes:
|
||||
db_data:
|
||||
|
|
|
|||
Loading…
Reference in a new issue