spatial_features 2.0.0 → 2.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: ee690ccc750978af8db4b018a4564dcad10085cc
4
- data.tar.gz: cb9e15cdee984d7c7398f68ec95af49a973a0571
3
+ metadata.gz: 0fdf60198410db9ef31c7a621c293516c69b5bd4
4
+ data.tar.gz: 87d7da1814c989804b8fb406346450f9b7df56c3
5
5
  SHA512:
6
- metadata.gz: d1816cccfc59bb3d8ad6dfd19467a734e0889fd658224d19099e0c7266dd30b16ecc6ff5c305c557649fe4278c3c0dcab9be4ee8f10d5aa1db37103c60a119d1
7
- data.tar.gz: 97f3e7e7eff1d3feac000560651944bd391d5533055a3131b29ad39901ee9442d23d70854f2b10c796006129cd38f8e336e2a5f73d53cffbcb642d3ac43ad5ca
6
+ metadata.gz: 87452c8cdb8b9730de959398e6676ef170233a2c0e191ec1f980f2c5c0a61f9f5202aa96d73cdf732b12c1d8725ec23a9d207807a417ad1f9fb14c9125c12038
7
+ data.tar.gz: 7438e02baec7504ded90e50b1e44fd2d76671e5308f981088c57ce6099863e40db950ea34e8015c6351a05362b50e47eab3ed62c257fb06a2e72e868109f5fc7
@@ -114,7 +114,7 @@ class Feature < ActiveRecord::Base
114
114
  self.geog = ActiveRecord::Base.connection.select_value("SELECT ST_CollectionExtract(ST_MakeValid('#{sanitize}'),3)")
115
115
  end
116
116
 
117
- # Use ST_Force_2D to discard z-coordinates that cause failures later in the process
117
+ # Use ST_Force2D to discard z-coordinates that cause failures later in the process
118
118
  def sanitize
119
119
  self.geog = ActiveRecord::Base.connection.select_value("SELECT ST_Force2D('#{geog}')")
120
120
  end
