# frozen_string_literal: true require 'georuby' require 'sequel' require 'sequel-postgis-georuby' require 'singleton' require 'concurrent-ruby' require 'haversine' module TacScribe # The in-memory cache that is updated by events in real-time before the data # is synced to the DB class Cache include Singleton include GeoRuby::SimpleFeatures @@cache = Concurrent::Hash.new attr_accessor :reference_latitude, :reference_longitude def data @@cache end def write_object(object) if reference_latitude != 0 || reference_longitude != 0 localize_position(object) end id = object[:object_id] object[:id] = id cache_object = @@cache[id] object[:position] = set_position(object, cache_object) %i[object_id latitude longitude color country].each do |key| object.delete(key) end if object[:coalition] object[:coalition] = case object[:coalition] when 'Allies' 0 when 'Enemies' 1 else 2 end end if cache_object object[:heading] = calculate_heading(cache_object, object) object[:speed] = calculate_speed(cache_object, object) cache_object.merge!(object) else object[:heading] = -1 object[:speed] = 0 cache_object = object end if !cache_object.key?(:altitude) || !cache_object[:altitude] cache_object[:altitude] = 0 end if !cache_object.key?(:coalition) || !cache_object[:coalition] cache_object[:coalition] = 2 end cache_object[:pilot] = nil unless cache_object.key?(:pilot) cache_object[:group] = nil unless cache_object.key?(:group) cache_object[:deleted] = false unless cache_object.key?(:deleted) cache_object[:updated_at] = Time.now @@cache[id] = cache_object end def delete_object(id) @@cache[id][:deleted] = true if @@cache[id] end def clear @@cache.clear self.reference_latitude = nil self.reference_longitude = nil end private def localize_position(object) if reference_latitude && object.key?(:latitude) object[:latitude] = reference_latitude + object[:latitude] end if reference_longitude && object.key?(:longitude) object[:longitude] = reference_longitude + object[:longitude] end end def set_position(object, cache_object) longitude = if object.key?(:longitude) object[:longitude] else cache_object[:position].y end latitude = if object.key?(:latitude) object[:latitude] else cache_object[:position].x end # This "Point" class is not lat/long aware which is why we are # flipping things up here because when it converts to SQL we need # the long to be first since that is what postgresql expects. Point.from_x_y(longitude, latitude) end def calculate_heading(cache_object, object) if cache_object[:position] == object[:position] return cache_object[:heading] end begin cache_object[:position].bearing_to(object[:position]).to_i rescue Math::DomainError => e puts 'Could not calculate heading: ' + e.message puts 'Old Position: ' + cache_object[:position].inspect puts 'New Position: ' + object[:position].inspect end end def calculate_speed(cache_object, object) time = object[:game_time] - cache_object[:game_time] start_point = cache_object[:position] end_point = object[:position] # Because of the above issues with Point and Lat/Lon # the values are reverse here :( distance = Haversine.distance(start_point.y, start_point.x, end_point.y, end_point.x).to_meters speed = distance / time speed.to_i end end end