HabitatMap/AirCasting

View on GitHub
app/services/timelapse/clusters_creator.rb

Summary

Maintainability
A
0 mins
Test Coverage
module Timelapse
  class ClustersCreator
    def initialize
      @measurements_repository = MeasurementsRepository.new
      @streams_repository = StreamsRepository.new
      @cluster_processor = ClusterProcessor.new
      @sessions_repository = SessionsRepository.new
    end

    def call(params:)
      sessions = filtered_sessions(params)
      zoom_level = params[:zoom_level] || 1
      sensor_name = params[:sensor_name]
      streams = streams_repository.find_by_session_id(sessions.pluck(:id))
      selected_sensor_streams = streams.select { |stream| Sensor.sensor_name(sensor_name).include? stream.sensor_name.downcase }
      streams_with_coordinates = streams_with_coordinates(selected_sensor_streams)

      clusters = cluster_measurements(streams_with_coordinates, zoom_level)
      clusters = calculate_centroids_for_clusters(clusters)
      cluster_processor.call(clusters: clusters)
    end

    private

    attr_reader :measurements_repository, :streams_repository, :cluster_processor, :sessions_repository

    def filtered_sessions(params)
      sessions_active_in_last_7_days = sessions_repository.active_in_last_7_days
      sessions_active_in_last_7_days.filter_(params)
    end

    def streams_with_coordinates(streams)
      streams.map do |stream|
        {
          stream_id: stream.id,
          latitude: stream.session.latitude,
          longitude: stream.session.longitude
        }
      end
    end

    def cluster_measurements(streams_with_coordinates, zoom_level)
      grid_cell_size = determine_grid_cell_size(zoom_level)

      grid = Hash.new { |hash, key| hash[key] = [] }

      streams_with_coordinates.each do |stream_with_coordinates|
        stream_id, latitude, longitude =
          stream_with_coordinates.values_at(
            :stream_id,
            :latitude,
            :longitude
          )

        cell_x = (longitude.to_f / grid_cell_size).floor
        cell_y = (latitude.to_f / grid_cell_size).floor

        grid[[cell_x, cell_y]] << { stream_id: stream_id, latitude: latitude.to_f, longitude: longitude.to_f }
      end

      clusters = grid.values

      clusters
    end

    def determine_grid_cell_size(zoom_level)
      base_cell_size = 15
      cell_size = base_cell_size / (1.7**zoom_level)
      minimum_cell_size = 0.0001
      [cell_size, minimum_cell_size].max
    end

    def calculate_centroids_for_clusters(clusters)
      clusters.map do |streams|
        latitudes = streams.map { |stream| stream[:latitude] }
        longitudes = streams.map { |stream| stream[:longitude] }

        next if latitudes.empty? || longitudes.empty?

        centroid_latitude = latitudes.sum / latitudes.size
        centroid_longitude = longitudes.sum / longitudes.size

        {
          latitude: centroid_latitude,
          longitude: centroid_longitude,
          stream_ids: streams.map { |stream| stream[:stream_id] },
          session_count: streams.size
        }
      end.compact
    end
  end
end