@@ -0,0 +1,79 @@
1
+ module SpatialFeatures
2
+ module FusionTables
3
+ module API
4
+ extend self
5
+
6
+ FEATURE_COLUMNS = {:name => 'STRING', :spatial_model_type => 'STRING', :spatial_model_id => 'NUMBER', :kml_lowres => 'LOCATION', :colour => 'STRING'}
7
+ TABLE_STYLE = {
8
+ :polygon_options => { :fill_color_styler => { :kind => 'fusiontables#fromColumn', :column_name => 'colour' }, :stroke_color => '#000000', :stroke_opacity => 0.2 },
9
+ :polyline_options => { :stroke_color_styler => { :kind => 'fusiontables#fromColumn', :column_name => 'colour'} }
10
+ }
11
+
12
+ def find_or_create_table(name)
13
+ find_table(name) || create_table(name)
14
+ end
15
+
16
+ def create_table(name)
17
+ table_id = service.create_table(name, FEATURE_COLUMNS.collect {|name, type| {:name => name, :type => type} })
18
+ service.share_table(table_id)
19
+ service.insert_style(table_id, TABLE_STYLE)
20
+ return table_id
21
+ end
22
+
23
+ def find_table(name)
24
+ service.tables.find {|table| table['name'] == name }.try(:fetch, 'tableId')
25
+ end
26
+
27
+ def delete_table(table_id)
28
+ service.delete_table(table_id)
29
+ end
30
+
31
+ def set_features(table_id, features, colour: nil)
32
+ colour_features(features, colour)
33
+ service.replace_rows(table_id, features_to_csv(features))
34
+ end
35
+
36
+ def service
37
+ @service ||= Service.new(Configuration.service_account_credentials)
38
+ end
39
+
40
+ private
41
+
42
+ def features_to_csv(features)
43
+ csv = CSV.generate do |csv|
44
+ features.each do |feature|
45
+ csv << FEATURE_COLUMNS.keys.collect {|attribute| feature.send(attribute) }
46
+ end
47
+ end
48
+
49
+ file = Tempfile.open
50
+ file.write(csv)
51
+ return file
52
+ end
53
+
54
+ def colour_features(features, colour)
55
+ case colour
56
+ when Symbol
57
+ ActiveRecord::Associations::Preloader.new.preload(features, :spatial_model)
58
+ features.each do |feature|
59
+ feature.define_singleton_method(:colour) do
60
+ spatial_model.send(colour)
61
+ end
62
+ end
63
+ when Proc
64
+ features.each do |feature|
65
+ feature.define_singleton_method(:colour) do
66
+ colour.call(feature)
67
+ end
68
+ end
69
+ else
70
+ features.each do |feature|
71
+ feature.define_singleton_method(:colour) do
72
+ colour
73
+ end
74
+ end
75
+ end
76
+ end
77
+ end
78
+ end
79
+ end
@@ -0,0 +1,15 @@
1
+ module SpatialFeatures
2
+ module FusionTables
3
+ def self.config
4
+ if block_given?
5
+ yield Configuration
6
+ else
7
+ Configuration
8
+ end
9
+ end
10
+
11
+ module Configuration
12
+ mattr_accessor :service_account_credentials
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,116 @@
1
+ require 'json'
2
+
3
+ module SpatialFeatures
4
+ module FusionTables
5
+ class Service
6
+ APPLICATION_NAME = 'Fusion Tables + Spatial Features'
7
+ GOOGLE_AUTH_SCOPES = %w(https://www.googleapis.com/auth/fusiontables https://www.googleapis.com/auth/drive)
8
+
9
+ def initialize(service_account_credentials_path)
10
+ @authorization = get_authorization(service_account_credentials_path, GOOGLE_AUTH_SCOPES)
11
+ end
12
+
13
+ def table_ids
14
+ tables.collect {|t| t['tableId'] }
15
+ end
16
+
17
+ def tables
18
+ parse_reponse(request(:get, 'https://www.googleapis.com/fusiontables/v2/tables')).fetch('items', [])
19
+ end
20
+
21
+ def create_table(name, columns = [], table_options = {})
22
+ body = {:name => name, :columns => columns}.merge(:description => "Features", :isExportable => true).merge(table_options).to_json
23
+ response = request(:post, 'https://www.googleapis.com/fusiontables/v2/tables', :body => body)
24
+ return parse_reponse(response)['tableId']
25
+ end
26
+
27
+ def select(query)
28
+ parse_reponse request(:get, "https://www.googleapis.com/fusiontables/v2/query", :params => {:sql => query})
29
+ end
30
+
31
+ def delete_table(table_id)
32
+ request(:delete, "https://www.googleapis.com/fusiontables/v2/tables/#{table_id}")
33
+ end
34
+
35
+ def style_ids(table_id)
36
+ styles(table_id).collect {|t| t['styleId'] }
37
+ end
38
+
39
+ def styles(table_id)
40
+ fusion_tables_service.list_styles(table_id).items
41
+ end
42
+
43
+ def delete_style(table_id, style_id)
44
+ fusion_tables_service.delete_style(table_id, style_id, :fields => nil)
45
+ end
46
+
47
+ def insert_style(table_id, style)
48
+ style.reverse_merge! 'name' => 'default_table_style', 'isDefaultForTable' => true
49
+ fusion_tables_service.insert_style(table_id, style, :fields => 'styleId')
50
+ end
51
+
52
+ def delete_row(table_id, row_id)
53
+ fusion_tables_service.sql_query("DELETE FROM #{table_id} WHERE ROWID = #{row_id}")
54
+ end
55
+
56
+ def row_ids(table_id, conditions = {})
57
+ clause = conditions.collect {|column, value| ActiveRecord::Base.send(:sanitize_sql_array, ["? IN (?)", column, value]) }.join(' AND ')
58
+ where = "WHERE #{clause}" if clause.present?
59
+ return fusion_tables_service.sql_query_get("SELECT rowid FROM #{table_id} #{where}}").rows.flatten
60
+ end
61
+
62
+ # Process mutliple commands in a single HTTP request
63
+ def bulk(&block)
64
+ fusion_tables_service.batch do
65
+ block.call(self)
66
+ end
67
+ end
68
+
69
+ def request(method, url, header: {}, body: {}, params: {})
70
+ headers = @authorization.apply({'Content-Type': 'application/json'})
71
+ headers.merge!(header)
72
+ headers.merge!(:params => params)
73
+ return RestClient::Request.execute(:method => method, :url => url, :headers => headers, :payload => body)
74
+ rescue RestClient::ExceptionWithResponse => e
75
+ puts e.response
76
+ raise e
77
+ end
78
+
79
+ def parse_reponse(response)
80
+ JSON.parse(response.body)
81
+ end
82
+
83
+ def replace_rows(table_id, csv)
84
+ fusion_tables_service.replace_table_rows(table_id, :upload_source => csv, :options => {:open_timeout_sec => 1.hour})
85
+ end
86
+
87
+ def upload_rows(table_id, csv)
88
+ fusion_tables_service.import_rows(table_id, :upload_source => csv, :options => {:open_timeout_sec => 1.hour})
89
+ end
90
+
91
+ def share_table(table_id)
92
+ permission = {:type => 'anyone', :role => 'reader', :withLink => true}
93
+ drive_service.create_permission(table_id, permission, :fields => 'id')
94
+ end
95
+
96
+ def fusion_tables_service
97
+ @fusion_tables_service ||= Google::Apis::FusiontablesV2::FusiontablesService.new.tap do |service|
98
+ service.client_options.application_name = APPLICATION_NAME
99
+ service.authorization = @authorization
100
+ end
101
+ end
102
+
103
+ def drive_service
104
+ @drive_service ||= Google::Apis::DriveV3::DriveService.new.tap do |drive|
105
+ drive.client_options.application_name = APPLICATION_NAME
106
+ drive.authorization = @authorization
107
+ end
108
+ end
109
+
110
+ def get_authorization(service_account_credentials_path, scopes)
111
+ ENV['GOOGLE_APPLICATION_CREDENTIALS'] = service_account_credentials_path
112
+ return Google::Auth.get_application_default(scopes)
113
+ end
114
+ end
115
+ end
116
+ end
@@ -0,0 +1,77 @@
1
+ module SpatialFeatures
2
+ module FusionTables
3
+ module ActMethod
4
+ def has_fusion_table_features(options = {})
5
+ class_attribute :fusion_table_features_options
6
+ self.fusion_table_features_options = options
7
+
8
+ include InstanceMethods
9
+ extend ClassMethods
10
+
11
+ delegate :update_fusion_table, :delete_fusion_table, :fusion_table_id_cache, :to => self
12
+ end
13
+ end
14
+
15
+ module ClassMethods
16
+ def to_fusion_condition
17
+ sanitize_sql(["spatial_model_id IN (?)", pluck(:id)])
18
+ end
19
+
20
+ def update_fusion_tables
21
+ fusion_table_groups do |fusion_table_id, records, group_features|
22
+ API.set_features(fusion_table_id, group_features, :colour => fusion_table_features_options[:colour])
23
+ end
24
+ end
25
+
26
+ def delete_fusion_tables
27
+ fusion_table_groups do |fusion_table_id, records, group_features|
28
+ API.delete_table(fusion_table_id)
29
+ end
30
+ fusion_table_id_cache.clear
31
+ end
32
+
33
+ def acts_like_fusion_table_features?
34
+ true
35
+ end
36
+
37
+ def fusion_table_id_cache
38
+ @fusion_table_id_cache ||= Hash.new do |hash, table_name|
39
+ hash[table_name] = API.find_or_create_table(table_name)
40
+ end
41
+ end
42
+
43
+ private
44
+
45
+ def fusion_table_groups
46
+ all.group_by(&:fusion_table_id).each do |fusion_table_id, records|
47
+ yield fusion_table_id, records, features.where(:spatial_model_id => records)
48
+ end
49
+ end
50
+ end
51
+
52
+ module InstanceMethods
53
+ def acts_like_fusion_table_features?
54
+ true
55
+ end
56
+
57
+ def fusion_table_id
58
+ fusion_table_id_cache[fusion_table_name]
59
+ end
60
+
61
+ def fusion_table_name
62
+ case fusion_table_features_options[:table_name]
63
+ when Symbol
64
+ send(fusion_table_features_options[:table_name])
65
+ when String
66
+ fusion_table_features_options[:table_name]
67
+ else
68
+ self.class.table_name
69
+ end
70
+ end
71
+
72
+ def to_fusion_condition
73
+ self.class.where(:id => self).to_fusion_condition
74
+ end
75
+ end
76
+ end
77
+ end
@@ -1,5 +1,6 @@
1
1
  module SpatialFeatures
2
2
  module DelayedFeatureImport
3
+ extend ActiveSupport::Concern
3
4
  include FeatureImport
4
5
 
5
6
  def queue_feature_update!(options = {})
@@ -2,6 +2,13 @@ require 'digest/md5'
2
2
 
3
3
  module SpatialFeatures
4
4
  module FeatureImport
5
+ extend ActiveSupport::Concern
6
+
7
+ included do
8
+ extend ActiveModel::Callbacks
9
+ define_model_callbacks :update_features
10
+ end
11
+
5
12
  def update_features!(skip_invalid: false, options: {})
6
13
  options = options.reverse_merge(spatial_features_options).reverse_merge(:import => {})
7
14
 
@@ -11,9 +18,11 @@ module SpatialFeatures
11
18
 
12
19
  return if features_cache_key_matches?(cache_key)
13
20
 
14
- import_features(imports)
15
- validate_features!(imports, skip_invalid)
16
- set_features_cache_key(cache_key)
21
+ run_callbacks :update_features do
22
+ import_features(imports)
23
+ validate_features!(imports, skip_invalid)
24
+ set_features_cache_key(cache_key)
25
+ end
17
26
 
18
27
  return true
19
28
  end
@@ -15,7 +15,7 @@ module SpatialFeatures
15
15
  proj4 = proj4_from_file(file)
16
16
  RGeo::Shapefile::Reader.open(file.path) do |records|
17
17
  records.each do |record|
18
- yield OpenStruct.new data_from_wkt(record.geometry.as_text, proj4).merge(:metadata => record.attributes)
18
+ yield OpenStruct.new data_from_wkt(record.geometry.as_text, proj4).merge(:metadata => record.attributes) if record.geometry.present?
19
19
  end
20
20
  end
21
21
  end
@@ -1,3 +1,3 @@
1
1
  module SpatialFeatures
2
- VERSION = "2.0.0"
2
+ VERSION = "2.1.0"
3
3
  end
@@ -2,6 +2,10 @@
2
2
  require 'rgeo/shapefile'
3
3
  require 'nokogiri'
4
4
  require 'zip'
5
+ require 'rest-client'
6
+ require 'googleauth'
7
+ require 'google/apis/fusiontables_v2'
8
+ require 'google/apis/drive_v3'
5
9
 
6
10
  # LIB
7
11
  require 'spatial_features/caching'
@@ -14,6 +18,11 @@ require 'spatial_features/has_spatial_features'
14
18
  require 'spatial_features/has_spatial_features/feature_import'
15
19
  require 'spatial_features/has_spatial_features/delayed_feature_import'
16
20
 
21
+ require 'spatial_features/has_fusion_table_features'
22
+ require 'spatial_features/has_fusion_table_features/api'
23
+ require 'spatial_features/has_fusion_table_features/configuration'
24
+ require 'spatial_features/has_fusion_table_features/service'
25
+
17
26
  require 'spatial_features/importers/base'
18
27
  require 'spatial_features/importers/file'
19
28
  require 'spatial_features/importers/kml'
@@ -34,6 +43,7 @@ end
34
43
 
35
44
  # Load the act method
36
45
  ActiveRecord::Base.send :extend, SpatialFeatures::ActMethod
46
+ ActiveRecord::Base.send :extend, SpatialFeatures::FusionTables::ActMethod
37
47
 
38
48
  # Suppress date warnings when unzipping KMZ saved by Google Earth, see https://github.com/rubyzip/rubyzip/issues/112
39
49
  Zip.warn_invalid_date = false
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: spatial_features
3
3
  version: !ruby/object:Gem::Version
4
- version: 2.0.0
4
+ version: 2.1.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Ryan Wallace
@@ -9,7 +9,7 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2016-11-25 00:00:00.000000000 Z
12
+ date: 2016-11-30 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: rails
@@ -81,6 +81,48 @@ dependencies:
81
81
  - - "~>"
82
82
  - !ruby/object:Gem::Version
83
83
  version: '1.6'
84
+ - !ruby/object:Gem::Dependency
85
+ name: rest-client
86
+ requirement: !ruby/object:Gem::Requirement
87
+ requirements:
88
+ - - "~>"
89
+ - !ruby/object:Gem::Version
90
+ version: '2.0'
91
+ type: :runtime
92
+ prerelease: false
93
+ version_requirements: !ruby/object:Gem::Requirement
94
+ requirements:
95
+ - - "~>"
96
+ - !ruby/object:Gem::Version
97
+ version: '2.0'
98
+ - !ruby/object:Gem::Dependency
99
+ name: googleauth
100
+ requirement: !ruby/object:Gem::Requirement
101
+ requirements:
102
+ - - "~>"
103
+ - !ruby/object:Gem::Version
104
+ version: 0.5.1
105
+ type: :runtime
106
+ prerelease: false
107
+ version_requirements: !ruby/object:Gem::Requirement
108
+ requirements:
109
+ - - "~>"
110
+ - !ruby/object:Gem::Version
111
+ version: 0.5.1
112
+ - !ruby/object:Gem::Dependency
113
+ name: google-api-client
114
+ requirement: !ruby/object:Gem::Requirement
115
+ requirements:
116
+ - - "~>"
117
+ - !ruby/object:Gem::Version
118
+ version: '0.9'
119
+ type: :runtime
120
+ prerelease: false
121
+ version_requirements: !ruby/object:Gem::Requirement
122
+ requirements:
123
+ - - "~>"
124
+ - !ruby/object:Gem::Version
125
+ version: '0.9'
84
126
  - !ruby/object:Gem::Dependency
85
127
  name: pg
86
128
  requirement: !ruby/object:Gem::Requirement
@@ -128,6 +170,10 @@ files:
128
170
  - lib/spatial_features/controller_helpers/spatial_extensions.rb
129
171
  - lib/spatial_features/download.rb
130
172
  - lib/spatial_features/engine.rb
173
+ - lib/spatial_features/has_fusion_table_features.rb
174
+ - lib/spatial_features/has_fusion_table_features/api.rb
175
+ - lib/spatial_features/has_fusion_table_features/configuration.rb
176
+ - lib/spatial_features/has_fusion_table_features/service.rb
131
177
  - lib/spatial_features/has_spatial_features.rb
132
178
  - lib/spatial_features/has_spatial_features/delayed_feature_import.rb
133
179
  - lib/spatial_features/has_spatial_features/feature_import.rb