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 +4 -4
- data/app/models/feature.rb +1 -1
- data/lib/spatial_features/has_fusion_table_features/api.rb +79 -0
- data/lib/spatial_features/has_fusion_table_features/configuration.rb +15 -0
- data/lib/spatial_features/has_fusion_table_features/service.rb +116 -0
- data/lib/spatial_features/has_fusion_table_features.rb +77 -0
- data/lib/spatial_features/has_spatial_features/delayed_feature_import.rb +1 -0
- data/lib/spatial_features/has_spatial_features/feature_import.rb +12 -3
- data/lib/spatial_features/importers/shapefile.rb +1 -1
- data/lib/spatial_features/version.rb +1 -1
- data/lib/spatial_features.rb +10 -0
- metadata +48 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 0fdf60198410db9ef31c7a621c293516c69b5bd4
|
4
|
+
data.tar.gz: 87d7da1814c989804b8fb406346450f9b7df56c3
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 87452c8cdb8b9730de959398e6676ef170233a2c0e191ec1f980f2c5c0a61f9f5202aa96d73cdf732b12c1d8725ec23a9d207807a417ad1f9fb14c9125c12038
|
7
|
+
data.tar.gz: 7438e02baec7504ded90e50b1e44fd2d76671e5308f981088c57ce6099863e40db950ea34e8015c6351a05362b50e47eab3ed62c257fb06a2e72e868109f5fc7
|
data/app/models/feature.rb
CHANGED
@@ -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
|
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,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
|
@@ -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
|
-
|
15
|
-
|
16
|
-
|
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
|
data/lib/spatial_features.rb
CHANGED
@@ -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.
|
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-
|
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
|