georuby_remake 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (70) hide show
  1. data/.gitignore +6 -0
  2. data/History.txt +4 -0
  3. data/LICENSE +21 -0
  4. data/README.txt +118 -0
  5. data/Rakefile +47 -0
  6. data/VERSION +1 -0
  7. data/georuby_remake.gemspec +132 -0
  8. data/lib/geo_ruby.rb +5 -0
  9. data/lib/geo_ruby/gpx4r.rb +9 -0
  10. data/lib/geo_ruby/gpx4r/gpx.rb +104 -0
  11. data/lib/geo_ruby/shp4r.rb +57 -0
  12. data/lib/geo_ruby/shp4r/dbf.rb +38 -0
  13. data/lib/geo_ruby/shp4r/shp_file.rb +297 -0
  14. data/lib/geo_ruby/shp4r/shp_record.rb +14 -0
  15. data/lib/geo_ruby/shp4r/shp_transaction.rb +345 -0
  16. data/lib/geo_ruby/simple_features.rb +20 -0
  17. data/lib/geo_ruby/simple_features/envelope.rb +167 -0
  18. data/lib/geo_ruby/simple_features/ewkb_parser.rb +216 -0
  19. data/lib/geo_ruby/simple_features/ewkt_parser.rb +336 -0
  20. data/lib/geo_ruby/simple_features/geometry.rb +230 -0
  21. data/lib/geo_ruby/simple_features/geometry_collection.rb +136 -0
  22. data/lib/geo_ruby/simple_features/geometry_factory.rb +81 -0
  23. data/lib/geo_ruby/simple_features/georss_parser.rb +135 -0
  24. data/lib/geo_ruby/simple_features/helper.rb +18 -0
  25. data/lib/geo_ruby/simple_features/line_string.rb +206 -0
  26. data/lib/geo_ruby/simple_features/linear_ring.rb +12 -0
  27. data/lib/geo_ruby/simple_features/multi_line_string.rb +51 -0
  28. data/lib/geo_ruby/simple_features/multi_point.rb +46 -0
  29. data/lib/geo_ruby/simple_features/multi_polygon.rb +52 -0
  30. data/lib/geo_ruby/simple_features/point.rb +364 -0
  31. data/lib/geo_ruby/simple_features/polygon.rb +157 -0
  32. data/spec/data/gpx/fells_loop.gpx +1077 -0
  33. data/spec/data/gpx/long.gpx +1642 -0
  34. data/spec/data/gpx/long.kml +31590 -0
  35. data/spec/data/gpx/long.nmea +2220 -0
  36. data/spec/data/gpx/short.gpx +13634 -0
  37. data/spec/data/gpx/short.kml +130 -0
  38. data/spec/data/gpx/tracktreks.gpx +706 -0
  39. data/spec/data/multipoint.dbf +0 -0
  40. data/spec/data/multipoint.shp +0 -0
  41. data/spec/data/multipoint.shx +0 -0
  42. data/spec/data/point.dbf +0 -0
  43. data/spec/data/point.shp +0 -0
  44. data/spec/data/point.shx +0 -0
  45. data/spec/data/polygon.dbf +0 -0
  46. data/spec/data/polygon.shp +0 -0
  47. data/spec/data/polygon.shx +0 -0
  48. data/spec/data/polyline.dbf +0 -0
  49. data/spec/data/polyline.shp +0 -0
  50. data/spec/data/polyline.shx +0 -0
  51. data/spec/geo_ruby/gpx4r/gpx_spec.rb +106 -0
  52. data/spec/geo_ruby/shp4r/shp_spec.rb +240 -0
  53. data/spec/geo_ruby/simple_features/envelope_spec.rb +45 -0
  54. data/spec/geo_ruby/simple_features/ewkb_parser_spec.rb +158 -0
  55. data/spec/geo_ruby/simple_features/ewkt_parser_spec.rb +179 -0
  56. data/spec/geo_ruby/simple_features/geometry_collection_spec.rb +55 -0
  57. data/spec/geo_ruby/simple_features/geometry_factory_spec.rb +11 -0
  58. data/spec/geo_ruby/simple_features/geometry_spec.rb +32 -0
  59. data/spec/geo_ruby/simple_features/georss_parser_spec.rb +218 -0
  60. data/spec/geo_ruby/simple_features/line_string_spec.rb +245 -0
  61. data/spec/geo_ruby/simple_features/linear_ring_spec.rb +14 -0
  62. data/spec/geo_ruby/simple_features/multi_line_string_spec.rb +54 -0
  63. data/spec/geo_ruby/simple_features/multi_point_spec.rb +35 -0
  64. data/spec/geo_ruby/simple_features/multi_polygon_spec.rb +50 -0
  65. data/spec/geo_ruby/simple_features/point_spec.rb +356 -0
  66. data/spec/geo_ruby/simple_features/polygon_spec.rb +108 -0
  67. data/spec/geo_ruby_spec.rb +27 -0
  68. data/spec/spec.opts +6 -0
  69. data/spec/spec_helper.rb +65 -0
  70. metadata +185 -0
