rgeo 0.1.22 → 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (86) hide show
  1. data/History.rdoc +31 -0
  2. data/README.rdoc +53 -59
  3. data/Spatial_Programming_With_RGeo.rdoc +120 -67
  4. data/Version +1 -1
  5. data/ext/proj4_c_impl/extconf.rb +1 -1
  6. data/lib/rgeo.rb +80 -87
  7. data/lib/rgeo/cartesian.rb +0 -10
  8. data/lib/rgeo/coord_sys.rb +0 -7
  9. data/lib/rgeo/coord_sys/proj4.rb +2 -2
  10. data/lib/rgeo/error.rb +2 -6
  11. data/lib/rgeo/feature.rb +0 -7
  12. data/lib/rgeo/feature/curve.rb +5 -5
  13. data/lib/rgeo/feature/factory.rb +45 -5
  14. data/lib/rgeo/feature/factory_generator.rb +2 -4
  15. data/lib/rgeo/feature/geometry.rb +97 -27
  16. data/lib/rgeo/feature/geometry_collection.rb +3 -3
  17. data/lib/rgeo/feature/line_string.rb +3 -3
  18. data/lib/rgeo/feature/multi_curve.rb +2 -2
  19. data/lib/rgeo/feature/multi_surface.rb +3 -3
  20. data/lib/rgeo/feature/point.rb +6 -6
  21. data/lib/rgeo/feature/polygon.rb +4 -4
  22. data/lib/rgeo/feature/surface.rb +3 -3
  23. data/lib/rgeo/geographic.rb +0 -11
  24. data/lib/rgeo/geographic/factory.rb +5 -2
  25. data/lib/rgeo/geographic/interface.rb +1 -1
  26. data/lib/rgeo/geos.rb +0 -7
  27. data/lib/rgeo/geos/factory.rb +1 -1
  28. data/lib/rgeo/impl_helper.rb +0 -7
  29. data/lib/rgeo/wkrep.rb +0 -7
  30. data/lib/rgeo/wkrep/wkb_parser.rb +2 -2
  31. data/test/tc_oneoff.rb +0 -1
  32. metadata +7 -155
  33. data/lib/active_record/connection_adapters/mysql2spatial_adapter.rb +0 -124
  34. data/lib/active_record/connection_adapters/mysqlspatial_adapter.rb +0 -136
  35. data/lib/active_record/connection_adapters/postgis_adapter.rb +0 -426
  36. data/lib/active_record/connection_adapters/spatialite_adapter.rb +0 -488
  37. data/lib/rgeo/active_record/arel_modifications.rb +0 -78
  38. data/lib/rgeo/active_record/base_modifications.rb +0 -124
  39. data/lib/rgeo/active_record/common.rb +0 -166
  40. data/lib/rgeo/active_record/mysql_common.rb +0 -150
  41. data/lib/rgeo/all.rb +0 -49
  42. data/lib/rgeo/geo_json.rb +0 -60
  43. data/lib/rgeo/geo_json/coder.rb +0 -401
  44. data/lib/rgeo/geo_json/entities.rb +0 -285
  45. data/lib/rgeo/geo_json/interface.rb +0 -129
  46. data/lib/rgeo/shapefile.rb +0 -60
  47. data/lib/rgeo/shapefile/reader.rb +0 -898
  48. data/test/active_record/common_setup_methods.rb +0 -129
  49. data/test/active_record/readme.txt +0 -43
  50. data/test/active_record/tc_mysqlspatial.rb +0 -170
  51. data/test/active_record/tc_postgis.rb +0 -282
  52. data/test/active_record/tc_spatialite.rb +0 -198
  53. data/test/shapefile/shapelib_testcases/readme.txt +0 -11
  54. data/test/shapefile/shapelib_testcases/test.dbf +0 -0
  55. data/test/shapefile/shapelib_testcases/test.shp +0 -0
  56. data/test/shapefile/shapelib_testcases/test.shx +0 -0
  57. data/test/shapefile/shapelib_testcases/test0.shp +0 -0
  58. data/test/shapefile/shapelib_testcases/test0.shx +0 -0
  59. data/test/shapefile/shapelib_testcases/test1.shp +0 -0
  60. data/test/shapefile/shapelib_testcases/test1.shx +0 -0
  61. data/test/shapefile/shapelib_testcases/test10.shp +0 -0
  62. data/test/shapefile/shapelib_testcases/test10.shx +0 -0
  63. data/test/shapefile/shapelib_testcases/test11.shp +0 -0
  64. data/test/shapefile/shapelib_testcases/test11.shx +0 -0
  65. data/test/shapefile/shapelib_testcases/test12.shp +0 -0
  66. data/test/shapefile/shapelib_testcases/test12.shx +0 -0
  67. data/test/shapefile/shapelib_testcases/test13.shp +0 -0
  68. data/test/shapefile/shapelib_testcases/test13.shx +0 -0
  69. data/test/shapefile/shapelib_testcases/test2.shp +0 -0
  70. data/test/shapefile/shapelib_testcases/test2.shx +0 -0
  71. data/test/shapefile/shapelib_testcases/test3.shp +0 -0
  72. data/test/shapefile/shapelib_testcases/test3.shx +0 -0
  73. data/test/shapefile/shapelib_testcases/test4.shp +0 -0
  74. data/test/shapefile/shapelib_testcases/test4.shx +0 -0
  75. data/test/shapefile/shapelib_testcases/test5.shp +0 -0
  76. data/test/shapefile/shapelib_testcases/test5.shx +0 -0
  77. data/test/shapefile/shapelib_testcases/test6.shp +0 -0
  78. data/test/shapefile/shapelib_testcases/test6.shx +0 -0
  79. data/test/shapefile/shapelib_testcases/test7.shp +0 -0
  80. data/test/shapefile/shapelib_testcases/test7.shx +0 -0
  81. data/test/shapefile/shapelib_testcases/test8.shp +0 -0
  82. data/test/shapefile/shapelib_testcases/test8.shx +0 -0
  83. data/test/shapefile/shapelib_testcases/test9.shp +0 -0
  84. data/test/shapefile/shapelib_testcases/test9.shx +0 -0
  85. data/test/shapefile/tc_shapelib_tests.rb +0 -527
  86. data/test/tc_geojson.rb +0 -279
