rgeo-shapefile 0.2.2 → 0.2.3
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 +7 -0
- data/README.rdoc +3 -5
- data/Version +1 -1
- data/lib/rgeo-shapefile.rb +36 -0
- data/lib/rgeo/shapefile.rb +13 -13
- data/lib/rgeo/shapefile/reader.rb +164 -163
- data/lib/rgeo/shapefile/version.rb +12 -12
- data/test/shapelib_testcases/readme.txt +1 -1
- data/test/tc_basic.rb +13 -13
- data/test/tc_shapelib_tests.rb +56 -55
- metadata +40 -43
data/History.rdoc
CHANGED
@@ -1,3 +1,10 @@
|
|
1
|
+
=== 0.2.3 / 2012-01-09
|
2
|
+
|
3
|
+
* The dbf gem is now a hard dependency (now that it no longer pulls in ActiveSupport).
|
4
|
+
* Reader#initialize no longer tries to modify its parameters in place. (Reported by Brad Johnson.)
|
5
|
+
* DBF column reading should be more robust. (Possible fix for GH-4, reported by michael-groble.)
|
6
|
+
* Added an "rgeo-shapefile.rb" wrapper so bundler's auto-require will work without modification. (Reported by Mauricio Pasquier Juan.)
|
7
|
+
|
1
8
|
=== 0.2.2 / 2011-04-11
|
2
9
|
|
3
10
|
* A .gemspec file is now available for gem building and bundler git integration.
|
data/README.rdoc
CHANGED
@@ -38,10 +38,8 @@ Example:
|
|
38
38
|
RGeo::Shapefile has the following requirements:
|
39
39
|
|
40
40
|
* Ruby 1.8.7 or later. Ruby 1.9.2 or later preferred.
|
41
|
-
*
|
42
|
-
*
|
43
|
-
needed to read the attributes file. If it is not present, shapefiles
|
44
|
-
can still be read, but attributes will not be available.
|
41
|
+
* rgeo 0.3.3 or later.
|
42
|
+
* dbf 1.7 or later.
|
45
43
|
|
46
44
|
Install RGeo::Shapefile as a gem:
|
47
45
|
|
@@ -79,7 +77,7 @@ ESRI shapefiles, we did borrow a bunch of their test cases.
|
|
79
77
|
|
80
78
|
=== License
|
81
79
|
|
82
|
-
Copyright 2010-
|
80
|
+
Copyright 2010-2012 Daniel Azuma
|
83
81
|
|
84
82
|
All rights reserved.
|
85
83
|
|
data/Version
CHANGED
@@ -1 +1 @@
|
|
1
|
-
0.2.
|
1
|
+
0.2.3
|
@@ -0,0 +1,36 @@
|
|
1
|
+
# -----------------------------------------------------------------------------
|
2
|
+
#
|
3
|
+
# Shapefile processing for RGeo
|
4
|
+
#
|
5
|
+
# -----------------------------------------------------------------------------
|
6
|
+
# Copyright 2010-2012 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
|
+
require 'rgeo/shapefile'
|
data/lib/rgeo/shapefile.rb
CHANGED
@@ -1,15 +1,15 @@
|
|
1
1
|
# -----------------------------------------------------------------------------
|
2
|
-
#
|
2
|
+
#
|
3
3
|
# Shapefile processing for RGeo
|
4
|
-
#
|
4
|
+
#
|
5
5
|
# -----------------------------------------------------------------------------
|
6
|
-
# Copyright 2010 Daniel Azuma
|
7
|
-
#
|
6
|
+
# Copyright 2010-2012 Daniel Azuma
|
7
|
+
#
|
8
8
|
# All rights reserved.
|
9
|
-
#
|
9
|
+
#
|
10
10
|
# Redistribution and use in source and binary forms, with or without
|
11
11
|
# modification, are permitted provided that the following conditions are met:
|
12
|
-
#
|
12
|
+
#
|
13
13
|
# * Redistributions of source code must retain the above copyright notice,
|
14
14
|
# this list of conditions and the following disclaimer.
|
15
15
|
# * Redistributions in binary form must reproduce the above copyright notice,
|
@@ -18,7 +18,7 @@
|
|
18
18
|
# * Neither the name of the copyright holder, nor the names of any other
|
19
19
|
# contributors to this software, may be used to endorse or promote products
|
20
20
|
# derived from this software without specific prior written permission.
|
21
|
-
#
|
21
|
+
#
|
22
22
|
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
23
23
|
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
24
24
|
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
@@ -39,22 +39,22 @@ require 'rgeo'
|
|
39
39
|
|
40
40
|
|
41
41
|
# RGeo is a spatial data library for Ruby, provided by the "rgeo" gem.
|
42
|
-
#
|
42
|
+
#
|
43
43
|
# The optional RGeo::Shapefile module provides a set of tools for reading
|
44
44
|
# ESRI shapefiles.
|
45
45
|
|
46
46
|
module RGeo
|
47
|
-
|
48
|
-
|
47
|
+
|
48
|
+
|
49
49
|
# This module contains an implementation of ESRI Shapefiles.
|
50
50
|
# Use the Shapefile::Reader class to read a shapefile, extracting
|
51
51
|
# geometry and attribute data from it.
|
52
52
|
# RGeo does not yet have support for writing shapefiles.
|
53
|
-
|
53
|
+
|
54
54
|
module Shapefile
|
55
55
|
end
|
56
|
-
|
57
|
-
|
56
|
+
|
57
|
+
|
58
58
|
end
|
59
59
|
|
60
60
|
|
@@ -1,15 +1,15 @@
|
|
1
1
|
# -----------------------------------------------------------------------------
|
2
|
-
#
|
2
|
+
#
|
3
3
|
# Shapefile reader for RGeo
|
4
|
-
#
|
4
|
+
#
|
5
5
|
# -----------------------------------------------------------------------------
|
6
|
-
# Copyright 2010 Daniel Azuma
|
7
|
-
#
|
6
|
+
# Copyright 2010-2012 Daniel Azuma
|
7
|
+
#
|
8
8
|
# All rights reserved.
|
9
|
-
#
|
9
|
+
#
|
10
10
|
# Redistribution and use in source and binary forms, with or without
|
11
11
|
# modification, are permitted provided that the following conditions are met:
|
12
|
-
#
|
12
|
+
#
|
13
13
|
# * Redistributions of source code must retain the above copyright notice,
|
14
14
|
# this list of conditions and the following disclaimer.
|
15
15
|
# * Redistributions in binary form must reproduce the above copyright notice,
|
@@ -18,7 +18,7 @@
|
|
18
18
|
# * Neither the name of the copyright holder, nor the names of any other
|
19
19
|
# contributors to this software, may be used to endorse or promote products
|
20
20
|
# derived from this software without specific prior written permission.
|
21
|
-
#
|
21
|
+
#
|
22
22
|
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
23
23
|
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
24
24
|
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
@@ -41,28 +41,28 @@ end
|
|
41
41
|
|
42
42
|
|
43
43
|
module RGeo
|
44
|
-
|
44
|
+
|
45
45
|
module Shapefile
|
46
|
-
|
47
|
-
|
46
|
+
|
47
|
+
|
48
48
|
# Represents a shapefile that is open for reading.
|
49
|
-
#
|
49
|
+
#
|
50
50
|
# You can use this object to read a shapefile straight through,
|
51
51
|
# yielding the data in a block; or you can perform random access
|
52
52
|
# reads of indexed records.
|
53
|
-
#
|
53
|
+
#
|
54
54
|
# You must close this object after you are done, in order to close
|
55
55
|
# the underlying files. Alternatively, you can pass a block to
|
56
56
|
# Reader::open, and the reader will be closed automatically for
|
57
57
|
# you at the end of the block.
|
58
|
-
#
|
58
|
+
#
|
59
59
|
# === Dependencies
|
60
|
-
#
|
60
|
+
#
|
61
61
|
# Attributes in shapefiles are stored in a ".dbf" (dBASE) format
|
62
62
|
# file. The "dbf" gem is required to read these files. If this
|
63
63
|
# gem is not installed, shapefile reading will still function,
|
64
64
|
# but attributes will not be available.
|
65
|
-
#
|
65
|
+
#
|
66
66
|
# Correct interpretation of the polygon shape type requires some
|
67
67
|
# functionality that is available in the RGeo::Geos module. Hence,
|
68
68
|
# reading a polygon shapefile will generally fail if that module is
|
@@ -70,13 +70,13 @@ module RGeo
|
|
70
70
|
# to bypass this requirement by relaxing the polygon tests and making
|
71
71
|
# some assumptions about the file format. See the documentation for
|
72
72
|
# Reader::open for details.
|
73
|
-
#
|
73
|
+
#
|
74
74
|
# === Shapefile support
|
75
|
-
#
|
75
|
+
#
|
76
76
|
# This class supports shapefiles formatted according to the 1998
|
77
77
|
# "ESRI Shapefile Technical Description". It converts shapefile
|
78
78
|
# data to RGeo geometry objects, as follows:
|
79
|
-
#
|
79
|
+
#
|
80
80
|
# * Shapefile records are represented by the
|
81
81
|
# RGeo::Shapefile::Reader::Record class, which provides the
|
82
82
|
# geometry, the attributes, and the record number (0-based).
|
@@ -96,9 +96,9 @@ module RGeo
|
|
96
96
|
# * The multipatch shape type yields GeometryCollection geometries.
|
97
97
|
# (See below for an explanation of why we do not return a
|
98
98
|
# MultiPolygon.)
|
99
|
-
#
|
99
|
+
#
|
100
100
|
# Some special notes and limitations in our shapefile support:
|
101
|
-
#
|
101
|
+
#
|
102
102
|
# * Our implementation assumes that shapefile data is in a Cartesian
|
103
103
|
# coordinate system when it performs certain computations, such as
|
104
104
|
# directionality of polygon rings. It also ignores the 180 degree
|
@@ -131,35 +131,35 @@ module RGeo
|
|
131
131
|
# assertions, which do not allow constituent polygons to share a
|
132
132
|
# common boundary. Therefore, when reading a multipatch, we return
|
133
133
|
# a GeometryCollection instead of a MultiPolygon.
|
134
|
-
|
134
|
+
|
135
135
|
class Reader
|
136
|
-
|
137
|
-
|
136
|
+
|
137
|
+
|
138
138
|
# Values less than this value are considered "no value" in the
|
139
139
|
# shapefile format specification.
|
140
140
|
NODATA_LIMIT = -1e38
|
141
|
-
|
142
|
-
|
141
|
+
|
142
|
+
|
143
143
|
# Create a new shapefile reader. You must pass the path for the
|
144
144
|
# main shapefile (e.g. "path/to/file.shp"). You may also omit the
|
145
145
|
# ".shp" extension from the path. All three files that make up the
|
146
|
-
# shapefile (".shp", ".
|
146
|
+
# shapefile (".shp", ".shx", and ".dbf") must be present for
|
147
147
|
# successful opening of a shapefile.
|
148
|
-
#
|
148
|
+
#
|
149
149
|
# You must also provide a RGeo::Feature::FactoryGenerator. It should
|
150
150
|
# understand the configuration options <tt>:has_z_coordinate</tt>
|
151
151
|
# and <tt>:has_m_coordinate</tt>. You may also pass a specific
|
152
152
|
# RGeo::Feature::Factory, or nil to specify the default Cartesian
|
153
153
|
# FactoryGenerator.
|
154
|
-
#
|
154
|
+
#
|
155
155
|
# If you provide a block, the shapefile reader will be yielded to
|
156
156
|
# the block, and automatically closed at the end of the block.
|
157
157
|
# If you do not provide a block, the shapefile reader will be
|
158
158
|
# returned from this call. It is then the caller's responsibility
|
159
159
|
# to close the reader when it is done.
|
160
|
-
#
|
160
|
+
#
|
161
161
|
# Options include:
|
162
|
-
#
|
162
|
+
#
|
163
163
|
# [<tt>:factory_generator</tt>]
|
164
164
|
# A RGeo::Feature::FactoryGenerator that should return a factory
|
165
165
|
# based on the dimension settings in the input. It should
|
@@ -174,9 +174,9 @@ module RGeo
|
|
174
174
|
# [<tt>:assume_inner_follows_outer</tt>]
|
175
175
|
# If set to true, some assumptions are made about ring ordering
|
176
176
|
# in a polygon shapefile. See below for details. Default is false.
|
177
|
-
#
|
177
|
+
#
|
178
178
|
# === Ring ordering in polygon shapefiles
|
179
|
-
#
|
179
|
+
#
|
180
180
|
# The ESRI polygon shape type specifies that the ordering of rings
|
181
181
|
# in the shapefile is not significant. That is, rings can be in any
|
182
182
|
# order, and inner rings need not necessarily follow the outer ring
|
@@ -185,7 +185,7 @@ module RGeo
|
|
185
185
|
# it becomes necessary to run some geometric analysis on the rings
|
186
186
|
# that are read in, in order to determine which inner rings should
|
187
187
|
# go with which outer rings.
|
188
|
-
#
|
188
|
+
#
|
189
189
|
# RGeo's shapefile reader uses GEOS to perform this analysis.
|
190
190
|
# However, this means that if GEOS is not available, the analysis
|
191
191
|
# will fail. It also means reading polygons may be slow, especially
|
@@ -199,7 +199,7 @@ module RGeo
|
|
199
199
|
# is not turned on by default. However, if you are running RGeo on
|
200
200
|
# a platform without GEOS, you have no choice but to turn on this
|
201
201
|
# switch and make this assumption about your input shapefiles.
|
202
|
-
|
202
|
+
|
203
203
|
def self.open(path_, opts_={}, &block_)
|
204
204
|
file_ = new(path_, opts_)
|
205
205
|
if block_
|
@@ -213,14 +213,14 @@ module RGeo
|
|
213
213
|
file_
|
214
214
|
end
|
215
215
|
end
|
216
|
-
|
217
|
-
|
216
|
+
|
217
|
+
|
218
218
|
# Low-level creation of a Reader. The arguments are the same as
|
219
219
|
# those passed to Reader::open, except that this doesn't take a
|
220
220
|
# block. You should use Reader::open instead.
|
221
|
-
|
221
|
+
|
222
222
|
def initialize(path_, opts_={}) # :nodoc:
|
223
|
-
path_.sub
|
223
|
+
path_ = path_.sub(/\.shp$/, '')
|
224
224
|
@base_path = path_
|
225
225
|
@opened = true
|
226
226
|
@main_file = ::File.open(path_+'.shp', 'rb:ascii-8bit')
|
@@ -235,7 +235,7 @@ module RGeo
|
|
235
235
|
index_length_ = @index_file.read(100).unpack('x24Nx72').first
|
236
236
|
@num_records = (index_length_ - 50) / 4
|
237
237
|
@cur_record_index = 0
|
238
|
-
|
238
|
+
|
239
239
|
if @num_records == 0
|
240
240
|
@xmin = @xmax = @ymin = @ymax = @zmin = @zmax = @mmin = @mmax = nil
|
241
241
|
else
|
@@ -253,7 +253,7 @@ module RGeo
|
|
253
253
|
@mmin = @mmax = @zmin = @zmax = nil
|
254
254
|
end
|
255
255
|
end
|
256
|
-
|
256
|
+
|
257
257
|
@factory = opts_[:factory_generator] || opts_[:factory] || Cartesian.method(:preferred_factory)
|
258
258
|
unless @factory.kind_of?(Feature::Factory::Instance)
|
259
259
|
factory_config_ = {}
|
@@ -268,15 +268,15 @@ module RGeo
|
|
268
268
|
end
|
269
269
|
@factory_supports_z = @factory.property(:has_z_coordinate)
|
270
270
|
@factory_supports_m = @factory.property(:has_m_coordinate)
|
271
|
-
|
271
|
+
|
272
272
|
@assume_inner_follows_outer = opts_[:assume_inner_follows_outer]
|
273
273
|
end
|
274
|
-
|
275
|
-
|
274
|
+
|
275
|
+
|
276
276
|
# Close the shapefile.
|
277
277
|
# You should not use this Reader after it has been closed.
|
278
278
|
# Most methods will return nil.
|
279
|
-
|
279
|
+
|
280
280
|
def close
|
281
281
|
if @opened
|
282
282
|
@main_file.close
|
@@ -285,131 +285,131 @@ module RGeo
|
|
285
285
|
@opened = false
|
286
286
|
end
|
287
287
|
end
|
288
|
-
|
289
|
-
|
288
|
+
|
289
|
+
|
290
290
|
# Returns true if this Reader is still open, or false if it has
|
291
291
|
# been closed.
|
292
|
-
|
292
|
+
|
293
293
|
def open?
|
294
294
|
@opened
|
295
295
|
end
|
296
|
-
|
297
|
-
|
296
|
+
|
297
|
+
|
298
298
|
# Returns true if attributes are available. This may be false
|
299
299
|
# because there is no ".dbf" file or because the dbf gem is not
|
300
300
|
# available.
|
301
|
-
|
301
|
+
|
302
302
|
def attributes_available?
|
303
303
|
@opened ? (@attr_dbf ? true : false) : nil
|
304
304
|
end
|
305
|
-
|
306
|
-
|
305
|
+
|
306
|
+
|
307
307
|
# Returns the factory used by this reader.
|
308
|
-
|
308
|
+
|
309
309
|
def factory
|
310
310
|
@opened ? @factory : nil
|
311
311
|
end
|
312
|
-
|
313
|
-
|
312
|
+
|
313
|
+
|
314
314
|
# Returns the number of records in the shapefile.
|
315
|
-
|
315
|
+
|
316
316
|
def num_records
|
317
317
|
@opened ? @num_records : nil
|
318
318
|
end
|
319
319
|
alias_method :size, :num_records
|
320
|
-
|
321
|
-
|
320
|
+
|
321
|
+
|
322
322
|
# Returns the shape type code.
|
323
|
-
|
323
|
+
|
324
324
|
def shape_type_code
|
325
325
|
@opened ? @shape_type_code : nil
|
326
326
|
end
|
327
|
-
|
328
|
-
|
327
|
+
|
328
|
+
|
329
329
|
# Returns the minimum x.
|
330
|
-
|
330
|
+
|
331
331
|
def xmin
|
332
332
|
@opened ? @xmin : nil
|
333
333
|
end
|
334
|
-
|
335
|
-
|
334
|
+
|
335
|
+
|
336
336
|
# Returns the maximum x.
|
337
|
-
|
337
|
+
|
338
338
|
def xmax
|
339
339
|
@opened ? @xmax : nil
|
340
340
|
end
|
341
|
-
|
342
|
-
|
341
|
+
|
342
|
+
|
343
343
|
# Returns the minimum y.
|
344
|
-
|
344
|
+
|
345
345
|
def ymin
|
346
346
|
@opened ? @ymin : nil
|
347
347
|
end
|
348
|
-
|
349
|
-
|
348
|
+
|
349
|
+
|
350
350
|
# Returns the maximum y.
|
351
|
-
|
351
|
+
|
352
352
|
def ymax
|
353
353
|
@opened ? @ymax : nil
|
354
354
|
end
|
355
|
-
|
356
|
-
|
355
|
+
|
356
|
+
|
357
357
|
# Returns the minimum z, or nil if the shapefile does not contain z.
|
358
|
-
|
358
|
+
|
359
359
|
def zmin
|
360
360
|
@opened ? @zmin : nil
|
361
361
|
end
|
362
|
-
|
363
|
-
|
362
|
+
|
363
|
+
|
364
364
|
# Returns the maximum z, or nil if the shapefile does not contain z.
|
365
|
-
|
365
|
+
|
366
366
|
def zmax
|
367
367
|
@opened ? @zmax : nil
|
368
368
|
end
|
369
|
-
|
370
|
-
|
369
|
+
|
370
|
+
|
371
371
|
# Returns the minimum m, or nil if the shapefile does not contain m.
|
372
|
-
|
372
|
+
|
373
373
|
def mmin
|
374
374
|
@opened ? @mmin : nil
|
375
375
|
end
|
376
|
-
|
377
|
-
|
376
|
+
|
377
|
+
|
378
378
|
# Returns the maximum m, or nil if the shapefile does not contain m.
|
379
|
-
|
379
|
+
|
380
380
|
def mmax
|
381
381
|
@opened ? @mmax : nil
|
382
382
|
end
|
383
|
-
|
384
|
-
|
383
|
+
|
384
|
+
|
385
385
|
# Returns the current file pointer as a record index (0-based).
|
386
386
|
# This is the record number that will be read when Reader#next
|
387
387
|
# is called.
|
388
|
-
|
388
|
+
|
389
389
|
def cur_index
|
390
390
|
@opened ? @cur_record_index : nil
|
391
391
|
end
|
392
|
-
|
393
|
-
|
392
|
+
|
393
|
+
|
394
394
|
# Read and return the next record as a Reader::Record.
|
395
|
-
|
395
|
+
|
396
396
|
def next
|
397
397
|
@opened && @cur_record_index < @num_records ? _read_next_record : nil
|
398
398
|
end
|
399
|
-
|
400
|
-
|
399
|
+
|
400
|
+
|
401
401
|
# Read the remaining records starting with the current record index,
|
402
402
|
# and yield the Reader::Record for each one.
|
403
|
-
|
403
|
+
|
404
404
|
def each
|
405
405
|
while @cur_record_index < @num_records
|
406
406
|
yield _read_next_record
|
407
407
|
end if @opened
|
408
408
|
end
|
409
|
-
|
410
|
-
|
409
|
+
|
410
|
+
|
411
411
|
# Seek to the given record index.
|
412
|
-
|
412
|
+
|
413
413
|
def seek_index(index_)
|
414
414
|
if @opened && index_ >= 0 && index_ <= @num_records
|
415
415
|
if index_ < @num_records && index_ != @cur_record_index
|
@@ -423,25 +423,25 @@ module RGeo
|
|
423
423
|
false
|
424
424
|
end
|
425
425
|
end
|
426
|
-
|
427
|
-
|
426
|
+
|
427
|
+
|
428
428
|
# Rewind to the beginning of the file.
|
429
429
|
# Equivalent to seek_index(0).
|
430
|
-
|
430
|
+
|
431
431
|
def rewind
|
432
432
|
seek_index(0)
|
433
433
|
end
|
434
|
-
|
435
|
-
|
434
|
+
|
435
|
+
|
436
436
|
# Get the given record number. Equivalent to seeking to that index
|
437
437
|
# and calling next.
|
438
|
-
|
438
|
+
|
439
439
|
def get(index_)
|
440
440
|
seek_index(index_) ? self.next : nil
|
441
441
|
end
|
442
442
|
alias_method :[], :get
|
443
|
-
|
444
|
-
|
443
|
+
|
444
|
+
|
445
445
|
def _read_next_record # :nodoc:
|
446
446
|
num_, length_ = @main_file.read(8).unpack('NN')
|
447
447
|
data_ = @main_file.read(length_ * 2)
|
@@ -465,17 +465,18 @@ module RGeo
|
|
465
465
|
end
|
466
466
|
attrs_ = {}
|
467
467
|
if @attr_dbf
|
468
|
-
|
468
|
+
dbf_record_attrs_ = @attr_dbf.record(@cur_record_index).attributes
|
469
469
|
@attr_dbf.columns.each do |col_|
|
470
|
-
|
470
|
+
name_ = col_.name
|
471
|
+
attrs_[name_] = dbf_record_attrs_[name_]
|
471
472
|
end
|
472
473
|
end
|
473
474
|
result_ = Record.new(@cur_record_index, geometry_, attrs_)
|
474
475
|
@cur_record_index += 1
|
475
476
|
result_
|
476
477
|
end
|
477
|
-
|
478
|
-
|
478
|
+
|
479
|
+
|
479
480
|
def _read_point(data_, opt_=nil) # :nodoc:
|
480
481
|
case opt_
|
481
482
|
when :z
|
@@ -493,18 +494,18 @@ module RGeo
|
|
493
494
|
extras_ << m_ if @factory_supports_m
|
494
495
|
@factory.point(x_, y_, *extras_)
|
495
496
|
end
|
496
|
-
|
497
|
-
|
497
|
+
|
498
|
+
|
498
499
|
def _read_multipoint(data_, opt_=nil) # :nodoc:
|
499
500
|
# Read number of points
|
500
501
|
num_points_ = data_[36,4].unpack('V').first
|
501
|
-
|
502
|
+
|
502
503
|
# Read remaining data
|
503
504
|
size_ = num_points_*16
|
504
505
|
size_ += 16 + num_points_*8 if opt_
|
505
506
|
size_ += 16 + num_points_*8 if opt_ == :z
|
506
507
|
values_ = data_[40, size_].unpack('E*')
|
507
|
-
|
508
|
+
|
508
509
|
# Extract XY, Z, and M values
|
509
510
|
xys_ = values_.slice!(0, num_points_*2)
|
510
511
|
ms_ = nil
|
@@ -517,7 +518,7 @@ module RGeo
|
|
517
518
|
ms_.map!{ |val_| val_ < NODATA_LIMIT ? 0 : val_ } if ms_
|
518
519
|
end
|
519
520
|
end
|
520
|
-
|
521
|
+
|
521
522
|
# Generate points
|
522
523
|
points_ = (0..num_points_-1).map do |i_|
|
523
524
|
extras_ = []
|
@@ -529,21 +530,21 @@ module RGeo
|
|
529
530
|
# Return a MultiPoint
|
530
531
|
@factory.multi_point(points_)
|
531
532
|
end
|
532
|
-
|
533
|
-
|
533
|
+
|
534
|
+
|
534
535
|
def _read_polyline(data_, opt_=nil) # :nodoc:
|
535
536
|
# Read counts
|
536
537
|
num_parts_, num_points_ = data_[36,8].unpack('VV')
|
537
|
-
|
538
|
+
|
538
539
|
# Read remaining data
|
539
540
|
size_ = num_parts_*4 + num_points_*16
|
540
541
|
size_ += 16 + num_points_*8 if opt_
|
541
542
|
size_ += 16 + num_points_*8 if opt_ == :z
|
542
543
|
values_ = data_[44, size_].unpack("V#{num_parts_}E*")
|
543
|
-
|
544
|
+
|
544
545
|
# Parts array
|
545
546
|
part_indexes_ = values_.slice!(0, num_parts_) + [num_points_]
|
546
|
-
|
547
|
+
|
547
548
|
# Extract XY, Z, and M values
|
548
549
|
xys_ = values_.slice!(0, num_points_*2)
|
549
550
|
ms_ = nil
|
@@ -556,7 +557,7 @@ module RGeo
|
|
556
557
|
ms_.map!{ |val_| val_ < NODATA_LIMIT ? 0 : val_ }
|
557
558
|
end
|
558
559
|
end
|
559
|
-
|
560
|
+
|
560
561
|
# Generate points
|
561
562
|
points_ = (0..num_points_-1).map do |i_|
|
562
563
|
extras_ = []
|
@@ -564,30 +565,30 @@ module RGeo
|
|
564
565
|
extras_ << ms_[i_] if ms_ && @factory_supports_m
|
565
566
|
@factory.point(xys_[i_*2], xys_[i_*2+1], *extras_)
|
566
567
|
end
|
567
|
-
|
568
|
+
|
568
569
|
# Generate LineString objects (parts)
|
569
570
|
parts_ = (0..num_parts_-1).map do |i_|
|
570
571
|
@factory.line_string(points_[part_indexes_[i_]...part_indexes_[i_+1]])
|
571
572
|
end
|
572
|
-
|
573
|
+
|
573
574
|
# Generate MultiLineString
|
574
575
|
@factory.multi_line_string(parts_)
|
575
576
|
end
|
576
|
-
|
577
|
-
|
577
|
+
|
578
|
+
|
578
579
|
def _read_polygon(data_, opt_=nil) # :nodoc:
|
579
580
|
# Read counts
|
580
581
|
num_parts_, num_points_ = data_[36,8].unpack('VV')
|
581
|
-
|
582
|
+
|
582
583
|
# Read remaining data
|
583
584
|
size_ = num_parts_*4 + num_points_*16
|
584
585
|
size_ += 16 + num_points_*8 if opt_
|
585
586
|
size_ += 16 + num_points_*8 if opt_ == :z
|
586
587
|
values_ = data_[44, size_].unpack("V#{num_parts_}E*")
|
587
|
-
|
588
|
+
|
588
589
|
# Parts array
|
589
590
|
part_indexes_ = values_.slice!(0, num_parts_) + [num_points_]
|
590
|
-
|
591
|
+
|
591
592
|
# Extract XY, Z, and M values
|
592
593
|
xys_ = values_.slice!(0, num_points_*2)
|
593
594
|
ms_ = nil
|
@@ -600,7 +601,7 @@ module RGeo
|
|
600
601
|
ms_.map!{ |val_| val_ < NODATA_LIMIT ? 0 : val_ } if ms_
|
601
602
|
end
|
602
603
|
end
|
603
|
-
|
604
|
+
|
604
605
|
# Generate points
|
605
606
|
points_ = (0..num_points_-1).map do |i_|
|
606
607
|
extras_ = []
|
@@ -608,12 +609,12 @@ module RGeo
|
|
608
609
|
extras_ << ms_[i_] if ms_ && @factory_supports_m
|
609
610
|
@factory.point(xys_[i_*2], xys_[i_*2+1], *extras_)
|
610
611
|
end
|
611
|
-
|
612
|
+
|
612
613
|
# The parts are LinearRing objects
|
613
614
|
parts_ = (0..num_parts_-1).map do |i_|
|
614
615
|
@factory.linear_ring(points_[part_indexes_[i_]...part_indexes_[i_+1]])
|
615
616
|
end
|
616
|
-
|
617
|
+
|
617
618
|
# Get a GEOS factory if needed.
|
618
619
|
geos_factory_ = nil
|
619
620
|
unless @assume_inner_follows_outer
|
@@ -622,7 +623,7 @@ module RGeo
|
|
622
623
|
raise Error::RGeoError, "GEOS is not available, but is required for correct interpretation of polygons in shapefiles."
|
623
624
|
end
|
624
625
|
end
|
625
|
-
|
626
|
+
|
626
627
|
# Special case: if there's only one part, treat it as an outer
|
627
628
|
# ring, regardless of its direction. This isn't strictly compliant
|
628
629
|
# with the shapefile spec, but the shapelib test cases seem to
|
@@ -630,14 +631,14 @@ module RGeo
|
|
630
631
|
if parts_.size == 1
|
631
632
|
return @factory.multi_polygon([@factory.polygon(parts_[0])])
|
632
633
|
end
|
633
|
-
|
634
|
+
|
634
635
|
# Collect some data on the rings: the ring direction, a GEOS
|
635
636
|
# polygon (for intersection calculation), and an initial guess
|
636
637
|
# of which polygon index the ring belongs to.
|
637
638
|
parts_.map! do |ring_|
|
638
639
|
[ring_, Cartesian::Analysis.ring_direction(ring_) < 0, geos_factory_ ? geos_factory_.polygon(ring_) : nil, nil]
|
639
640
|
end
|
640
|
-
|
641
|
+
|
641
642
|
# Initial population of the polygon data array.
|
642
643
|
# Each element is an array of the part data for the rings, first
|
643
644
|
# the outer ring and then the inner rings.
|
@@ -653,7 +654,7 @@ module RGeo
|
|
653
654
|
end
|
654
655
|
part_data_[3] = polygons_.size - 1
|
655
656
|
end
|
656
|
-
|
657
|
+
|
657
658
|
# If :assume_inner_follows_outer is in effect, we assume this
|
658
659
|
# initial guess is the correct one, and we don't run the
|
659
660
|
# potentially expensive intersection tests.
|
@@ -704,37 +705,37 @@ module RGeo
|
|
704
705
|
end
|
705
706
|
end
|
706
707
|
end
|
707
|
-
|
708
|
+
|
708
709
|
# Generate the actual polygons from the collected polygon data
|
709
710
|
polygons_.map! do |poly_data_|
|
710
711
|
outer_ = poly_data_[0][0]
|
711
712
|
inner_ = poly_data_[1..-1].map{ |part_data_| part_data_[0] }
|
712
713
|
@factory.polygon(outer_, inner_)
|
713
714
|
end
|
714
|
-
|
715
|
+
|
715
716
|
# Finally, return the MultiPolygon.
|
716
717
|
@factory.multi_polygon(polygons_)
|
717
718
|
end
|
718
|
-
|
719
|
-
|
719
|
+
|
720
|
+
|
720
721
|
def _read_multipatch(data_) # :nodoc:
|
721
722
|
# Read counts
|
722
723
|
num_parts_, num_points_ = data_[36,8].unpack('VV')
|
723
|
-
|
724
|
+
|
724
725
|
# Read remaining data
|
725
726
|
values_ = data_[44, 32 + num_parts_*8 + num_points_*32].unpack("V#{num_parts_*2}E*")
|
726
|
-
|
727
|
+
|
727
728
|
# Parts arrays
|
728
729
|
part_indexes_ = values_.slice!(0, num_parts_) + [num_points_]
|
729
730
|
part_types_ = values_.slice!(0, num_parts_)
|
730
|
-
|
731
|
+
|
731
732
|
# Extract XY, Z, and M values
|
732
733
|
xys_ = values_.slice!(0, num_points_*2)
|
733
734
|
zs_ = values_.slice!(2, num_points_)
|
734
735
|
zs_.map!{ |val_| val_ < NODATA_LIMIT ? 0 : val_ } if zs_
|
735
736
|
ms_ = values_.slice!(4, num_points_)
|
736
737
|
ms_.map!{ |val_| val_ < NODATA_LIMIT ? 0 : val_ } if ms_
|
737
|
-
|
738
|
+
|
738
739
|
# Generate points
|
739
740
|
points_ = (0..num_points_-1).map do |i_|
|
740
741
|
extras_ = []
|
@@ -742,7 +743,7 @@ module RGeo
|
|
742
743
|
extras_ << ms_[i_] if ms_ && @factory_supports_m
|
743
744
|
@factory.point(xys_[i_*2], xys_[i_*2+1], *extras_)
|
744
745
|
end
|
745
|
-
|
746
|
+
|
746
747
|
# Create the parts
|
747
748
|
parts_ = (0..num_parts_-1).map do |i_|
|
748
749
|
ps_ = points_[part_indexes_[i_]...part_indexes_[i_+1]]
|
@@ -765,7 +766,7 @@ module RGeo
|
|
765
766
|
end
|
766
767
|
@factory.linear_ring(ps_)
|
767
768
|
end
|
768
|
-
|
769
|
+
|
769
770
|
# Get a GEOS factory if needed.
|
770
771
|
geos_factory_ = nil
|
771
772
|
unless @assume_inner_follows_outer
|
@@ -774,7 +775,7 @@ module RGeo
|
|
774
775
|
raise Error::RGeoError, "GEOS is not available, but is required for correct interpretation of polygons in shapefiles."
|
775
776
|
end
|
776
777
|
end
|
777
|
-
|
778
|
+
|
778
779
|
# Walk the parts and generate polygons
|
779
780
|
polygons_ = []
|
780
781
|
state_ = :empty
|
@@ -785,7 +786,7 @@ module RGeo
|
|
785
786
|
(0..num_parts_).each do |index_|
|
786
787
|
part_ = parts_[index_]
|
787
788
|
type_ = part_types_[index_]
|
788
|
-
|
789
|
+
|
789
790
|
# This section handles any state.
|
790
791
|
# It either stays in the state and goes to the next part,
|
791
792
|
# or it wraps up the state. Either way, at the end of this
|
@@ -839,7 +840,7 @@ module RGeo
|
|
839
840
|
sequence_ = []
|
840
841
|
end
|
841
842
|
end
|
842
|
-
|
843
|
+
|
843
844
|
# State is now :empty. We allow any type except 3 (since an
|
844
845
|
# (inner must come during an outer-led sequence).
|
845
846
|
# We treat a type 5 ring that isn't part of a first-led sequence
|
@@ -855,52 +856,52 @@ module RGeo
|
|
855
856
|
state_ = :first
|
856
857
|
end
|
857
858
|
end
|
858
|
-
|
859
|
+
|
859
860
|
# Return the geometry as a collection.
|
860
861
|
@factory.collection(polygons_)
|
861
862
|
end
|
862
|
-
|
863
|
-
|
863
|
+
|
864
|
+
|
864
865
|
# Shapefile records are provided to the caller as objects of this
|
865
866
|
# type. The record includes the record index (0-based), the
|
866
867
|
# geometry (which may be nil if the shape type is the null type),
|
867
868
|
# and a hash of attributes from the associated dbf file.
|
868
|
-
#
|
869
|
+
#
|
869
870
|
# You should not need to create objects of this type yourself.
|
870
|
-
|
871
|
+
|
871
872
|
class Record
|
872
|
-
|
873
|
+
|
873
874
|
def initialize(index_, geometry_, attributes_) # :nodoc:
|
874
875
|
@index = index_
|
875
876
|
@geometry = geometry_
|
876
877
|
@attributes = attributes_
|
877
878
|
end
|
878
|
-
|
879
|
+
|
879
880
|
# The 0-based record number
|
880
881
|
attr_reader :index
|
881
|
-
|
882
|
+
|
882
883
|
# The geometry contained in this shapefile record
|
883
884
|
attr_reader :geometry
|
884
|
-
|
885
|
+
|
885
886
|
# The attributes as a hash.
|
886
887
|
attr_reader :attributes
|
887
|
-
|
888
|
+
|
888
889
|
# Returns an array of keys for all this record's attributes.
|
889
890
|
def keys
|
890
891
|
@attributes.keys
|
891
892
|
end
|
892
|
-
|
893
|
+
|
893
894
|
# Returns the value for the given attribute key.
|
894
895
|
def [](key_)
|
895
|
-
@attributes[key_]
|
896
|
+
@attributes[key_.to_s]
|
896
897
|
end
|
897
|
-
|
898
|
+
|
898
899
|
end
|
899
|
-
|
900
|
-
|
900
|
+
|
901
|
+
|
901
902
|
end
|
902
|
-
|
903
|
-
|
903
|
+
|
904
|
+
|
904
905
|
end
|
905
|
-
|
906
|
+
|
906
907
|
end
|