georuby_remake 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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