@@ -1,898 +0,0 @@
1
- # -----------------------------------------------------------------------------
2
- #
3
- # Shapefile reader for RGeo
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
- begin
38
- require 'dbf'
39
- rescue ::LoadError => ex_
40
- end
41
-
42
-
43
- module RGeo
44
-
45
- module Shapefile
46
-
47
-
48
- # Represents a shapefile that is open for reading.
49
- #
50
- # You can use this object to read a shapefile straight through,
51
- # yielding the data in a block; or you can perform random access
52
- # reads of indexed records.
53
- #
54
- # You must close this object after you are done, in order to close
55
- # the underlying files. Alternatively, you can pass a block to
56
- # Reader::open, and the reader will be closed automatically for
57
- # you at the end of the block.
58
- #
59
- # === Dependencies
60
- #
61
- # Attributes in shapefiles are stored in a ".dbf" (dBASE) format
62
- # file. The "dbf" gem is required to read these files. If this
63
- # gem is not installed, shapefile reading will still function,
64
- # but attributes will not be available.
65
- #
66
- # Correct interpretation of the polygon shape type requires some
67
- # functionality that is available in the RGeo::Geos module. Hence,
68
- # reading a polygon shapefile will generally fail if that module is
69
- # not available or the GEOS library is not installed. It is possible
70
- # to bypass this requirement by relaxing the polygon tests and making
71
- # some assumptions about the file format. See the documentation for
72
- # Reader::open for details.
73
- #
74
- # === Shapefile support
75
- #
76
- # This class supports shapefiles formatted according to the 1998
77
- # "ESRI Shapefile Technical Description". It converts shapefile
78
- # data to RGeo geometry objects, as follows:
79
- #
80
- # * Shapefile records are represented by the
81
- # RGeo::Shapefile::Reader::Record class, which provides the
82
- # geometry, the attributes, and the record number (0-based).
83
- # * Attribute reading is supported by the "dbf" gem, which provides
84
- # the proper typecasting for numeric, string, boolean, and
85
- # date/time column types. Data in unrecognized column types are
86
- # returned as strings.
87
- # * All shape types documented in the 1998 publication are supported,
88
- # including point, polyline, polygon, multipoint, and multipatch,
89
- # along with Z and M versions.
90
- # * Null shapes are translated into nil geometry objects. That is,
91
- # Record#geometry will return nil if that record has a null shape.
92
- # * The point shape type yields Point geometries.
93
- # * The multipoint shape type yields MultiPoint geometries.
94
- # * The polyline shape type yields MultiLineString geometries.
95
- # * The polygon shape type yields MultiPolygon geometries.
96
- # * The multipatch shape type yields GeometryCollection geometries.
97
- # (See below for an explanation of why we do not return a
98
- # MultiPolygon.)
99
- #
100
- # Some special notes and limitations in our shapefile support:
101
- #
102
- # * Our implementation assumes that shapefile data is in a Cartesian
103
- # coordinate system when it performs certain computations, such as
104
- # directionality of polygon rings. It also ignores the 180 degree
105
- # longitude seam, so it may not correctly interpret objects whose
106
- # coordinates are in lat/lon space and which span that seam.
107
- # * The ESRI polygon specification allows interior rings to touch
108
- # their exterior ring in a finite number of points. This technically
109
- # violates the OGC Polygon definition. However, such a structure
110
- # remains a legal OGC MultiPolygon, and it is in principle possible
111
- # to detect this case and transform the geometry type accordingly.
112
- # We do not yet do this. Therefore, it is possible for a shapefile
113
- # with polygon type to yield an illegal geometry.
114
- # * The ESRI polygon specification clearly specifies the winding order
115
- # for inner and outer rings: outer rings are clockwise while inner
116
- # rings are counterclockwise. We have heard it reported that there
117
- # may be shapefiles out there that do not conform to this spec. Such
118
- # shapefiles may not read correctly.
119
- # * The ESRI multipatch specification includes triangle strips and
120
- # triangle fans as ways of constructing polygonal patches. We read
121
- # in the aggregate polygonal patches, and do not preserve the
122
- # individual triangles.
123
- # * The ESRI multipatch specification allows separate patch parts to
124
- # share common boundaries, thus effectively becoming a single
125
- # polygon. It is in principle possible to detect this case and
126
- # merge the constituent polygons; however, such a data structure
127
- # implies that the intent is for such polygons to remain distinct
128
- # objects even though they share a common boundary. Therefore, we
129
- # do not attempt to merge such polygons. However, this means it is
130
- # possible for a multipatch to violate the OGC MultiPolygon
131
- # assertions, which do not allow constituent polygons to share a
132
- # common boundary. Therefore, when reading a multipatch, we return
133
- # a GeometryCollection instead of a MultiPolygon.
134
-
135
- class Reader
136
-
137
-
138
- # Values less than this value are considered "no value" in the
139
- # shapefile format specification.
140
- NODATA_LIMIT = -1e38
141
-
142
-
143
- # Create a new shapefile reader. You must pass the path for the
144
- # main shapefile (e.g. "path/to/file.shp"). You may also omit the
145
- # ".shp" extension from the path. All three files that make up the
146
- # shapefile (".shp", ".idx", and ".dbf") must be present for
147
- # successful opening of a shapefile.
148
- #
149
- # If you provide a block, the shapefile reader will be yielded to
150
- # the block, and automatically closed at the end of the block.
151
- # If you do not provide a block, the shapefile reader will be
152
- # returned from this call. It is then the caller's responsibility
153
- # to close the reader when it is done.
154
- #
155
- # Options include:
156
- #
157
- # <tt>:default_factory</tt>::
158
- # The default factory for parsed geometries, used when no factory
159
- # generator is provided. If no default is provided either, the
160
- # default cartesian factory will be used as the default.
161
- # <tt>:factory_generator</tt>::
162
- # A factory generator that should return a factory based on the
163
- # srid and dimension settings in the input. The factory generator
164
- # should understand the configuration options
165
- # <tt>:has_z_coordinate</tt> and <tt>:has_m_coordinate</tt>.
166
- # See RGeo::Feature::FactoryGenerator for more information.
167
- # If no generator is provided, the <tt>:default_factory</tt> is
168
- # used.
169
- # <tt>:srid</tt>::
170
- # If provided, this option is passed to the factory generator.
171
- # This is useful because shapefiles do not contain a SRID.
172
- # <tt>:assume_inner_follows_outer</tt>::
173
- # If set to true, some assumptions are made about ring ordering
174
- # in a polygon shapefile. See below for details. Default is false.
175
- #
176
- # === Ring ordering in polygon shapefiles
177
- #
178
- # The ESRI polygon shape type specifies that the ordering of rings
179
- # in the shapefile is not significant. That is, rings can be in any
180
- # order, and inner rings need not necessarily follow the outer ring
181
- # they are associated with. This specification causes some headache
182
- # in the process of constructing polygons from a shapefile, because
183
- # it becomes necessary to run some geometric analysis on the rings
184
- # that are read in, in order to determine which inner rings should
185
- # go with which outer rings.
186
- #
187
- # RGeo's shapefile reader uses GEOS to perform this analysis.
188
- # However, this means that if GEOS is not available, the analysis
189
- # will fail. It also means reading polygons may be slow, especially
190
- # for polygon records with a large number of parts. Therefore, it
191
- # is possible to turn off this analysis by setting the
192
- # <tt>:assume_inner_follows_outer</tt> switch when creating a
193
- # Reader. This causes the shapefile reader to assume that inner
194
- # rings always follow their corresponding outer ring in the file.
195
- # This is probably true for most well-behaved shapefiles out there,
196
- # but since it is not part of the specification, this shortcutting
197
- # is not turned on by default. However, if you are running RGeo on
198
- # a platform without GEOS, you have no choice but to turn on this
199
- # switch and make this assumption about your input shapefiles.
200
-
201
- def self.open(path_, opts_={}, &block_)
202
- file_ = new(path_, opts_)
203
- if block_
204
- begin
205
- yield file_
206
- ensure
207
- file_.close
208
- end
209
- nil
210
- else
211
- file_
212
- end
213
- end
214
-
215
-
216
- # Low-level creation of a Reader. The arguments are the same as
217
- # those passed to Reader::open, except that this doesn't take a
218
- # block. You should use Reader::open instead.
219
-
220
- def initialize(path_, opts_={}) # :nodoc:
221
- path_.sub!(/\.shp$/, '')
222
- @base_path = path_
223
- @opened = true
224
- @main_file = ::File.open(path_+'.shp', 'rb:ascii-8bit')
225
- @index_file = ::File.open(path_+'.shx', 'rb:ascii-8bit')
226
- @attr_dbf = ::DBF::Table.new(path_+'.dbf') rescue nil
227
- @main_length, @shape_type_code, @xmin, @ymin, @xmax, @ymax, @zmin, @zmax, @mmin, @mmax = @main_file.read(100).unpack('x24Nx4VE8')
228
- @main_length *= 2
229
- index_length_ = @index_file.read(100).unpack('x24Nx72').first
230
- @num_records = (index_length_ - 50) / 4
231
- @cur_record_index = 0
232
-
233
- if @num_records == 0
234
- @xmin = @xmax = @ymin = @ymax = @zmin = @zmax = @mmin = @mmax = nil
235
- else
236
- case @shape_type_code
237
- when 11, 13, 15, 18, 31
238
- if @mmin < NODATA_LIMIT || @mmax < NODATA_LIMIT
239
- @mmin = @mmax = nil
240
- end
241
- if @zmin < NODATA_LIMIT || @zmax < NODATA_LIMIT
242
- @zmin = @zmax = nil
243
- end
244
- when 21, 23, 25, 28
245
- @zmin = @zmax = nil
246
- else
247
- @mmin = @mmax = @zmin = @zmax = nil
248
- end
249
- end
250
-
251
- factory_generator_ = opts_[:factory_generator]
252
- if factory_generator_
253
- factory_config_ = {}
254
- factory_config_[:srid] = opts_[:srid] if opts_[:srid]
255
- unless @zmin.nil?
256
- factory_config_[:has_z_coordinate] = true
257
- end
258
- unless @mmin.nil?
259
- factory_config_[:has_m_coordinate] = true
260
- end
261
- @factory = factory_generator_.call(factory_config_)
262
- else
263
- @factory = opts_[:default_factory] || Cartesian.preferred_factory
264
- end
265
- @factory_supports_z = @factory.property(:has_z_coordinate)
266
- @factory_supports_m = @factory.property(:has_m_coordinate)
267
-
268
- @assume_inner_follows_outer = opts_[:assume_inner_follows_outer]
269
- end
270
-
271
-
272
- # Close the shapefile.
273
- # You should not use this Reader after it has been closed.
274
- # Most methods will return nil.
275
-
276
- def close
277
- if @opened
278
- @main_file.close
279
- @index_file.close
280
- @attr_dbf.close if @attr_dbf
281
- @opened = false
282
- end
283
- end
284
-
285
-
286
- # Returns true if this Reader is still open, or false if it has
287
- # been closed.
288
-
289
- def open?
290
- @opened
291
- end
292
-
293
-
294
- # Returns true if attributes are available. This may be false
295
- # because there is no ".dbf" file or because the dbf gem is not
296
- # available.
297
-
298
- def attributes_available?
299
- @opened ? (@attr_dbf ? true : false) : nil
300
- end
301
-
302
-
303
- # Returns the factory used by this reader.
304
-
305
- def factory
306
- @opened ? @factory : nil
307
- end
308
-
309
-
310
- # Returns the number of records in the shapefile.
311
-
312
- def num_records
313
- @opened ? @num_records : nil
314
- end
315
- alias_method :size, :num_records
316
-
317
-
318
- # Returns the shape type code.
319
-
320
- def shape_type_code
321
- @shape_type_code
322
- end
323
-
324
-
325
- # Returns the minimum x.
326
-
327
- def xmin
328
- @opened ? @xmin : nil
329
- end
330
-
331
-
332
- # Returns the maximum x.
333
-
334
- def xmax
335
- @opened ? @xmax : nil
336
- end
337
-
338
-
339
- # Returns the minimum y.
340
-
341
- def ymin
342
- @opened ? @ymin : nil
343
- end
344
-
345
-
346
- # Returns the maximum y.
347
-
348
- def ymax
349
- @opened ? @ymax : nil
350
- end
351
-
352
-
353
- # Returns the minimum z, or nil if the shapefile does not contain z.
354
-
355
- def zmin
356
- @opened ? @zmin : nil
357
- end
358
-
359
-
360
- # Returns the maximum z, or nil if the shapefile does not contain z.
361
-
362
- def zmax
363
- @opened ? @zmax : nil
364
- end
365
-
366
-
367
- # Returns the minimum m, or nil if the shapefile does not contain m.
368
-
369
- def mmin
370
- @opened ? @mmin : nil
371
- end
372
-
373
-
374
- # Returns the maximum m, or nil if the shapefile does not contain m.
375
-
376
- def mmax
377
- @opened ? @mmax : nil
378
- end
379
-
380
-
381
- # Returns the current file pointer as a record index (0-based).
382
- # This is the record number that will be read when Reader#next
383
- # is called.
384
-
385
- def cur_index
386
- @opened ? @cur_record_index : nil
387
- end
388
-
389
-
390
- # Read and return the next record as a Reader::Record.
391
-
392
- def next
393
- @cur_record_index < @num_records ? _read_next_record : nil
394
- end
395
-
396
-
397
- # Read the remaining records starting with the current record index,
398
- # and yield the Reader::Record for each one.
399
-
400
- def each
401
- while @cur_record_index < @num_records
402
- yield _read_next_record
403
- end
404
- end
405
-
406
-
407
- # Seek to the given record index.
408
-
409
- def seek_index(index_)
410
- if index_ >= 0 && index_ <= @num_records
411
- if index_ < @num_records && index_ != @cur_record_index
412
- @index_file.seek(100+8*index_)
413
- offset_ = @index_file.read(4).unpack('N').first
414
- @main_file.seek(offset_*2)
415
- end
416
- @cur_record_index = index_
417
- true
418
- else
419
- false
420
- end
421
- end
422
-
423
-
424
- # Rewind to the beginning of the file.
425
- # Equivalent to seek_index(0).
426
-
427
- def rewind
428
- seek_index(0)
429
- end
430
-
431
-
432
- # Get the given record number. Equivalent to seeking to that index
433
- # and calling next.
434
-
435
- def get(index_)
436
- seek_index(index_) ? self.next : nil
437
- end
438
- alias_method :[], :get
439
-
440
-
441
- def _read_next_record # :nodoc:
442
- num_, length_ = @main_file.read(8).unpack('NN')
443
- data_ = @main_file.read(length_ * 2)
444
- shape_type_ = data_[0,4].unpack('V').first
445
- geometry_ =
446
- case shape_type_
447
- when 1 then _read_point(data_)
448
- when 3 then _read_polyline(data_)
449
- when 5 then _read_polygon(data_)
450
- when 8 then _read_multipoint(data_)
451
- when 11 then _read_point(data_, :z)
452
- when 13 then _read_polyline(data_, :z)
453
- when 15 then _read_polygon(data_, :z)
454
- when 18 then _read_multipoint(data_, :z)
455
- when 21 then _read_point(data_, :m)
456
- when 23 then _read_polyline(data_, :m)
457
- when 25 then _read_polygon(data_, :m)
458
- when 28 then _read_multipoint(data_, :m)
459
- when 31 then _read_multipatch(data_)
460
- else nil
461
- end
462
- dbf_record_ = @attr_dbf ? @attr_dbf.record(@cur_record_index) : nil
463
- attrs_ = {}
464
- attrs_.merge!(dbf_record_.attributes) if dbf_record_
465
- result_ = Record.new(@cur_record_index, geometry_, attrs_)
466
- @cur_record_index += 1
467
- result_
468
- end
469
-
470
-
471
- def _read_point(data_, opt_=nil) # :nodoc:
472
- case opt_
473
- when :z
474
- x_, y_, z_, m_ = data_[4,32].unpack('EEEE')
475
- m_ = 0 if m_.nil? || m_ < NODATA_LIMIT
476
- when :m
477
- x_, y_, m_ = data_[4,24].unpack('EEE')
478
- z_ = 0
479
- else
480
- x_, y_ = data_[4,16].unpack('EE')
481
- z_ = m_ = 0
482
- end
483
- extras_ = []
484
- extras_ << z_ if @factory_supports_z
485
- extras_ << m_ if @factory_supports_m
486
- @factory.point(x_, y_, *extras_)
487
- end
488
-
489
-
490
- def _read_multipoint(data_, opt_=nil) # :nodoc:
491
- # Read number of points
492
- num_points_ = data_[36,4].unpack('V').first
493
-
494
- # Read remaining data
495
- size_ = num_points_*16
496
- size_ += 16 + num_points_*8 if opt_
497
- size_ += 16 + num_points_*8 if opt_ == :z
498
- values_ = data_[40, size_].unpack('E*')
499
-
500
- # Extract XY, Z, and M values
501
- xys_ = values_.slice!(0, num_points_*2)
502
- ms_ = nil
503
- zs_ = nil
504
- if opt_
505
- ms_ = values_.slice!(2, num_points_)
506
- if opt_ == :z
507
- zs_ = ms_
508
- ms_ = values_.slice!(4, num_points_)
509
- ms_.map!{ |val_| val_ < NODATA_LIMIT ? 0 : val_ } if ms_
510
- end
511
- end
512
-
513
- # Generate points
514
- points_ = (0..num_points_-1).map do |i_|
515
- extras_ = []
516
- extras_ << zs_[i_] if zs_ && @factory_supports_z
517
- extras_ << ms_[i_] if ms_ && @factory_supports_m
518
- @factory.point(xys_[i_*2], xys_[i_*2+1], *extras_)
519
- end
520
-
521
- # Return a MultiPoint
522
- @factory.multi_point(points_)
523
- end
524
-
525
-
526
- def _read_polyline(data_, opt_=nil) # :nodoc:
527
- # Read counts
528
- num_parts_, num_points_ = data_[36,8].unpack('VV')
529
-
530
- # Read remaining data
531
- size_ = num_parts_*4 + num_points_*16
532
- size_ += 16 + num_points_*8 if opt_
533
- size_ += 16 + num_points_*8 if opt_ == :z
534
- values_ = data_[44, size_].unpack("V#{num_parts_}E*")
535
-
536
- # Parts array
537
- part_indexes_ = values_.slice!(0, num_parts_) + [num_points_]
538
-
539
- # Extract XY, Z, and M values
540
- xys_ = values_.slice!(0, num_points_*2)
541
- ms_ = nil
542
- zs_ = nil
543
- if opt_
544
- ms_ = values_.slice!(2, num_points_)
545
- if opt_ == :z
546
- zs_ = ms_
547
- ms_ = values_.slice!(4, num_points_)
548
- ms_.map!{ |val_| val_ < NODATA_LIMIT ? 0 : val_ }
549
- end
550
- end
551
-
552
- # Generate points
553
- points_ = (0..num_points_-1).map do |i_|
554
- extras_ = []
555
- extras_ << zs_[i_] if zs_ && @factory_supports_z
556
- extras_ << ms_[i_] if ms_ && @factory_supports_m
557
- @factory.point(xys_[i_*2], xys_[i_*2+1], *extras_)
558
- end
559
-
560
- # Generate LineString objects (parts)
561
- parts_ = (0..num_parts_-1).map do |i_|
562
- @factory.line_string(points_[part_indexes_[i_]...part_indexes_[i_+1]])
563
- end
564
-
565
- # Generate MultiLineString
566
- @factory.multi_line_string(parts_)
567
- end
568
-
569
-
570
- def _read_polygon(data_, opt_=nil) # :nodoc:
571
- # Read counts
572
- num_parts_, num_points_ = data_[36,8].unpack('VV')
573
-
574
- # Read remaining data
575
- size_ = num_parts_*4 + num_points_*16
576
- size_ += 16 + num_points_*8 if opt_
577
- size_ += 16 + num_points_*8 if opt_ == :z
578
- values_ = data_[44, size_].unpack("V#{num_parts_}E*")
579
-
580
- # Parts array
581
- part_indexes_ = values_.slice!(0, num_parts_) + [num_points_]
582
-
583
- # Extract XY, Z, and M values
584
- xys_ = values_.slice!(0, num_points_*2)
585
- ms_ = nil
586
- zs_ = nil
587
- if opt_
588
- ms_ = values_.slice!(2, num_points_)
589
- if opt_ == :z
590
- zs_ = ms_
591
- ms_ = values_.slice!(4, num_points_)
592
- ms_.map!{ |val_| val_ < NODATA_LIMIT ? 0 : val_ } if ms_
593
- end
594
- end
595
-
596
- # Generate points
597
- points_ = (0..num_points_-1).map do |i_|
598
- extras_ = []
599
- extras_ << zs_[i_] if zs_ && @factory_supports_z
600
- extras_ << ms_[i_] if ms_ && @factory_supports_m
601
- @factory.point(xys_[i_*2], xys_[i_*2+1], *extras_)
602
- end
603
-
604
- # The parts are LinearRing objects
605
- parts_ = (0..num_parts_-1).map do |i_|
606
- @factory.linear_ring(points_[part_indexes_[i_]...part_indexes_[i_+1]])
607
- end
608
-
609
- # Get a GEOS factory if needed.
610
- geos_factory_ = nil
611
- unless @assume_inner_follows_outer
612
- geos_factory_ = Geos.factory
613
- unless geos_factory_
614
- raise Error::RGeoError, "GEOS is not available, but is required for correct interpretation of polygons in shapefiles."
615
- end
616
- end
617
-
618
- # Special case: if there's only one part, treat it as an outer
619
- # ring, regardless of its direction. This isn't strictly compliant
620
- # with the shapefile spec, but the shapelib test cases seem to
621
- # include this case, so we'll relax the assertions here.
622
- if parts_.size == 1
623
- return @factory.multi_polygon([@factory.polygon(parts_[0])])
624
- end
625
-
626
- # Collect some data on the rings: the ring direction, a GEOS
627
- # polygon (for intersection calculation), and an initial guess
628
- # of which polygon index the ring belongs to.
629
- parts_.map! do |ring_|
630
- [ring_, Cartesian::Analysis.ring_direction(ring_) < 0, geos_factory_ ? geos_factory_.polygon(ring_) : nil, nil]
631
- end
632
-
633
- # Initial population of the polygon data array.
634
- # Each element is an array of the part data for the rings, first
635
- # the outer ring and then the inner rings.
636
- # Here we populate the outer rings, and we do an initial
637
- # assignment of rings to polygon index. The initial guess is that
638
- # inner rings always follow their outer ring.
639
- polygons_ = []
640
- parts_.each do |part_data_|
641
- if part_data_[1]
642
- polygons_ << [part_data_]
643
- elsif @assume_inner_follows_outer && polygons_.size > 0
644
- polygons_.last << part_data_
645
- end
646
- part_data_[3] = polygons_.size - 1
647
- end
648
-
649
- # If :assume_inner_follows_outer is in effect, we assume this
650
- # initial guess is the correct one, and we don't run the
651
- # potentially expensive intersection tests.
652
- unless @assume_inner_follows_outer
653
- case polygons_.size
654
- when 0
655
- # Skip this algorithm if there's no outer
656
- when 1
657
- # Shortcut if there's only one outer. Assume all the inners
658
- # are members of this one polygon.
659
- parts_.each do |part_data_|
660
- unless part_data_[1]
661
- polygons_[0] << part_data_
662
- end
663
- end
664
- else
665
- # Go through the remaining (inner) rings, and assign them to
666
- # the correct polygon. For each inner ring, we find the outer
667
- # ring containing it, and add it to that polygon's data. We
668
- # check the initial guess first, and if it fails we go through
669
- # the remaining polygons in order.
670
- parts_.each do |part_data_|
671
- unless part_data_[1]
672
- # This will hold the polygon index for this inner ring.
673
- parent_index_ = nil
674
- # The initial guess. It could be -1 if this inner ring
675
- # appeared before any outer rings had appeared.
676
- first_try_ = part_data_[3]
677
- if first_try_ >= 0 && part_data_[2].within?(polygons_[first_try_].first[2])
678
- parent_index_ = first_try_
679
- end
680
- # If the initial guess didn't work, go through the
681
- # remaining polygons and check their outer rings.
682
- unless parent_index_
683
- polygons_.each_with_index do |poly_data_, index_|
684
- if index_ != first_try_ && part_data_[2].within?(poly_data_.first[2])
685
- parent_index_ = index_
686
- break
687
- end
688
- end
689
- end
690
- # If we found a match, append this inner ring to that
691
- # polygon data. Otherwise, just throw away the inner ring.
692
- if parent_index_
693
- polygons_[parent_index_] << part_data_
694
- end
695
- end
696
- end
697
- end
698
- end
699
-
700
- # Generate the actual polygons from the collected polygon data
701
- polygons_.map! do |poly_data_|
702
- outer_ = poly_data_[0][0]
703
- inner_ = poly_data_[1..-1].map{ |part_data_| part_data_[0] }
704
- @factory.polygon(outer_, inner_)
705
- end
706
-
707
- # Finally, return the MultiPolygon.
708
- @factory.multi_polygon(polygons_)
709
- end
710
-
711
-
712
- def _read_multipatch(data_) # :nodoc:
713
- # Read counts
714
- num_parts_, num_points_ = data_[36,8].unpack('VV')
715
-
716
- # Read remaining data
717
- values_ = data_[44, 32 + num_parts_*8 + num_points_*32].unpack("V#{num_parts_*2}E*")
718
-
719
- # Parts arrays
720
- part_indexes_ = values_.slice!(0, num_parts_) + [num_points_]
721
- part_types_ = values_.slice!(0, num_parts_)
722
-
723
- # Extract XY, Z, and M values
724
- xys_ = values_.slice!(0, num_points_*2)
725
- zs_ = values_.slice!(2, num_points_)
726
- zs_.map!{ |val_| val_ < NODATA_LIMIT ? 0 : val_ } if zs_
727
- ms_ = values_.slice!(4, num_points_)
728
- ms_.map!{ |val_| val_ < NODATA_LIMIT ? 0 : val_ } if ms_
729
-
730
- # Generate points
731
- points_ = (0..num_points_-1).map do |i_|
732
- extras_ = []
733
- extras_ << zs_[i_] if zs_ && @factory_supports_z
734
- extras_ << ms_[i_] if ms_ && @factory_supports_m
735
- @factory.point(xys_[i_*2], xys_[i_*2+1], *extras_)
736
- end
737
-
738
- # Create the parts
739
- parts_ = (0..num_parts_-1).map do |i_|
740
- ps_ = points_[part_indexes_[i_]...part_indexes_[i_+1]]
741
- # All part types just translate directly into rings, except for
742
- # triangle fan, which requires that we reorder the vertices.
743
- if part_types_[i_] == 0
744
- ps2_ = []
745
- i2_ = 0
746
- while i2_ < ps_.size
747
- ps2_ << ps_[i2_]
748
- i2_ += 2
749
- end
750
- i2_ -= 1
751
- i2_ -= 2 if i2_ >= ps_.size
752
- while i2_ > 0
753
- ps2_ << ps_[i2_]
754
- i2_ -= 2
755
- end
756
- ps_ = ps2_
757
- end
758
- @factory.linear_ring(ps_)
759
- end
760
-
761
- # Get a GEOS factory if needed.
762
- geos_factory_ = nil
763
- unless @assume_inner_follows_outer
764
- geos_factory_ = Geos.factory
765
- unless geos_factory_
766
- raise Error::RGeoError, "GEOS is not available, but is required for correct interpretation of polygons in shapefiles."
767
- end
768
- end
769
-
770
- # Walk the parts and generate polygons
771
- polygons_ = []
772
- state_ = :empty
773
- sequence_ = []
774
- # We deliberately include num_parts_ so there's an extra iteration
775
- # with a null part_ and type_. This is so the state handling block
776
- # can finish up any currently live sequence.
777
- (0..num_parts_).each do |index_|
778
- part_ = parts_[index_]
779
- type_ = part_types_[index_]
780
-
781
- # This section handles any state.
782
- # It either stays in the state and goes to the next part,
783
- # or it wraps up the state. Either way, at the end of this
784
- # case block, the state must be :empty.
785
- case state_
786
- when :outer
787
- if type_ == 3
788
- # Inner ring in an outer-led sequence.
789
- # Just add it to the sequence and continue.
790
- sequence_ << part_
791
- next
792
- else
793
- # End of an outer-led sequence.
794
- # Add the polygon and reset the state.
795
- polygons_ << @factory.polygon(sequence_[0], sequence_[1..-1])
796
- state_ = :empty
797
- sequence_ = []
798
- end
799
- when :first
800
- if type_ == 5
801
- # Unknown ring in a first-led sequence.
802
- # Just add it to the sequence and continue.
803
- sequence_ << part_
804
- else
805
- # End of a first-led sequence.
806
- # Need to determine which is the outer ring before we can
807
- # add the polygon.
808
- # If :assume_inner_follows_outer is in effect, we assume
809
- # the first ring is the outer one. Otherwise, we have to
810
- # use GEOS to determine containment.
811
- unless @assume_inner_follows_outer
812
- geos_polygons_ = sequence_.map{ |ring_| geos_factory_.polygon(ring_) }
813
- outer_poly_ = nil
814
- outer_index_ = 0
815
- geos_polygons_.each_with_index do |poly_, index_|
816
- if outer_poly_
817
- if poly_.contains?(outer_poly_)
818
- outer_poly_ = poly_
819
- outer_index_ = index_
820
- break;
821
- end
822
- else
823
- outer_poly_ = poly_
824
- end
825
- end
826
- sequence_.slice!(outer_index_)
827
- sequence_.unshift(outer_poly_)
828
- end
829
- polygons_ << @factory.polygon(sequence_[0], sequence_[1..-1])
830
- state_ = :empty
831
- sequence_ = []
832
- end
833
- end
834
-
835
- # State is now :empty. We allow any type except 3 (since an
836
- # (inner must come during an outer-led sequence).
837
- # We treat a type 5 ring that isn't part of a first-led sequence
838
- # as an outer ring.
839
- case type_
840
- when 0, 1
841
- polygons_ << @factory.polygon(part_)
842
- when 2, 5
843
- sequence_ << part_
844
- state_ = :outer
845
- when 4
846
- sequence_ << part_
847
- state_ = :first
848
- end
849
- end
850
-
851
- # Return the geometry as a collection.
852
- @factory.collection(polygons_)
853
- end
854
-
855
-
856
- # Shapefile records are provided to the caller as objects of this
857
- # type. The record includes the record index (0-based), the
858
- # geometry (which may be nil if the shape type is the null type),
859
- # and a hash of attributes from the associated dbf file.
860
- #
861
- # You should not need to create objects of this type yourself.
862
-
863
- class Record
864
-
865
- def initialize(index_, geometry_, attributes_) # :nodoc:
866
- @index = index_
867
- @geometry = geometry_
868
- @attributes = attributes_
869
- end
870
-
871
- # The 0-based record number
872
- attr_reader :index
873
-
874
- # The geometry contained in this shapefile record
875
- attr_reader :geometry
876
-
877
- # The attributes as a hash.
878
- attr_reader :attributes
879
-
880
- # Returns an array of keys for all this record's attributes.
881
- def keys
882
- @attributes.keys
883
- end
884
-
885
- # Returns the value for the given attribute key.
886
- def [](key_)
887
- @attributes[key_]
888
- end
889
-
890
- end
891
-
892
-
893
- end
894
-
895
-
896
- end
897
-
898
- end