spatial_features 2.0.0 → 2.1.0

Sign up to get free protection for your applications and to get access to all the features.
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