rgeo 0.1.20 → 0.1.21
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.
- data/History.rdoc +10 -0
- data/README.rdoc +38 -35
- data/Version +1 -1
- data/lib/active_record/connection_adapters/mysql2spatial_adapter.rb +1 -3
- data/lib/active_record/connection_adapters/mysqlspatial_adapter.rb +4 -4
- data/lib/active_record/connection_adapters/postgis_adapter.rb +426 -0
- data/lib/active_record/connection_adapters/spatialite_adapter.rb +488 -0
- data/lib/rgeo.rb +10 -29
- data/lib/rgeo/active_record/arel_modifications.rb +1 -0
- data/lib/rgeo/active_record/base_modifications.rb +27 -10
- data/lib/rgeo/active_record/common.rb +128 -0
- data/lib/rgeo/active_record/mysql_common.rb +14 -51
- data/lib/rgeo/cartesian/factory.rb +2 -2
- data/lib/rgeo/coord_sys.rb +1 -1
- data/lib/rgeo/coord_sys/proj4.rb +3 -2
- data/lib/rgeo/error.rb +0 -3
- data/lib/rgeo/feature.rb +1 -3
- data/lib/rgeo/feature/factory_generator.rb +8 -0
- data/lib/rgeo/geography/factory.rb +2 -2
- data/lib/rgeo/geography/interface.rb +3 -3
- data/lib/rgeo/geos/zm_factory.rb +2 -2
- data/lib/rgeo/wkrep/wkb_parser.rb +35 -36
- data/lib/rgeo/wkrep/wkt_parser.rb +36 -38
- data/test/active_record/common_setup_methods.rb +129 -0
- data/test/active_record/readme.txt +10 -0
- data/test/active_record/tc_mysqlspatial.rb +22 -71
- data/test/active_record/tc_postgis.rb +282 -0
- data/test/active_record/tc_spatialite.rb +198 -0
- data/test/coord_sys/tc_proj4.rb +12 -5
- data/test/projected_geography/tc_geometry_collection.rb +1 -1
- data/test/projected_geography/tc_line_string.rb +1 -1
- data/test/projected_geography/tc_multi_line_string.rb +1 -1
- data/test/projected_geography/tc_multi_point.rb +1 -1
- data/test/projected_geography/tc_multi_polygon.rb +2 -2
- data/test/projected_geography/tc_point.rb +4 -4
- data/test/projected_geography/tc_polygon.rb +1 -1
- data/test/simple_mercator/tc_geometry_collection.rb +1 -1
- data/test/simple_mercator/tc_line_string.rb +1 -1
- data/test/simple_mercator/tc_multi_line_string.rb +1 -1
- data/test/simple_mercator/tc_multi_point.rb +1 -1
- data/test/simple_mercator/tc_multi_polygon.rb +2 -2
- data/test/simple_mercator/tc_point.rb +4 -4
- data/test/simple_mercator/tc_polygon.rb +1 -1
- data/test/simple_mercator/tc_window.rb +1 -1
- data/test/spherical_geography/tc_geometry_collection.rb +1 -1
- data/test/spherical_geography/tc_line_string.rb +1 -1
- data/test/spherical_geography/tc_multi_line_string.rb +1 -1
- data/test/spherical_geography/tc_multi_point.rb +1 -1
- data/test/spherical_geography/tc_multi_polygon.rb +2 -2
- data/test/spherical_geography/tc_point.rb +4 -4
- data/test/spherical_geography/tc_polygon.rb +1 -1
- data/test/tc_oneoff.rb +3 -3
- data/test/wkrep/tc_wkb_parser.rb +14 -14
- data/test/wkrep/tc_wkt_parser.rb +37 -45
- metadata +10 -3
@@ -0,0 +1,488 @@
|
|
1
|
+
# -----------------------------------------------------------------------------
|
2
|
+
#
|
3
|
+
# SpatiaLite adapter for ActiveRecord
|
4
|
+
#
|
5
|
+
# -----------------------------------------------------------------------------
|
6
|
+
# Copyright 2010 Daniel Azuma
|
7
|
+
#
|
8
|
+
# All rights reserved.
|
9
|
+
#
|
10
|
+
# Redistribution and use in source and binary forms, with or without
|
11
|
+
# modification, are permitted provided that the following conditions are met:
|
12
|
+
#
|
13
|
+
# * Redistributions of source code must retain the above copyright notice,
|
14
|
+
# this list of conditions and the following disclaimer.
|
15
|
+
# * Redistributions in binary form must reproduce the above copyright notice,
|
16
|
+
# this list of conditions and the following disclaimer in the documentation
|
17
|
+
# and/or other materials provided with the distribution.
|
18
|
+
# * Neither the name of the copyright holder, nor the names of any other
|
19
|
+
# contributors to this software, may be used to endorse or promote products
|
20
|
+
# derived from this software without specific prior written permission.
|
21
|
+
#
|
22
|
+
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
23
|
+
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
24
|
+
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
25
|
+
# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
|
26
|
+
# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
|
27
|
+
# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
|
28
|
+
# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
29
|
+
# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
|
30
|
+
# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
31
|
+
# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
32
|
+
# POSSIBILITY OF SUCH DAMAGE.
|
33
|
+
# -----------------------------------------------------------------------------
|
34
|
+
;
|
35
|
+
|
36
|
+
|
37
|
+
require 'rgeo/active_record/common'
|
38
|
+
require 'active_record/connection_adapters/sqlite3_adapter'
|
39
|
+
|
40
|
+
|
41
|
+
module ActiveRecord
|
42
|
+
|
43
|
+
class Base
|
44
|
+
|
45
|
+
|
46
|
+
# Create a spatialite connection adapter.
|
47
|
+
|
48
|
+
|
49
|
+
def self.spatialite_connection(config_)
|
50
|
+
unless 'spatialite' == config_[:adapter]
|
51
|
+
raise ::ArgumentError, 'adapter name should be "spatialite"'
|
52
|
+
end
|
53
|
+
unless config_[:database]
|
54
|
+
raise ::ArgumentError, "No database file specified. Missing argument: database"
|
55
|
+
end
|
56
|
+
|
57
|
+
# Allow database path relative to Rails.root, but only if
|
58
|
+
# the database path is not the special path that tells
|
59
|
+
# Sqlite to build a database only in memory.
|
60
|
+
if defined?(::Rails.root) && ':memory:' != config_[:database]
|
61
|
+
config_[:database] = ::File.expand_path(config_[:database], ::Rails.root)
|
62
|
+
end
|
63
|
+
|
64
|
+
unless self.class.const_defined?(:SQLite3)
|
65
|
+
require_library_or_gem('sqlite3')
|
66
|
+
end
|
67
|
+
db_ = ::SQLite3::Database.new(config_[:database], :results_as_hash => true)
|
68
|
+
db_.busy_timeout(config_[:timeout]) unless config_[:timeout].nil?
|
69
|
+
|
70
|
+
# Load SpatiaLite
|
71
|
+
path_ = config_[:libspatialite]
|
72
|
+
if path_ && (!::File.file?(path_) || !::File.readable?(path_))
|
73
|
+
raise "Cannot read libspatialite library at #{path_}"
|
74
|
+
end
|
75
|
+
unless path_
|
76
|
+
prefixes_ = ['/usr/local/spatialite', '/usr/local/libspatialite', '/usr/local', '/opt/local', '/sw/local', '/usr']
|
77
|
+
suffixes_ = ['so', 'dylib'].join(',')
|
78
|
+
prefixes_.each do |prefix_|
|
79
|
+
pa_ = ::Dir.glob("#{prefix_}/lib/libspatialite.{#{suffixes_}}")
|
80
|
+
if pa_.size > 0
|
81
|
+
path_ = pa_.first
|
82
|
+
break
|
83
|
+
end
|
84
|
+
end
|
85
|
+
end
|
86
|
+
unless path_
|
87
|
+
raise 'Cannot find libspatialite in the usual places. Please provide the path in the "libspatialite" config parameter.'
|
88
|
+
end
|
89
|
+
db_.enable_load_extension(1)
|
90
|
+
db_.load_extension(path_)
|
91
|
+
|
92
|
+
ConnectionAdapters::SpatiaLiteAdapter.new(db_, logger, config_)
|
93
|
+
end
|
94
|
+
|
95
|
+
|
96
|
+
end
|
97
|
+
|
98
|
+
|
99
|
+
module ConnectionAdapters # :nodoc:
|
100
|
+
|
101
|
+
|
102
|
+
class SpatiaLiteAdapter < SQLite3Adapter # :nodoc:
|
103
|
+
|
104
|
+
|
105
|
+
ADAPTER_NAME = 'SpatiaLite'.freeze
|
106
|
+
|
107
|
+
@@native_database_types = nil
|
108
|
+
|
109
|
+
|
110
|
+
def native_database_types
|
111
|
+
unless @@native_database_types
|
112
|
+
@@native_database_types = super.dup
|
113
|
+
@@native_database_types.merge!(:geometry => {:name => "geometry"}, :point => {:name => "point"}, :line_string => {:name => "linestring"}, :polygon => {:name => "polygon"}, :geometry_collection => {:name => "geometrycollection"}, :multi_point => {:name => "multipoint"}, :multi_line_string => {:name => "multilinestring"}, :multi_polygon => {:name => "multipolygon"})
|
114
|
+
end
|
115
|
+
@@native_database_types
|
116
|
+
end
|
117
|
+
|
118
|
+
|
119
|
+
def adapter_name
|
120
|
+
ADAPTER_NAME
|
121
|
+
end
|
122
|
+
|
123
|
+
|
124
|
+
def spatialite_version
|
125
|
+
@spatialite_version ||= SQLiteAdapter::Version.new(select_value('SELECT spatialite_version()'))
|
126
|
+
end
|
127
|
+
|
128
|
+
|
129
|
+
def quote(value_, column_=nil)
|
130
|
+
if ::RGeo::Feature::Geometry.check_type(value_)
|
131
|
+
"GeomFromWKB(X'#{::RGeo::WKRep::WKBGenerator.new(:hex_format => true).generate(value_)}', #{value_.srid})"
|
132
|
+
else
|
133
|
+
super
|
134
|
+
end
|
135
|
+
end
|
136
|
+
|
137
|
+
|
138
|
+
def columns(table_name_, name_=nil) #:nodoc:
|
139
|
+
spatial_info_ = spatial_column_info(table_name_)
|
140
|
+
table_structure(table_name_).map do |field_|
|
141
|
+
col_ = SpatialColumn.new(field_['name'], field_['dflt_value'], field_['type'], field_['notnull'].to_i == 0)
|
142
|
+
info_ = spatial_info_[field_['name']]
|
143
|
+
if info_
|
144
|
+
col_.set_srid(info_[:srid])
|
145
|
+
end
|
146
|
+
col_
|
147
|
+
end
|
148
|
+
end
|
149
|
+
|
150
|
+
|
151
|
+
def spatial_indexes(table_name_, name_=nil)
|
152
|
+
table_name_ = table_name_.to_s
|
153
|
+
names_ = select_values("SELECT name FROM sqlite_master WHERE type='table' AND name LIKE 'idx_#{quote_string(table_name_)}_%' AND rootpage=0") || []
|
154
|
+
names_.map do |name_|
|
155
|
+
col_name_ = name_.sub("idx_#{table_name_}_", '')
|
156
|
+
::RGeo::ActiveRecord::Common::IndexDefinition.new(table_name_, name_, false, [col_name_], [], true)
|
157
|
+
end
|
158
|
+
end
|
159
|
+
|
160
|
+
|
161
|
+
def create_table(table_name_, options_={})
|
162
|
+
table_name_ = table_name_.to_s
|
163
|
+
table_definition_ = SpatialTableDefinition.new(self)
|
164
|
+
table_definition_.primary_key(options_[:primary_key] || ::ActiveRecord::Base.get_primary_key(table_name_.singularize)) unless options_[:id] == false
|
165
|
+
yield table_definition_ if block_given?
|
166
|
+
if options_[:force] && table_exists?(table_name_)
|
167
|
+
drop_table(table_name_, options_)
|
168
|
+
end
|
169
|
+
|
170
|
+
create_sql_ = "CREATE#{' TEMPORARY' if options_[:temporary]} TABLE "
|
171
|
+
create_sql_ << "#{quote_table_name(table_name_)} ("
|
172
|
+
create_sql_ << table_definition_.to_sql
|
173
|
+
create_sql_ << ") #{options_[:options]}"
|
174
|
+
execute create_sql_
|
175
|
+
|
176
|
+
table_definition_.spatial_columns.each do |col_|
|
177
|
+
execute("SELECT AddGeometryColumn('#{quote_string(table_name_)}', '#{quote_string(col_.name)}', #{col_.srid}, '#{quote_string(col_.type.to_s.gsub('_','').upcase)}', 'XY', #{col_.null ? 0 : 1})")
|
178
|
+
end
|
179
|
+
end
|
180
|
+
|
181
|
+
|
182
|
+
def drop_table(table_name_, options_={})
|
183
|
+
execute("DELETE from geometry_columns where f_table_name='#{quote_string(table_name_.to_s)}'")
|
184
|
+
super
|
185
|
+
end
|
186
|
+
|
187
|
+
|
188
|
+
def add_column(table_name_, column_name_, type_, options_={})
|
189
|
+
if ::RGeo::ActiveRecord::GEOMETRY_TYPES.include?(type_.to_sym)
|
190
|
+
execute("SELECT AddGeometryColumn('#{quote_string(table_name_.to_s)}', '#{quote_string(column_name_.to_s)}', #{options_[:srid].to_i}, '#{quote_string(type_.to_s)}', 'XY', #{options_[:null] == false ? 0 : 1})")
|
191
|
+
else
|
192
|
+
super
|
193
|
+
end
|
194
|
+
end
|
195
|
+
|
196
|
+
|
197
|
+
def add_index(table_name_, column_name_, options_={})
|
198
|
+
if options_[:spatial]
|
199
|
+
column_name_ = column_name_.first if column_name_.kind_of?(::Array) && column_name_.size == 1
|
200
|
+
table_name_ = table_name_.to_s
|
201
|
+
column_name_ = column_name_.to_s
|
202
|
+
spatial_info_ = spatial_column_info(table_name_)
|
203
|
+
unless spatial_info_[column_name_]
|
204
|
+
raise ::ArgumentError, "Can't create spatial index because column '#{column_name_}' in table '#{table_name_}' is not a geometry column"
|
205
|
+
end
|
206
|
+
result_ = select_value("SELECT CreateSpatialIndex('#{quote_string(table_name_)}', '#{quote_string(column_name_)}')").to_i
|
207
|
+
if result_ == 0
|
208
|
+
raise ::ArgumentError, "Spatial index already exists on table '#{table_name_}', column '#{column_name_}'"
|
209
|
+
end
|
210
|
+
result_
|
211
|
+
else
|
212
|
+
super
|
213
|
+
end
|
214
|
+
end
|
215
|
+
|
216
|
+
|
217
|
+
def remove_index(table_name_, options_={})
|
218
|
+
if options_[:spatial]
|
219
|
+
column_ = options_[:column]
|
220
|
+
unless column_
|
221
|
+
raise ::ArgumentError, "You need to specify a column to remove a spatial index."
|
222
|
+
end
|
223
|
+
table_name_ = table_name_.to_s
|
224
|
+
column_ = column_.to_s
|
225
|
+
spatial_info_ = spatial_column_info(table_name_)
|
226
|
+
unless spatial_info_[column_]
|
227
|
+
raise ::ArgumentError, "Can't remove spatial index because column '#{column_name_}' in table '#{table_name_}' is not a geometry column"
|
228
|
+
end
|
229
|
+
index_name_ = "idx_#{table_name_}_#{column_}"
|
230
|
+
has_index_ = select_value("SELECT COUNT(*) FROM sqlite_master WHERE type='table' AND name='#{quote_string(index_name_)}'").to_i > 0
|
231
|
+
unless has_index_
|
232
|
+
raise ::ArgumentError, "Spatial index not present on table '#{table_name_}', column '#{column_name_}'"
|
233
|
+
end
|
234
|
+
execute("SELECT DisableSpatialIndex('#{quote_string(table_name_)}', '#{quote_string(column_)}')")
|
235
|
+
execute("DROP TABLE #{quote_table_name(index_name_)}")
|
236
|
+
else
|
237
|
+
super
|
238
|
+
end
|
239
|
+
end
|
240
|
+
|
241
|
+
|
242
|
+
def spatial_column_info(table_name_)
|
243
|
+
info_ = execute("SELECT * FROM geometry_columns WHERE f_table_name='#{quote_string(table_name_.to_s)}'")
|
244
|
+
result_ = {}
|
245
|
+
info_.each do |row_|
|
246
|
+
result_[row_['f_geometry_column']] = {
|
247
|
+
:name => row_['f_geometry_column'],
|
248
|
+
:type => row_['type'],
|
249
|
+
:dimension => row_['coord_dimension'],
|
250
|
+
:srid => row_['srid'],
|
251
|
+
:has_index => row_['spatial_index_enabled'],
|
252
|
+
}
|
253
|
+
end
|
254
|
+
result_
|
255
|
+
end
|
256
|
+
|
257
|
+
|
258
|
+
class SpatialTableDefinition < ConnectionAdapters::TableDefinition # :nodoc:
|
259
|
+
|
260
|
+
attr_reader :spatial_columns
|
261
|
+
|
262
|
+
def initialize(base_)
|
263
|
+
super
|
264
|
+
end
|
265
|
+
|
266
|
+
def column(name_, type_, options_={})
|
267
|
+
super
|
268
|
+
col_ = self[name_]
|
269
|
+
if ::RGeo::ActiveRecord::GEOMETRY_TYPES.include?(col_.type.to_sym)
|
270
|
+
col_.extend(GeometricColumnDefinitionMethods) unless col_.respond_to?(:srid)
|
271
|
+
col_.set_srid(options_[:srid].to_i)
|
272
|
+
end
|
273
|
+
self
|
274
|
+
end
|
275
|
+
|
276
|
+
def to_sql
|
277
|
+
@columns.find_all{ |c_| !c_.respond_to?(:srid) }.map{ |c_| c_.to_sql } * ', '
|
278
|
+
end
|
279
|
+
|
280
|
+
def spatial_columns
|
281
|
+
@columns.find_all{ |c_| c_.respond_to?(:srid) }
|
282
|
+
end
|
283
|
+
|
284
|
+
end
|
285
|
+
|
286
|
+
|
287
|
+
module GeometricColumnDefinitionMethods # :nodoc:
|
288
|
+
|
289
|
+
def srid
|
290
|
+
defined?(@srid) ? @srid : 4326
|
291
|
+
end
|
292
|
+
|
293
|
+
def set_srid(value_)
|
294
|
+
@srid = value_
|
295
|
+
end
|
296
|
+
|
297
|
+
end
|
298
|
+
|
299
|
+
|
300
|
+
class SpatialColumn < ConnectionAdapters::SQLiteColumn # :nodoc:
|
301
|
+
|
302
|
+
|
303
|
+
def initialize(name_, default_, sql_type_=nil, null_=true)
|
304
|
+
super(name_, default_, sql_type_, null_)
|
305
|
+
@geometric_type = ::RGeo::ActiveRecord::Common.geometric_type_from_name(sql_type_)
|
306
|
+
@ar_class = ::ActiveRecord::Base
|
307
|
+
@srid = 0
|
308
|
+
end
|
309
|
+
|
310
|
+
|
311
|
+
def set_ar_class(val_)
|
312
|
+
@ar_class = val_
|
313
|
+
end
|
314
|
+
|
315
|
+
def set_srid(val_)
|
316
|
+
@srid = val_
|
317
|
+
end
|
318
|
+
|
319
|
+
|
320
|
+
attr_reader :srid
|
321
|
+
attr_reader :geometric_type
|
322
|
+
|
323
|
+
|
324
|
+
def spatial?
|
325
|
+
type == :geometry
|
326
|
+
end
|
327
|
+
|
328
|
+
|
329
|
+
def klass
|
330
|
+
type == :geometry ? ::RGeo::Feature::Geometry : super
|
331
|
+
end
|
332
|
+
|
333
|
+
|
334
|
+
def type_cast(value_)
|
335
|
+
type == :geometry ? SpatialColumn.string_to_geometry(value_, @ar_class, @srid) : super
|
336
|
+
end
|
337
|
+
|
338
|
+
|
339
|
+
def type_cast_code(var_name_)
|
340
|
+
type == :geometry ? "::ActiveRecord::ConnectionAdapters::SpatiaLiteAdapter::SpatialColumn.string_to_geometry(#{var_name_}, self.class, #{@srid})" : super
|
341
|
+
end
|
342
|
+
|
343
|
+
|
344
|
+
private
|
345
|
+
|
346
|
+
|
347
|
+
def simplified_type(sql_type_)
|
348
|
+
sql_type_ =~ /geometry|point|linestring|polygon/i ? :geometry : super
|
349
|
+
end
|
350
|
+
|
351
|
+
|
352
|
+
def self.string_to_geometry(str_, ar_class_, column_srid_)
|
353
|
+
case str_
|
354
|
+
when ::RGeo::Feature::Geometry
|
355
|
+
str_
|
356
|
+
when ::String
|
357
|
+
if str_.length == 0
|
358
|
+
nil
|
359
|
+
else
|
360
|
+
factory_generator_ = ar_class_.rgeo_factory_generator
|
361
|
+
if str_[0,1] == "\x00"
|
362
|
+
NativeFormatParser.new(factory_generator_).parse(str_) rescue nil
|
363
|
+
else
|
364
|
+
::RGeo::WKRep::WKTParser.new(factory_generator_.call(:srid => column_srid_), :support_ewkt => true).parse(str_)
|
365
|
+
end
|
366
|
+
end
|
367
|
+
else
|
368
|
+
nil
|
369
|
+
end
|
370
|
+
end
|
371
|
+
|
372
|
+
|
373
|
+
end
|
374
|
+
|
375
|
+
|
376
|
+
class NativeFormatParser # :nodoc:
|
377
|
+
|
378
|
+
|
379
|
+
def initialize(factory_generator_)
|
380
|
+
@factory_generator = factory_generator_
|
381
|
+
end
|
382
|
+
|
383
|
+
|
384
|
+
def parse(data_)
|
385
|
+
@little_endian = data_[1,1] == "\x01"
|
386
|
+
srid_ = data_[2,4].unpack(@little_endian ? 'V' : 'N').first
|
387
|
+
@cur_factory = @factory_generator.call(:srid => srid_)
|
388
|
+
begin
|
389
|
+
_start_scanner(data_)
|
390
|
+
obj_ = _parse_object(false)
|
391
|
+
_get_byte(0xfe)
|
392
|
+
ensure
|
393
|
+
_clean_scanner
|
394
|
+
end
|
395
|
+
obj_
|
396
|
+
end
|
397
|
+
|
398
|
+
|
399
|
+
def _parse_object(contained_)
|
400
|
+
_get_byte(contained_ ? 0x69 : 0x7c)
|
401
|
+
type_code_ = _get_integer
|
402
|
+
case type_code_
|
403
|
+
when 1
|
404
|
+
coords_ = _get_doubles(2)
|
405
|
+
@cur_factory.point(*coords_)
|
406
|
+
when 2
|
407
|
+
_parse_line_string
|
408
|
+
when 3
|
409
|
+
interior_rings_ = (1.._get_integer).map{ _parse_line_string }
|
410
|
+
exterior_ring_ = interior_rings_.shift || @cur_factory.linear_ring([])
|
411
|
+
@cur_factory.polygon(exterior_ring_, interior_rings_)
|
412
|
+
when 4
|
413
|
+
@cur_factory.multi_point((1.._get_integer).map{ _parse_object(1) })
|
414
|
+
when 5
|
415
|
+
@cur_factory.multi_line_string((1.._get_integer).map{ _parse_object(2) })
|
416
|
+
when 6
|
417
|
+
@cur_factory.multi_polygon((1.._get_integer).map{ _parse_object(3) })
|
418
|
+
when 7
|
419
|
+
@cur_factory.collection((1.._get_integer).map{ _parse_object(true) })
|
420
|
+
else
|
421
|
+
raise ::RGeo::Error::ParseError, "Unknown type value: #{type_code_}."
|
422
|
+
end
|
423
|
+
end
|
424
|
+
|
425
|
+
|
426
|
+
def _parse_line_string
|
427
|
+
count_ = _get_integer
|
428
|
+
coords_ = _get_doubles(2 * count_)
|
429
|
+
@cur_factory.line_string((0...count_).map{ |i_| @cur_factory.point(*coords_[2*i_,2]) })
|
430
|
+
end
|
431
|
+
|
432
|
+
|
433
|
+
def _start_scanner(data_)
|
434
|
+
@_data = data_
|
435
|
+
@_len = data_.length
|
436
|
+
@_pos = 38
|
437
|
+
end
|
438
|
+
|
439
|
+
|
440
|
+
def _clean_scanner
|
441
|
+
@_data = nil
|
442
|
+
end
|
443
|
+
|
444
|
+
|
445
|
+
def _get_byte(expect_=nil)
|
446
|
+
if @_pos + 1 > @_len
|
447
|
+
raise ::RGeo::Error::ParseError, "Not enough bytes left to fulfill 1 byte"
|
448
|
+
end
|
449
|
+
str_ = @_data[@_pos, 1]
|
450
|
+
@_pos += 1
|
451
|
+
val_ = str_.unpack("C").first
|
452
|
+
if expect_ && expect_ != val_
|
453
|
+
raise ::RGeo::Error::ParseError, "Expected byte 0x#{expect_.to_s(16)} but got 0x#{val_.to_s(16)}"
|
454
|
+
end
|
455
|
+
val_
|
456
|
+
end
|
457
|
+
|
458
|
+
|
459
|
+
def _get_integer
|
460
|
+
if @_pos + 4 > @_len
|
461
|
+
raise ::RGeo::Error::ParseError, "Not enough bytes left to fulfill 1 integer"
|
462
|
+
end
|
463
|
+
str_ = @_data[@_pos, 4]
|
464
|
+
@_pos += 4
|
465
|
+
str_.unpack("#{@little_endian ? 'V' : 'N'}").first
|
466
|
+
end
|
467
|
+
|
468
|
+
|
469
|
+
def _get_doubles(count_)
|
470
|
+
len_ = 8 * count_
|
471
|
+
if @_pos + len_ > @_len
|
472
|
+
raise ::RGeo::Error::ParseError, "Not enough bytes left to fulfill #{count_} doubles"
|
473
|
+
end
|
474
|
+
str_ = @_data[@_pos, len_]
|
475
|
+
@_pos += len_
|
476
|
+
str_.unpack("#{@little_endian ? 'E' : 'G'}*")
|
477
|
+
end
|
478
|
+
|
479
|
+
|
480
|
+
end
|
481
|
+
|
482
|
+
|
483
|
+
end
|
484
|
+
|
485
|
+
end
|
486
|
+
|
487
|
+
|
488
|
+
end
|