From 1f3ee41bcb1bad9566467df2107a65a0c1de2a04 Mon Sep 17 00:00:00 2001 From: Eugene Burmakin Date: Sun, 17 Mar 2024 17:58:09 +0100 Subject: [PATCH] Implement calculation of cities and countries with the number of points and the last timestamp --- app/controllers/points_controller.rb | 17 ++++++- app/models/point.rb | 5 -- app/services/countries_and_cities.rb | 56 +++++++++++++++++++++ app/services/google_maps/timeline_parser.rb | 2 + app/views/points/index.html.erb | 6 +-- app/views/shared/_right_sidebar.html.erb | 10 ++-- config/initializers/00_constants.rb | 1 + docker-compose.yml | 1 + 8 files changed, 85 insertions(+), 13 deletions(-) create mode 100644 app/services/countries_and_cities.rb create mode 100644 config/initializers/00_constants.rb diff --git a/app/controllers/points_controller.rb b/app/controllers/points_controller.rb index f9c1f88b..cdb725d3 100644 --- a/app/controllers/points_controller.rb +++ b/app/controllers/points_controller.rb @@ -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 diff --git a/app/models/point.rb b/app/models/point.rb index 3d0405e6..56543b9e 100644 --- a/app/models/point.rb +++ b/app/models/point.rb @@ -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] = [] } diff --git a/app/services/countries_and_cities.rb b/app/services/countries_and_cities.rb new file mode 100644 index 00000000..fa952448 --- /dev/null +++ b/app/services/countries_and_cities.rb @@ -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 diff --git a/app/services/google_maps/timeline_parser.rb b/app/services/google_maps/timeline_parser.rb index ff568f87..5196e601 100644 --- a/app/services/google_maps/timeline_parser.rb +++ b/app/services/google_maps/timeline_parser.rb @@ -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], diff --git a/app/views/points/index.html.erb b/app/views/points/index.html.erb index 276d5eaf..b201664c 100644 --- a/app/views/points/index.html.erb +++ b/app/views/points/index.html.erb @@ -5,13 +5,13 @@
<%= 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 %>
<%= 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 %>
@@ -23,6 +23,6 @@ <% end %>
-
+
diff --git a/app/views/shared/_right_sidebar.html.erb b/app/views/shared/_right_sidebar.html.erb index 9798125d..600bc4de 100644 --- a/app/views/shared/_right_sidebar.html.erb +++ b/app/views/shared/_right_sidebar.html.erb @@ -1,9 +1,11 @@ +<%= "#{@distance} km" if @distance %> + <% if @countries_and_cities.any? %> - <% @countries_and_cities.each do |country, cities| %> -

<%= country %>

+ <% @countries_and_cities.each do |country| %> +

<%= country[:country] %>

<% end %> diff --git a/config/initializers/00_constants.rb b/config/initializers/00_constants.rb new file mode 100644 index 00000000..bd40aa2c --- /dev/null +++ b/config/initializers/00_constants.rb @@ -0,0 +1 @@ +MINIMUM_POINTS_IN_CITY = ENV.fetch('MINIMUM_POINTS_IN_CITY', 5).to_i diff --git a/docker-compose.yml b/docker-compose.yml index 0a7dac0a..67f07529 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -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