Implement calculation of cities and countries with the number of points and the last timestamp

This commit is contained in:
Eugene Burmakin 2024-03-17 17:58:09 +01:00
parent fc3f2b52ad
commit 1f3ee41bcb
8 changed files with 85 additions and 13 deletions

View file

@ -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

View file

@ -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] = [] }

View 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

View file

@ -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],

View file

@ -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>

View file

@ -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 %>

View file

@ -0,0 +1 @@
MINIMUM_POINTS_IN_CITY = ENV.fetch('MINIMUM_POINTS_IN_CITY', 5).to_i

View file

@ -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