@@ -0,0 +1,57 @@
1
+ require 'date'
2
+ require 'fileutils' if !defined?(FileUtils)
3
+
4
+ module GeoRuby::Shp4r
5
+ autoload :Dbf, 'geo_ruby/shp4r/dbf'
6
+ autoload :ShpFile, 'geo_ruby/shp4r/shp_file'
7
+ autoload :ShpRecord, 'geo_ruby/shp4r/shp_record'
8
+ autoload :ShpTransaction, 'geo_ruby/shp4r/shp_transaction'
9
+
10
+ #Enumerates all the types of SHP geometries. The MULTIPATCH one is the only one not currently supported by Geo_ruby.
11
+ module ShpType
12
+ NULL_SHAPE = 0
13
+ POINT = 1
14
+ POLYLINE = 3
15
+ POLYGON = 5
16
+ MULTIPOINT = 8
17
+ POINTZ = 11
18
+ POLYLINEZ = 13
19
+ POLYGONZ = 15
20
+ MULTIPOINTZ = 18
21
+ POINTM = 21
22
+ POLYLINEM = 23
23
+ POLYGONM = 25
24
+ MULTIPOINTM = 28
25
+ end
26
+
27
+ class MalformedShpException < StandardError
28
+ end
29
+
30
+ class UnexistantRecordException < StandardError
31
+ end
32
+
33
+ class IncompatibleGeometryException < StandardError
34
+ end
35
+
36
+ class IncompatibleDataException < StandardError
37
+ end
38
+
39
+ =begin
40
+ GeoRuby::Shp4r
41
+ GeoRuby::Shp4r::Dbf
42
+ GeoRuby::Shp4r::Dbf::DBFError
43
+ GeoRuby::Shp4r::Dbf::DbfRecord
44
+ GeoRuby::Shp4r::Dbf::Field
45
+ GeoRuby::Shp4r::Dbf::FieldError
46
+ GeoRuby::Shp4r::Dbf::Reader
47
+ GeoRuby::Shp4r::Dbf::UnpackError
48
+ GeoRuby::Shp4r::IncompatibleDataException
49
+ GeoRuby::Shp4r::IncompatibleGeometryException
50
+ GeoRuby::Shp4r::MalformedShpException
51
+ GeoRuby::Shp4r::ShpFile
52
+ GeoRuby::Shp4r::ShpRecord
53
+ GeoRuby::Shp4r::ShpTransaction
54
+ GeoRuby::Shp4r::ShpType
55
+ GeoRuby::Shp4r::UnexistantRecordException
56
+ =end
57
+ end
@@ -0,0 +1,38 @@
1
+ # Uses the dbf lib, Copyright 2006 Keith Morrison (http://infused.org)
2
+ # Modified to work as external gem now
3
+ require 'rubygems'
4
+ begin
5
+ require 'dbf'
6
+ rescue LoadError
7
+ puts "Unable to find gem 'dbf'. Please install."
8
+ end
9
+
10
+ GeoRuby::Shp4r::Dbf = DBF
11
+ module GeoRuby::Shp4r::Dbf
12
+
13
+ class Record
14
+ def [](v)
15
+ attributes[v.downcase]
16
+ end
17
+ end
18
+
19
+ class Field < Column
20
+ def initialize(name, type, length, decimal = 0)
21
+ super(name, type, length, decimal)
22
+ end
23
+ end
24
+
25
+ class Reader < Table
26
+ alias_method :fields, :columns
27
+ def header_length
28
+ @columns_count
29
+ end
30
+
31
+ def self.open(f)
32
+ new(f)
33
+ end
34
+
35
+ def close(); nil; end
36
+ end
37
+
38
+ end
@@ -0,0 +1,297 @@
1
+ #An interface to an ESRI shapefile (actually 3 files : shp, shx and dbf). Currently supports only the reading of geometries.
2
+ class GeoRuby::Shp4r::ShpFile
3
+ attr_reader :shp_type, :record_count, :xmin, :ymin, :xmax, :ymax, :zmin, :zmax, :mmin, :mmax, :file_root, :file_length
4
+
5
+ include Enumerable
6
+
7
+ #Opens a SHP file. Both "abc.shp" and "abc" are accepted. The files "abc.shp", "abc.shx" and "abc.dbf" must be present
8
+ def initialize(file)
9
+ #strip the shp out of the file if present
10
+ @file_root = file.gsub(/.shp$/i,"")
11
+ #check existence of shp, dbf and shx files
12
+ unless File.exists?(@file_root + ".shp") and File.exists?(@file_root + ".dbf") and File.exists?(@file_root + ".shx")
13
+ raise MalformedShpException.new("Missing one of shp, dbf or shx for: #{@file}")
14
+ end
15
+
16
+ @dbf = ::GeoRuby::Shp4r::Dbf::Reader.open(@file_root + ".dbf")
17
+ @shx = File.open(@file_root + ".shx","rb")
18
+ @shp = File.open(@file_root + ".shp","rb")
19
+ read_index
20
+ end
21
+
22
+ #force the reopening of the files compsing the shp. Close before calling this.
23
+ def reload!
24
+ initialize(@file_root)
25
+ end
26
+
27
+ #opens a SHP "file". If a block is given, the ShpFile object is yielded to it and is closed upon return. Else a call to <tt>open</tt> is equivalent to <tt>ShpFile.new(...)</tt>.
28
+ def self.open(file)
29
+ shpfile = ShpFile.new(file)
30
+ if block_given?
31
+ yield shpfile
32
+ shpfile.close
33
+ else
34
+ shpfile
35
+ end
36
+ end
37
+
38
+ #create a new Shapefile of the specified shp type (see ShpType) and with the attribute specified in the +fields+ array (see Dbf::Field). If a block is given, the ShpFile object newly created is passed to it.
39
+ def self.create(file,shp_type,fields,&proc)
40
+ file_root = file.gsub(/.shp$/i,"")
41
+ shx_io = File.open(file_root + ".shx","wb")
42
+ shp_io = File.open(file_root + ".shp","wb")
43
+ dbf_io = File.open(file_root + ".dbf","wb")
44
+ str = [9994,0,0,0,0,0,50,1000,shp_type,0,0,0,0,0,0,0,0].pack("N7V2E8")
45
+ shp_io << str
46
+ shx_io << str
47
+ rec_length = 1 + fields.inject(0) {|s,f| s + f.length} #+1 for the prefixed space (active record marker)
48
+ dbf_io << [3,107,7,7,0,33 + 32 * fields.length,rec_length ].pack("c4Vv2x20") #32 bytes for first part of header
49
+ fields.each do |field|
50
+ dbf_io << [field.name,field.type,field.length,field.decimal].pack("a10xax4CCx14")
51
+ end
52
+ dbf_io << ['0d'].pack("H2")
53
+
54
+ shx_io.close
55
+ shp_io.close
56
+ dbf_io.close
57
+
58
+ open(file,&proc)
59
+
60
+ end
61
+
62
+ #Closes a shapefile
63
+ def close
64
+ @dbf.close
65
+ @shx.close
66
+ @shp.close
67
+ end
68
+
69
+ #starts a transaction, to buffer physical file operations on the shapefile components.
70
+ def transaction
71
+ trs = ShpTransaction.new(self,@dbf)
72
+ if block_given?
73
+ answer = yield trs
74
+ if answer == :rollback
75
+ trs.rollback
76
+ elsif !trs.rollbacked
77
+ trs.commit
78
+ end
79
+ else
80
+ trs
81
+ end
82
+ end
83
+
84
+ #return the description of data fields
85
+ def fields
86
+ @dbf.fields
87
+ end
88
+
89
+ #Tests if the file has no record
90
+ def empty?
91
+ record_count == 0
92
+ end
93
+
94
+ #Goes through each record
95
+ def each
96
+ (0...record_count).each do |i|
97
+ yield get_record(i)
98
+ end
99
+ end
100
+ alias :each_record :each
101
+
102
+ #Returns record +i+
103
+ def [](i)
104
+ get_record(i)
105
+ end
106
+
107
+ #Returns all the records
108
+ def records
109
+ Array.new(record_count) do |i|
110
+ get_record(i)
111
+ end
112
+ end
113
+
114
+ private
115
+ def read_index
116
+ @file_length, @shp_type, @xmin, @ymin, @xmax, @ymax, @zmin, @zmax, @mmin,@mmax = @shx.read(100).unpack("x24Nx4VE8")
117
+ @record_count = (@file_length - 50) / 4
118
+ if @record_count == 0
119
+ #initialize the bboxes to default values so if data added, they will be replaced
120
+ @xmin, @ymin, @xmax, @ymax, @zmin, @zmax, @mmin,@mmax = Float::MAX, Float::MAX, -Float::MAX, -Float::MAX, Float::MAX, -Float::MAX, Float::MAX, -Float::MAX
121
+ end
122
+ unless @record_count == @dbf.record_count
123
+ raise MalformedShpException.new("Not the same number of records in SHP and DBF")
124
+ end
125
+ end
126
+
127
+ #TODO : refactor to minimize redundant code
128
+ def get_record(i)
129
+ return nil if record_count <= i or i < 0
130
+ dbf_record = @dbf.record(i)
131
+ @shx.seek(100 + 8 * i) #100 is the header length
132
+ offset,length = @shx.read(8).unpack("N2")
133
+ @shp.seek(offset * 2 + 8)
134
+ rec_shp_type = @shp.read(4).unpack("V")[0]
135
+
136
+ case(rec_shp_type)
137
+ when ShpType::POINT
138
+ x, y = @shp.read(16).unpack("E2")
139
+ geometry = GeoRuby::SimpleFeatures::Point.from_x_y(x,y)
140
+ when ShpType::POLYLINE #actually creates a multi_polyline
141
+ @shp.seek(32,IO::SEEK_CUR) #extent
142
+ num_parts, num_points = @shp.read(8).unpack("V2")
143
+ parts = @shp.read(num_parts * 4).unpack("V" + num_parts.to_s)
144
+ parts << num_points #indexes for LS of idx i go to parts of idx i to idx i +1
145
+ points = Array.new(num_points) do
146
+ x, y = @shp.read(16).unpack("E2")
147
+ GeoRuby::SimpleFeatures::Point.from_x_y(x,y)
148
+ end
149
+ line_strings = Array.new(num_parts) do |i|
150
+ GeoRuby::SimpleFeatures::LineString.from_points(points[(parts[i])...(parts[i+1])])
151
+ end
152
+ geometry = GeoRuby::SimpleFeatures::MultiLineString.from_line_strings(line_strings)
153
+ when ShpType::POLYGON
154
+ #TODO : TO CORRECT
155
+ #does not take into account the possibility that the outer loop could be after the inner loops in the SHP + more than one outer loop
156
+ #Still sends back a multi polygon (so the correction above won't change what gets sent back)
157
+ @shp.seek(32,IO::SEEK_CUR)
158
+ num_parts, num_points = @shp.read(8).unpack("V2")
159
+ parts = @shp.read(num_parts * 4).unpack("V" + num_parts.to_s)
160
+ parts << num_points #indexes for LS of idx i go to parts of idx i to idx i +1
161
+ points = Array.new(num_points) do
162
+ x, y = @shp.read(16).unpack("E2")
163
+ GeoRuby::SimpleFeatures::Point.from_x_y(x,y)
164
+ end
165
+ linear_rings = Array.new(num_parts) do |i|
166
+ GeoRuby::SimpleFeatures::LinearRing.from_points(points[(parts[i])...(parts[i+1])])
167
+ end
168
+ geometry = GeoRuby::SimpleFeatures::MultiPolygon.from_polygons([GeoRuby::SimpleFeatures::Polygon.from_linear_rings(linear_rings)])
169
+ when ShpType::MULTIPOINT
170
+ @shp.seek(32,IO::SEEK_CUR)
171
+ num_points = @shp.read(4).unpack("V")[0]
172
+ points = Array.new(num_points) do
173
+ x, y = @shp.read(16).unpack("E2")
174
+ GeoRuby::SimpleFeatures::Point.from_x_y(x,y)
175
+ end
176
+ geometry = GeoRuby::SimpleFeatures::MultiPoint.from_points(points)
177
+
178
+
179
+ when ShpType::POINTZ
180
+ x, y, z, m = @shp.read(24).unpack("E4")
181
+ geometry = GeoRuby::SimpleFeatures::Point.from_x_y_z_m(x,y,z,m)
182
+
183
+
184
+ when ShpType::POLYLINEZ
185
+ @shp.seek(32,IO::SEEK_CUR)
186
+ num_parts, num_points = @shp.read(8).unpack("V2")
187
+ parts = @shp.read(num_parts * 4).unpack("V" + num_parts.to_s)
188
+ parts << num_points #indexes for LS of idx i go to parts of idx i to idx i +1
189
+ xys = Array.new(num_points) { @shp.read(16).unpack("E2") }
190
+ @shp.seek(16,IO::SEEK_CUR)
191
+ zs = Array.new(num_points) {@shp.read(8).unpack("E")[0]}
192
+ @shp.seek(16,IO::SEEK_CUR)
193
+ ms = Array.new(num_points) {@shp.read(8).unpack("E")[0]}
194
+ points = Array.new(num_points) do |i|
195
+ GeoRuby::SimpleFeatures::Point.from_x_y_z_m(xys[i][0],xys[i][1],zs[i],ms[i])
196
+ end
197
+ line_strings = Array.new(num_parts) do |i|
198
+ GeoRuby::SimpleFeatures::LineString.from_points(points[(parts[i])...(parts[i+1])],GeoRuby::SimpleFeatures::default_srid,true,true)
199
+ end
200
+ geometry = GeoRuby::SimpleFeatures::MultiLineString.from_line_strings(line_strings,GeoRuby::SimpleFeatures::default_srid,true,true)
201
+
202
+
203
+ when ShpType::POLYGONZ
204
+ #TODO : CORRECT
205
+
206
+ @shp.seek(32,IO::SEEK_CUR)#extent
207
+ num_parts, num_points = @shp.read(8).unpack("V2")
208
+ parts = @shp.read(num_parts * 4).unpack("V" + num_parts.to_s)
209
+ parts << num_points #indexes for LS of idx i go to parts of idx i to idx i +1
210
+ xys = Array.new(num_points) { @shp.read(16).unpack("E2") }
211
+ @shp.seek(16,IO::SEEK_CUR)#extent
212
+ zs = Array.new(num_points) {@shp.read(8).unpack("E")[0]}
213
+ @shp.seek(16,IO::SEEK_CUR)#extent
214
+ ms = Array.new(num_points) {@shp.read(8).unpack("E")[0]}
215
+ points = Array.new(num_points) do |i|
216
+ Point.from_x_y_z_m(xys[i][0],xys[i][1],zs[i],ms[i])
217
+ end
218
+ linear_rings = Array.new(num_parts) do |i|
219
+ GeoRuby::SimpleFeatures::LinearRing.from_points(points[(parts[i])...(parts[i+1])],GeoRuby::SimpleFeatures::default_srid,true,true)
220
+ end
221
+ geometry = GeoRuby::SimpleFeatures::MultiPolygon.from_polygons([GeoRuby::SimpleFeatures::Polygon.from_linear_rings(linear_rings)],GeoRuby::SimpleFeatures::default_srid,true,true)
222
+
223
+
224
+ when ShpType::MULTIPOINTZ
225
+ @shp.seek(32,IO::SEEK_CUR)
226
+ num_points = @shp.read(4).unpack("V")[0]
227
+ xys = Array.new(num_points) { @shp.read(16).unpack("E2") }
228
+ @shp.seek(16,IO::SEEK_CUR)
229
+ zs = Array.new(num_points) {@shp.read(8).unpack("E")[0]}
230
+ @shp.seek(16,IO::SEEK_CUR)
231
+ ms = Array.new(num_points) {@shp.read(8).unpack("E")[0]}
232
+
233
+ points = Array.new(num_points) do |i|
234
+ Point.from_x_y_z_m(xys[i][0],xys[i][1],zs[i],ms[i])
235
+ end
236
+
237
+ geometry = GeoRuby::SimpleFeatures::MultiPoint.from_points(points,GeoRuby::SimpleFeatures::default_srid,true,true)
238
+
239
+ when ShpType::POINTM
240
+ x, y, m = @shp.read(24).unpack("E3")
241
+ geometry = GeoRuby::SimpleFeatures::Point.from_x_y_m(x,y,m)
242
+
243
+ when ShpType::POLYLINEM
244
+ @shp.seek(32,IO::SEEK_CUR)
245
+ num_parts, num_points = @shp.read(8).unpack("V2")
246
+ parts = @shp.read(num_parts * 4).unpack("V" + num_parts.to_s)
247
+ parts << num_points #indexes for LS of idx i go to parts of idx i to idx i +1
248
+ xys = Array.new(num_points) { @shp.read(16).unpack("E2") }
249
+ @shp.seek(16,IO::SEEK_CUR)
250
+ ms = Array.new(num_points) {@shp.read(8).unpack("E")[0]}
251
+ points = Array.new(num_points) do |i|
252
+ Point.from_x_y_m(xys[i][0],xys[i][1],ms[i])
253
+ end
254
+ line_strings = Array.new(num_parts) do |i|
255
+ GeoRuby::SimpleFeatures::LineString.from_points(points[(parts[i])...(parts[i+1])],GeoRuby::SimpleFeatures::default_srid,false,true)
256
+ end
257
+ geometry = GeoRuby::SimpleFeatures::MultiLineString.from_line_strings(line_strings,GeoRuby::SimpleFeatures::default_srid,false,true)
258
+
259
+
260
+ when ShpType::POLYGONM
261
+ #TODO : CORRECT
262
+
263
+ @shp.seek(32,IO::SEEK_CUR)
264
+ num_parts, num_points = @shp.read(8).unpack("V2")
265
+ parts = @shp.read(num_parts * 4).unpack("V" + num_parts.to_s)
266
+ parts << num_points #indexes for LS of idx i go to parts of idx i to idx i +1
267
+ xys = Array.new(num_points) { @shp.read(16).unpack("E2") }
268
+ @shp.seek(16,IO::SEEK_CUR)
269
+ ms = Array.new(num_points) {@shp.read(8).unpack("E")[0]}
270
+ points = Array.new(num_points) do |i|
271
+ Point.from_x_y_m(xys[i][0],xys[i][1],ms[i])
272
+ end
273
+ linear_rings = Array.new(num_parts) do |i|
274
+ GeoRuby::SimpleFeatures::LinearRing.from_points(points[(parts[i])...(parts[i+1])],GeoRuby::SimpleFeatures::default_srid,false,true)
275
+ end
276
+ geometry = GeoRuby::SimpleFeatures::MultiPolygon.from_polygons([GeoRuby::SimpleFeatures::Polygon.from_linear_rings(linear_rings)],GeoRuby::SimpleFeatures::default_srid,false,true)
277
+
278
+
279
+ when ShpType::MULTIPOINTM
280
+ @shp.seek(32,IO::SEEK_CUR)
281
+ num_points = @shp.read(4).unpack("V")[0]
282
+ xys = Array.new(num_points) { @shp.read(16).unpack("E2") }
283
+ @shp.seek(16,IO::SEEK_CUR)
284
+ ms = Array.new(num_points) {@shp.read(8).unpack("E")[0]}
285
+
286
+ points = Array.new(num_points) do |i|
287
+ Point.from_x_y_m(xys[i][0],xys[i][1],ms[i])
288
+ end
289
+
290
+ geometry = GeoRuby::SimpleFeatures::MultiPoint.from_points(points,GeoRuby::SimpleFeatures::default_srid,false,true)
291
+ else
292
+ geometry = nil
293
+ end
294
+
295
+ ShpRecord.new(geometry,dbf_record)
296
+ end
297
+ end
@@ -0,0 +1,14 @@
1
+ #A SHP record : contains both the geometry and the data fields (from the DBF)
2
+ class GeoRuby::Shp4r::ShpRecord
3
+ attr_reader :geometry , :data
4
+
5
+ def initialize(geometry, data)
6
+ @geometry = geometry
7
+ @data = data
8
+ end
9
+
10
+ #Tests if the geometry is a NULL SHAPE
11
+ def has_null_shape?
12
+ @geometry.nil?
13
+ end
14
+ end
@@ -0,0 +1,345 @@
1
+ #An object returned from ShpFile#transaction. Buffers updates to a Shapefile
2
+ class GeoRuby::Shp4r::ShpTransaction
3
+ attr_reader :rollbacked
4
+
5
+ def initialize(shp, dbf)
6
+ @deleted = Hash.new
7
+ @added = Array.new
8
+ @shp = shp
9
+ @dbf = dbf
10
+ end
11
+
12
+ #delete a record. Does not take into account the records added in the current transaction
13
+ def delete(i)
14
+ raise UnexistantRecordException.new("Invalid index : #{i}") if @shp.record_count <= i
15
+ @deleted[i] = true
16
+ end
17
+
18
+ #Update a record. In effect just a delete followed by an add.
19
+ def update(i, record)
20
+ delete(i)
21
+ add(record)
22
+ end
23
+
24
+ #add a ShpRecord at the end
25
+ def add(record)
26
+ record_type = to_shp_type(record.geometry)
27
+ raise IncompatibleGeometryException.new("Incompatible type") unless record_type==@shp.shp_type
28
+ @added << record
29
+ end
30
+
31
+ #updates the physical files
32
+ def commit
33
+ @shp.close
34
+ @shp_r = open(@shp.file_root + ".shp", "rb")
35
+ @dbf_r = open(@shp.file_root + ".dbf", "rb")
36
+ @shp_io = open(@shp.file_root + ".shp.tmp.shp", "wb")
37
+ @shx_io = open(@shp.file_root + ".shx.tmp.shx", "wb")
38
+ @dbf_io = open(@shp.file_root + ".dbf.tmp.dbf", "wb")
39
+ index = commit_delete
40
+ min_x,max_x,min_y,max_y,min_z,max_z,min_m,max_m = commit_add(index)
41
+ commit_finalize(min_x,max_x,min_y,max_y,min_z,max_z,min_m,max_m)
42
+ @shp_r.close
43
+ @dbf_r.close
44
+ @dbf_io.close
45
+ @shp_io.close
46
+ @shx_io.close
47
+ FileUtils.move(@shp.file_root + ".shp.tmp.shp", @shp.file_root + ".shp")
48
+ FileUtils.move(@shp.file_root + ".shx.tmp.shx", @shp.file_root + ".shx")
49
+ FileUtils.move(@shp.file_root + ".dbf.tmp.dbf", @shp.file_root + ".dbf")
50
+
51
+ @deleted = Hash.new
52
+ @added = Array.new
53
+
54
+ @shp.reload!
55
+ end
56
+
57
+ #prevents the udpate from taking place
58
+ def rollback
59
+ @deleted = Hash.new
60
+ @added = Array.new
61
+ @rollbacked = true
62
+ end
63
+
64
+ private
65
+
66
+ def to_shp_type(geom)
67
+ root = if geom.is_a? GeoRuby::SimpleFeatures::Point
68
+ "POINT"
69
+ elsif geom.is_a? GeoRuby::SimpleFeatures::LineString
70
+ "POLYLINE"
71
+ elsif geom.is_a? GeoRuby::SimpleFeatures::Polygon
72
+ "POLYGON"
73
+ elsif geom.is_a? GeoRuby::SimpleFeatures::MultiPoint
74
+ "MULTIPOINT"
75
+ elsif geom.is_a? GeoRuby::SimpleFeatures::MultiLineString
76
+ "POLYLINE"
77
+ elsif geom.is_a? GeoRuby::SimpleFeatures::MultiPolygon
78
+ "POLYGON"
79
+ else
80
+ false
81
+ end
82
+ return false if !root
83
+
84
+ if geom.with_z
85
+ root = root + "Z"
86
+ elsif geom.with_m
87
+ root = root + "M"
88
+ end
89
+ eval "ShpType::" + root
90
+ end
91
+
92
+ def commit_add(index)
93
+ max_x, min_x, max_y, min_y,max_z,min_z,max_m,min_m = @shp.xmax,@shp.xmin,@shp.ymax,@shp.ymin,@shp.zmax,@shp.zmin,@shp.mmax,@shp.mmin
94
+ @added.each do |record|
95
+ @dbf_io << ['20'].pack('H2')
96
+ @dbf.fields.each do |field|
97
+ data = record.data[field.name]
98
+ str = if field.type == 'D'
99
+ data && sprintf("%04i%02i%02i",data.year,data.month,data.mday)
100
+ elsif field.type == 'L'
101
+ data ? "T" : "F"
102
+ else
103
+ data.to_s
104
+ end
105
+ @dbf_io << [str].pack("A#{field.length}")
106
+ end
107
+
108
+ shp_str,min_xp,max_xp,min_yp,max_yp,min_zp,max_zp,min_mp,max_mp = build_shp_geometry(record.geometry)
109
+ max_x = max_xp if max_xp > max_x
110
+ min_x = min_xp if min_xp < min_x
111
+ max_y = max_yp if max_yp > max_y
112
+ min_y = min_yp if min_yp < min_y
113
+ max_z = max_zp if max_zp > max_z
114
+ min_z = min_zp if min_zp < min_z
115
+ max_m = max_mp if max_mp > max_m
116
+ min_m = min_mp if min_mp < min_m
117
+ length = (shp_str.length/2 + 2).to_i #num of 16-bit words; geom type is included (+2)
118
+ @shx_io << [(@shp_io.pos/2).to_i,length].pack("N2")
119
+ @shp_io << [index,length,@shp.shp_type].pack("N2V")
120
+ @shp_io << shp_str
121
+ index += 1
122
+ end
123
+ @shp_io.flush
124
+ @shx_io.flush
125
+ @dbf_io.flush
126
+ [min_x,max_x,min_y,max_y,min_z,max_z,min_m,max_m]
127
+ end
128
+
129
+ def commit_delete
130
+ @shp_r.rewind
131
+ header = @shp_r.read(100)
132
+ @shp_io << header
133
+ @shx_io << header
134
+ index = 1
135
+ while(!@shp_r.eof?)
136
+ icur,length = @shp_r.read(8).unpack("N2")
137
+ unless(@deleted[icur-1])
138
+ @shx_io << [(@shp_io.pos/2).to_i,length].pack("N2")
139
+ @shp_io << [index,length].pack("N2")
140
+ @shp_io << @shp_r.read(length * 2)
141
+ index += 1
142
+ else
143
+ @shp_r.seek(length * 2,IO::SEEK_CUR)
144
+ end
145
+ end
146
+ @shp_io.flush
147
+ @shx_io.flush
148
+
149
+ @dbf_r.rewind
150
+ @dbf_io << @dbf_r.read(@dbf.header_length)
151
+ icur = 0
152
+ while(!@dbf_r.eof?)
153
+ unless(@deleted[icur])
154
+ @dbf_io << @dbf_r.read(@dbf.record_length)
155
+ else
156
+ @dbf_r.seek(@dbf.record_length,IO::SEEK_CUR)
157
+ end
158
+ icur += 1
159
+ end
160
+ @dbf_io.flush
161
+ index
162
+ end
163
+
164
+ def commit_finalize(min_x,max_x,min_y,max_y,min_z,max_z,min_m,max_m)
165
+ #update size in shp and dbf + extent and num records in dbf
166
+ @shp_io.seek(0,IO::SEEK_END)
167
+ shp_size = @shp_io.pos / 2
168
+ @shx_io.seek(0,IO::SEEK_END)
169
+ shx_size= @shx_io.pos / 2
170
+ @shp_io.seek(24)
171
+ @shp_io.write([shp_size].pack("N"))
172
+ @shx_io.seek(24)
173
+ @shx_io.write([shx_size].pack("N"))
174
+ @shp_io.seek(36)
175
+ @shx_io.seek(36)
176
+ str = [min_x,min_y,max_x,max_y,min_z,max_z,min_m,max_m].pack("E8")
177
+ @shp_io.write(str)
178
+ @shx_io.write(str)
179
+
180
+ @dbf_io.seek(4)
181
+ @dbf_io.write([@dbf.record_count + @added.length - @deleted.length].pack("V"))
182
+ end
183
+
184
+ def build_shp_geometry(geometry)
185
+ m_range = nil
186
+ answer =
187
+ case @shp.shp_type
188
+ when ShpType::POINT
189
+ bbox = geometry.bounding_box
190
+ [geometry.x,geometry.y].pack("E2")
191
+ when ShpType::POLYLINE
192
+ str,bbox = create_bbox(geometry)
193
+ build_polyline(geometry,str)
194
+ when ShpType::POLYGON
195
+ str,bbox = create_bbox(geometry)
196
+ build_polygon(geometry,str)
197
+ when ShpType::MULTIPOINT
198
+ str,bbox = create_bbox(geometry)
199
+ build_multi_point(geometry,str)
200
+ when ShpType::POINTZ
201
+ bbox = geometry.bounding_box
202
+ [geometry.x,geometry.y,geometry.z,geometry.m].pack("E4")
203
+ when ShpType::POLYLINEZ
204
+ str,bbox = create_bbox(geometry)
205
+ m_range = geometry.m_range
206
+ build_polyline(geometry,str)
207
+ build_polyline_zm(geometry,:@z,[bbox[0].z,bbox[1].z],str)
208
+ build_polyline_zm(geometry,:@m,m_range,str)
209
+ when ShpType::POLYGONZ
210
+ str,bbox = create_bbox(geometry)
211
+ m_range = geometry.m_range
212
+ build_polygon(geometry,str)
213
+ build_polygon_zm(geometry,:@z,[bbox[0].z,bbox[1].z],str)
214
+ build_polygon_zm(geometry,:@m,m_range,str)
215
+ when ShpType::MULTIPOINTZ
216
+ str,bbox = create_bbox(geometry)
217
+ m_range = geometry.m_range
218
+ build_multi_point(geometry,str)
219
+ build_multi_point_zm(geometry,:@z,[bbox[0].z,bbox[1].z],str)
220
+ build_multi_point_zm(geometry,:@m,m_range,str)
221
+ when ShpType::POINTM
222
+ bbox = geometry.bounding_box
223
+ [geometry.x,geometry.y,geometry.m].pack("E3")
224
+ when ShpType::POLYLINEM
225
+ str,bbox = create_bbox(geometry)
226
+ m_range = geometry.m_range
227
+ build_polyline(geometry,str)
228
+ build_polyline_zm(geometry,:@m,m_range,str)
229
+ when ShpType::POLYGONM
230
+ str,bbox = create_bbox(geometry)
231
+ m_range = geometry.m_range
232
+ build_polygon(geometry,str)
233
+ build_polygon_zm(geometry,:@m,m_range,str)
234
+ when ShpType::MULTIPOINTM
235
+ str,bbox = create_bbox(geometry)
236
+ m_range = geometry.m_range
237
+ build_multi_point(geometry,str)
238
+ build_multi_point_zm(geometry,:@m,m_range,str)
239
+ end
240
+ m_range ||= [0,0]
241
+ [answer,bbox[0].x,bbox[1].x,bbox[0].y,bbox[1].y,bbox[0].z || 0, bbox[1].z || 0, m_range[0], m_range[1]]
242
+ end
243
+
244
+ def create_bbox(geometry)
245
+ bbox = geometry.bounding_box
246
+ [[bbox[0].x,bbox[0].y,bbox[1].x,bbox[1].y].pack("E4"),bbox]
247
+ end
248
+
249
+ def build_polyline(geometry,str)
250
+ if geometry.is_a? GeoRuby::SimpleFeatures::LineString
251
+ str << [1,geometry.length,0].pack("V3")
252
+ geometry.each do |point|
253
+ str << [point.x,point.y].pack("E2")
254
+ end
255
+ else
256
+ #multilinestring
257
+ str << [geometry.length,geometry.inject(0) {|l, ls| l + ls.length}].pack("V2")
258
+ str << geometry.inject([0]) {|a,ls| a << (a.last + ls.length)}.pack("V#{geometry.length}") #last element of the previous array is dropped
259
+ geometry.each do |ls|
260
+ ls.each do |point|
261
+ str << [point.x,point.y].pack("E2")
262
+ end
263
+ end
264
+ end
265
+ str
266
+ end
267
+
268
+ def build_polyline_zm(geometry,zm,range,str)
269
+ str << range.pack("E2")
270
+ if geometry.is_a? GeoRuby::SimpleFeatures::LineString
271
+ geometry.each do |point|
272
+ str << [point.instance_variable_get(zm)].pack("E")
273
+ end
274
+ else
275
+ #multilinestring
276
+ geometry.each do |ls|
277
+ ls.each do |point|
278
+ str << [point.instance_variable_get(zm)].pack("E")
279
+ end
280
+ end
281
+ end
282
+ str
283
+ end
284
+
285
+ def build_polygon(geometry,str)
286
+ if geometry.is_a? GeoRuby::SimpleFeatures::Polygon
287
+ str << [geometry.length,geometry.inject(0) {|l, lr| l + lr.length}].pack("V2")
288
+ str << geometry.inject([0]) {|a,lr| a << (a.last + lr.length)}.pack("V#{geometry.length}") #last element of the previous array is dropped
289
+ geometry.each do |lr|
290
+ lr.each do |point|
291
+ str << [point.x,point.y].pack("E2")
292
+ end
293
+ end
294
+ else
295
+ #multipolygon
296
+ num_rings = geometry.inject(0) {|l, poly| l + poly.length}
297
+ str << [num_rings, geometry.inject(0) {|l, poly| l + poly.inject(0) {|l2,lr| l2 + lr.length} }].pack("V2")
298
+ str << geometry.inject([0]) {|a,poly| poly.inject(a) {|a2, lr| a2 << (a2.last + lr.length)}}.pack("V#{num_rings}") #last element of the previous array is dropped
299
+ geometry.each do |poly|
300
+ poly.each do |lr|
301
+ lr.each do |point|
302
+ str << [point.x,point.y].pack("E2")
303
+ end
304
+ end
305
+ end
306
+ end
307
+ str
308
+ end
309
+
310
+ def build_polygon_zm(geometry,zm,range,str)
311
+ str << range.pack("E2")
312
+ if geometry.is_a? GeoRuby::SimpleFeatures::Polygon
313
+ geometry.each do |lr|
314
+ lr.each do |point|
315
+ str << [point.instance_variable_get(zm)].pack("E")
316
+ end
317
+ end
318
+ else
319
+ geometry.each do |poly|
320
+ poly.each do |lr|
321
+ lr.each do |point|
322
+ str << [point.instance_variable_get(zm)].pack("E")
323
+ end
324
+ end
325
+ end
326
+ end
327
+ str
328
+ end
329
+
330
+ def build_multi_point(geometry,str)
331
+ str << [geometry.length].pack("V")
332
+ geometry.each do |point|
333
+ str << [point.x,point.y].pack("E2")
334
+ end
335
+ str
336
+ end
337
+
338
+ def build_multi_point_zm(geometry,zm,range,str)
339
+ str << range.pack("E2")
340
+ geometry.each do |point|
341
+ str << [point.instance_variable_get(zm)].pack("E")
342
+ end
343
+ str
344
+ end
345
+ end