mirror of
https://github.com/Freika/dawarich.git
synced 2026-01-11 09:41:40 -05:00
Implement calculation of cities and countries with the number of points and the last timestamp
This commit is contained in:
parent
fc3f2b52ad
commit
1f3ee41bcb
8 changed files with 85 additions and 13 deletions
|
|
@ -4,10 +4,15 @@ class PointsController < ApplicationController
|
|||
def index
|
||||
@points = Point.where('timestamp >= ? AND timestamp <= ?', start_at, end_at).order(timestamp: :asc)
|
||||
|
||||
@countries_and_cities = @points.group_by(&:country).transform_values { _1.pluck(:city).uniq.compact }
|
||||
@countries_and_cities = CountriesAndCities.new(@points).call
|
||||
@coordinates = @points.pluck(:latitude, :longitude).map { [_1.to_f, _2.to_f] }
|
||||
@distance = distance
|
||||
@start_at = Time.at(start_at)
|
||||
@end_at = Time.at(end_at)
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def start_at
|
||||
return 1.month.ago.beginning_of_day.to_i if params[:start_at].nil?
|
||||
|
||||
|
|
@ -19,4 +24,14 @@ class PointsController < ApplicationController
|
|||
|
||||
params[:end_at].to_datetime.to_i
|
||||
end
|
||||
|
||||
def distance
|
||||
@distance ||= 0
|
||||
|
||||
@coordinates.each_cons(2) do
|
||||
@distance += Geocoder::Calculations.distance_between(_1[0], _1[1], units: :km)
|
||||
end
|
||||
|
||||
@distance.round(1)
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -17,10 +17,6 @@ class Point < ApplicationRecord
|
|||
Time.at(timestamp).strftime('%Y-%m-%d %H:%M:%S')
|
||||
end
|
||||
|
||||
def cities_by_countries
|
||||
group_by { _1.country }.compact.map { |k, v| { k => v.pluck(:city).uniq.compact } }
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def async_reverse_geocode
|
||||
|
|
@ -28,7 +24,6 @@ class Point < ApplicationRecord
|
|||
end
|
||||
end
|
||||
|
||||
|
||||
def group_records_by_hour(records)
|
||||
grouped_records = Hash.new { |hash, key| hash[key] = [] }
|
||||
|
||||
|
|
|
|||
56
app/services/countries_and_cities.rb
Normal file
56
app/services/countries_and_cities.rb
Normal file
|
|
@ -0,0 +1,56 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class CountriesAndCities
|
||||
def initialize(points)
|
||||
@points = points
|
||||
end
|
||||
|
||||
def call
|
||||
grouped_records = group_points
|
||||
mapped_with_cities = map_with_cities(grouped_records)
|
||||
|
||||
filtered_cities = filter_cities(mapped_with_cities)
|
||||
|
||||
normalize_result(filtered_cities)
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
attr_reader :points
|
||||
|
||||
def group_points
|
||||
points.group_by(&:country)
|
||||
end
|
||||
|
||||
def map_with_cities(grouped_records)
|
||||
grouped_records.transform_values do |grouped_points|
|
||||
grouped_points
|
||||
.pluck(:city, :timestamp) # Extract city and timestamp
|
||||
.delete_if { _1.first.nil? } # Remove records without city
|
||||
.group_by { |city, _| city }
|
||||
.transform_values do |cities|
|
||||
{
|
||||
points: cities.count,
|
||||
timestamp: cities.map(&:last).max # Get the maximum timestamp
|
||||
}
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
def filter_cities(mapped_with_cities)
|
||||
# Remove cities with less than MINIMUM_POINTS_IN_CITY
|
||||
mapped_with_cities.transform_values do |cities|
|
||||
cities.reject { |_, data| data[:points] < MINIMUM_POINTS_IN_CITY }
|
||||
end
|
||||
end
|
||||
|
||||
def normalize_result(hash)
|
||||
hash.map do |country, cities|
|
||||
{
|
||||
country: country,
|
||||
cities: cities.map { |city, data| { city: city, points: data[:points], timestamp: data[:timestamp] } }
|
||||
}
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
@ -17,6 +17,8 @@ class GoogleMaps::TimelineParser
|
|||
points_data = parse_json
|
||||
|
||||
points_data.each do |point_data|
|
||||
# next if Point.exists?(timestamp: point_data[:timestamp])
|
||||
|
||||
Point.create(
|
||||
latitude: point_data[:latitude],
|
||||
longitude: point_data[:longitude],
|
||||
|
|
|
|||
|
|
@ -5,13 +5,13 @@
|
|||
<div class="w-full md:w-1/3">
|
||||
<div class="flex flex-col space-y-2">
|
||||
<%= f.label :start_at, class: "text-sm font-semibold" %>
|
||||
<%= f.datetime_local_field :start_at, class: "rounded-md w-full", value: params[:start_at] %>
|
||||
<%= f.datetime_local_field :start_at, class: "rounded-md w-full", value: @start_at %>
|
||||
</div>
|
||||
</div>
|
||||
<div class="w-full md:w-1/3">
|
||||
<div class="flex flex-col space-y-2">
|
||||
<%= f.label :end_at, class: "text-sm font-semibold" %>
|
||||
<%= f.datetime_local_field :end_at, class: "rounded-md w-full", value: params[:end_at] %>
|
||||
<%= f.datetime_local_field :end_at, class: "rounded-md w-full", value: @end_at %>
|
||||
</div>
|
||||
</div>
|
||||
<div class="w-full md:w-1/3">
|
||||
|
|
@ -23,6 +23,6 @@
|
|||
<% end %>
|
||||
|
||||
<div class="w-full" data-controller="maps" data-coordinates="<%= @coordinates %>">
|
||||
<div data-maps-target="container" class="h-[25rem] w-auto h-screen"></div>
|
||||
<div data-maps-target="container" class="h-[25rem] w-auto min-h-screen"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -1,9 +1,11 @@
|
|||
<%= "#{@distance} km" if @distance %>
|
||||
|
||||
<% if @countries_and_cities.any? %>
|
||||
<% @countries_and_cities.each do |country, cities| %>
|
||||
<h2 class="text-lg font-semibold"><%= country %></h2>
|
||||
<% @countries_and_cities.each do |country| %>
|
||||
<h2 class="text-lg font-semibold"><%= country[:country] %></h2>
|
||||
<ul>
|
||||
<% cities.each do |city| %>
|
||||
<li><%= city %></li>
|
||||
<% country[:cities].each do |city| %>
|
||||
<li><%= city[:city] %> (<%= Time.at(city[:timestamp]).strftime("%d.%m.%Y") %>)</li>
|
||||
<% end %>
|
||||
</ul>
|
||||
<% end %>
|
||||
|
|
|
|||
1
config/initializers/00_constants.rb
Normal file
1
config/initializers/00_constants.rb
Normal file
|
|
@ -0,0 +1 @@
|
|||
MINIMUM_POINTS_IN_CITY = ENV.fetch('MINIMUM_POINTS_IN_CITY', 5).to_i
|
||||
|
|
@ -40,6 +40,7 @@ services:
|
|||
DATABASE_USERNAME: postgres
|
||||
DATABASE_PASSWORD: password
|
||||
DATABASE_NAME: dawarich_development
|
||||
MINIMUM_POINTS_IN_CITY: 5
|
||||
depends_on:
|
||||
- dawarich_db
|
||||
- dawarich_redis
|
||||
|
|
|
|||
Loading…
Reference in a new issue