231 lines
6.7 KiB
Ruby
231 lines
6.7 KiB
Ruby
|
require 'active_record'
|
||
|
|
||
|
require_relative './carto_json_serializer'
|
||
|
require_dependency 'common/map_common'
|
||
|
require_dependency 'carto/bounding_box_utils'
|
||
|
|
||
|
class Carto::Map < ActiveRecord::Base
|
||
|
include Carto::MapBoundaries
|
||
|
|
||
|
has_many :layers_maps, dependent: :destroy
|
||
|
has_many :layers, -> { order(:order) }, class_name: 'Carto::Layer',
|
||
|
through: :layers_maps,
|
||
|
after_add: Proc.new { |map, layer| layer.after_added_to_map(map) }
|
||
|
|
||
|
has_many :base_layers, -> { order(:order) }, class_name: 'Carto::Layer',
|
||
|
through: :layers_maps,
|
||
|
source: :layer
|
||
|
|
||
|
# autosave must be explicitly disabled due to https://github.com/rails/rails/issues/9336
|
||
|
has_one :user_table, class_name: Carto::UserTable, inverse_of: :map, dependent: :destroy, autosave: false
|
||
|
|
||
|
belongs_to :user
|
||
|
|
||
|
# Autosave disabled because this caused the `inverse_of` to break (visualization.map != self) until `reload`
|
||
|
# Fixed in Rails 5 https://github.com/rails/rails/pull/23197
|
||
|
has_one :visualization, class_name: Carto::Visualization, inverse_of: :map, autosave: false
|
||
|
|
||
|
# bounding_box_sw, bounding_box_new and center should probably be JSON serialized fields
|
||
|
# However, many callers expect to get an string (and do the JSON deserialization themselves), mainly in presenters
|
||
|
# So for now, we are just treating them as strings (see the .to_s in the constant below), but this could be improved
|
||
|
DEFAULT_OPTIONS = {
|
||
|
zoom: 3,
|
||
|
bounding_box_sw: [Carto::BoundingBoxUtils::DEFAULT_BOUNDS[:miny],
|
||
|
Carto::BoundingBoxUtils::DEFAULT_BOUNDS[:minx]].to_s,
|
||
|
bounding_box_ne: [Carto::BoundingBoxUtils::DEFAULT_BOUNDS[:maxy],
|
||
|
Carto::BoundingBoxUtils::DEFAULT_BOUNDS[:maxx]].to_s,
|
||
|
provider: 'leaflet',
|
||
|
center: [30, 0].to_s
|
||
|
}.freeze
|
||
|
|
||
|
serialize :options, ::Carto::CartoJsonSerializer
|
||
|
validates :options, carto_json_symbolizer: true
|
||
|
|
||
|
validate :validate_options
|
||
|
|
||
|
after_initialize :ensure_options
|
||
|
after_save :notify_map_change
|
||
|
after_save :save_table # Manual save, since autosave is disabled
|
||
|
|
||
|
def data_layers
|
||
|
layers.select(&:data_layer?)
|
||
|
end
|
||
|
|
||
|
def carto_layers
|
||
|
layers.select(&:carto?)
|
||
|
end
|
||
|
|
||
|
def user_layers
|
||
|
layers.select(&:user_layer?)
|
||
|
end
|
||
|
|
||
|
def torque_layers
|
||
|
layers.select(&:torque?)
|
||
|
end
|
||
|
|
||
|
def other_layers
|
||
|
layers.reject(&:carto?)
|
||
|
.reject(&:tiled?)
|
||
|
.reject(&:background?)
|
||
|
.reject(&:gmapsbase?)
|
||
|
.reject(&:wms?)
|
||
|
end
|
||
|
|
||
|
def named_map_layers
|
||
|
layers.select(&:named_map_layer?)
|
||
|
end
|
||
|
|
||
|
def viz_updated_at
|
||
|
latest_mapcap = visualization.latest_mapcap
|
||
|
|
||
|
latest_mapcap ? latest_mapcap.created_at : get_the_last_time_tiles_have_changed_to_render_it_in_vizjsons
|
||
|
end
|
||
|
|
||
|
def self.provider_for_baselayer_kind(kind)
|
||
|
kind == 'tiled' ? 'leaflet' : 'googlemaps'
|
||
|
end
|
||
|
|
||
|
# (lat,lon) points on all map data
|
||
|
def center_data
|
||
|
(center.nil? || center == '') ? DEFAULT_OPTIONS[:center] : center.gsub(/\[|\]|\s*/, '').split(',')
|
||
|
end
|
||
|
|
||
|
def view_bounds_data
|
||
|
if view_bounds_sw.nil? || view_bounds_sw == ''
|
||
|
bbox_sw = DEFAULT_OPTIONS[:bounding_box_sw]
|
||
|
else
|
||
|
bbox_sw = view_bounds_sw.gsub(/\[|\]|\s*/, '').split(',').map(&:to_f)
|
||
|
end
|
||
|
if view_bounds_ne.nil? || view_bounds_ne == ''
|
||
|
bbox_ne = DEFAULT_OPTIONS[:bounding_box_ne]
|
||
|
else
|
||
|
bbox_ne = view_bounds_ne.gsub(/\[|\]|\s*/, '').split(',').map(&:to_f)
|
||
|
end
|
||
|
|
||
|
{
|
||
|
# LowerCorner longitude, in decimal degrees
|
||
|
west: bbox_sw[1],
|
||
|
# LowerCorner latitude, in decimal degrees
|
||
|
south: bbox_sw[0],
|
||
|
# UpperCorner longitude, in decimal degrees
|
||
|
east: bbox_ne[1],
|
||
|
# UpperCorner latitude, in decimal degrees
|
||
|
north: bbox_ne[0]
|
||
|
}
|
||
|
end
|
||
|
|
||
|
def writable_by_user?(user)
|
||
|
visualization.writable_by?(user)
|
||
|
end
|
||
|
|
||
|
def contains_layer?(layer)
|
||
|
return false unless layer
|
||
|
|
||
|
layers_maps.map(&:layer_id).include?(layer.id)
|
||
|
end
|
||
|
|
||
|
def notify_map_change
|
||
|
visualization.try(:invalidate_after_commit)
|
||
|
end
|
||
|
alias :force_notify_map_change :notify_map_change
|
||
|
|
||
|
def update_dataset_dependencies
|
||
|
data_layers.each(&:register_table_dependencies)
|
||
|
end
|
||
|
|
||
|
def dashboard_menu=(value)
|
||
|
options[:dashboard_menu] = value
|
||
|
end
|
||
|
|
||
|
def dashboard_menu
|
||
|
options[:dashboard_menu]
|
||
|
end
|
||
|
|
||
|
def layer_selector=(value)
|
||
|
options[:layer_selector] = value
|
||
|
end
|
||
|
|
||
|
def layer_selector
|
||
|
options[:layer_selector]
|
||
|
end
|
||
|
|
||
|
def admits_layer?(layer)
|
||
|
return admits_more_torque_layers? if layer.torque?
|
||
|
return admits_more_data_layers? if layer.data_layer?
|
||
|
return admits_more_base_layers?(layer) if layer.base_layer?
|
||
|
end
|
||
|
|
||
|
def can_add_layer?(user, layer)
|
||
|
return true if layer.base_layer?
|
||
|
return false if user.max_layers && user.max_layers <= data_layers.count
|
||
|
|
||
|
visualization.writable_by?(user)
|
||
|
end
|
||
|
|
||
|
def process_privacy_in(layer)
|
||
|
if layer.uses_private_tables? && visualization.can_be_private?
|
||
|
visualization.privacy = Carto::Visualization::PRIVACY_PRIVATE
|
||
|
visualization.save
|
||
|
end
|
||
|
end
|
||
|
|
||
|
private
|
||
|
|
||
|
def save_table
|
||
|
if user_table && !user_table.persisted?
|
||
|
user_table.map = self
|
||
|
user_table.save!
|
||
|
end
|
||
|
end
|
||
|
|
||
|
def admits_more_data_layers?
|
||
|
!visualization.canonical? || data_layers.empty?
|
||
|
end
|
||
|
|
||
|
def admits_more_torque_layers?
|
||
|
!visualization.canonical? || torque_layers.empty?
|
||
|
end
|
||
|
|
||
|
def admits_more_base_layers?(layer)
|
||
|
# no basemap layer, always allow
|
||
|
return true if user_layers.empty?
|
||
|
# have basemap? then allow only if comes on top (for labels)
|
||
|
layer.order >= layers.last.order
|
||
|
end
|
||
|
|
||
|
def ensure_options
|
||
|
self.zoom ||= DEFAULT_OPTIONS[:zoom]
|
||
|
self.bounding_box_sw ||= DEFAULT_OPTIONS[:bounding_box_sw]
|
||
|
self.bounding_box_ne ||= DEFAULT_OPTIONS[:bounding_box_ne]
|
||
|
self.center ||= DEFAULT_OPTIONS[:center]
|
||
|
self.provider ||= DEFAULT_OPTIONS[:provider]
|
||
|
|
||
|
self.options ||= {}
|
||
|
options[:dashboard_menu] = true if options[:dashboard_menu].nil?
|
||
|
options[:layer_selector] = false if options[:layer_selector].nil?
|
||
|
options[:legends] = legends if options[:legends].nil?
|
||
|
options[:scrollwheel] = scrollwheel if options[:scrollwheel].nil?
|
||
|
|
||
|
options
|
||
|
end
|
||
|
|
||
|
def validate_options
|
||
|
location = "#{Rails.root}/lib/formats/map/options.json"
|
||
|
schema = Carto::Definition.instance.load_from_file(location)
|
||
|
|
||
|
options_wia = options.with_indifferent_access
|
||
|
json_errors = JSON::Validator.fully_validate(schema, options_wia)
|
||
|
errors.add(:options, json_errors.join(', ')) if json_errors.any?
|
||
|
end
|
||
|
|
||
|
def get_the_last_time_tiles_have_changed_to_render_it_in_vizjsons
|
||
|
from_table = user_table.service.data_last_modified if user_table
|
||
|
|
||
|
[from_table, data_layers.map(&:updated_at)].flatten.compact.max
|
||
|
end
|
||
|
|
||
|
def table_name
|
||
|
user_table.try(:name)
|
||
|
end
|
||
|
end
|