georuby_remake 1.0.0
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +6 -0
- data/History.txt +4 -0
- data/LICENSE +21 -0
- data/README.txt +118 -0
- data/Rakefile +47 -0
- data/VERSION +1 -0
- data/georuby_remake.gemspec +132 -0
- data/lib/geo_ruby.rb +5 -0
- data/lib/geo_ruby/gpx4r.rb +9 -0
- data/lib/geo_ruby/gpx4r/gpx.rb +104 -0
- data/lib/geo_ruby/shp4r.rb +57 -0
- data/lib/geo_ruby/shp4r/dbf.rb +38 -0
- data/lib/geo_ruby/shp4r/shp_file.rb +297 -0
- data/lib/geo_ruby/shp4r/shp_record.rb +14 -0
- data/lib/geo_ruby/shp4r/shp_transaction.rb +345 -0
- data/lib/geo_ruby/simple_features.rb +20 -0
- data/lib/geo_ruby/simple_features/envelope.rb +167 -0
- data/lib/geo_ruby/simple_features/ewkb_parser.rb +216 -0
- data/lib/geo_ruby/simple_features/ewkt_parser.rb +336 -0
- data/lib/geo_ruby/simple_features/geometry.rb +230 -0
- data/lib/geo_ruby/simple_features/geometry_collection.rb +136 -0
- data/lib/geo_ruby/simple_features/geometry_factory.rb +81 -0
- data/lib/geo_ruby/simple_features/georss_parser.rb +135 -0
- data/lib/geo_ruby/simple_features/helper.rb +18 -0
- data/lib/geo_ruby/simple_features/line_string.rb +206 -0
- data/lib/geo_ruby/simple_features/linear_ring.rb +12 -0
- data/lib/geo_ruby/simple_features/multi_line_string.rb +51 -0
- data/lib/geo_ruby/simple_features/multi_point.rb +46 -0
- data/lib/geo_ruby/simple_features/multi_polygon.rb +52 -0
- data/lib/geo_ruby/simple_features/point.rb +364 -0
- data/lib/geo_ruby/simple_features/polygon.rb +157 -0
- data/spec/data/gpx/fells_loop.gpx +1077 -0
- data/spec/data/gpx/long.gpx +1642 -0
- data/spec/data/gpx/long.kml +31590 -0
- data/spec/data/gpx/long.nmea +2220 -0
- data/spec/data/gpx/short.gpx +13634 -0
- data/spec/data/gpx/short.kml +130 -0
- data/spec/data/gpx/tracktreks.gpx +706 -0
- data/spec/data/multipoint.dbf +0 -0
- data/spec/data/multipoint.shp +0 -0
- data/spec/data/multipoint.shx +0 -0
- data/spec/data/point.dbf +0 -0
- data/spec/data/point.shp +0 -0
- data/spec/data/point.shx +0 -0
- data/spec/data/polygon.dbf +0 -0
- data/spec/data/polygon.shp +0 -0
- data/spec/data/polygon.shx +0 -0
- data/spec/data/polyline.dbf +0 -0
- data/spec/data/polyline.shp +0 -0
- data/spec/data/polyline.shx +0 -0
- data/spec/geo_ruby/gpx4r/gpx_spec.rb +106 -0
- data/spec/geo_ruby/shp4r/shp_spec.rb +240 -0
- data/spec/geo_ruby/simple_features/envelope_spec.rb +45 -0
- data/spec/geo_ruby/simple_features/ewkb_parser_spec.rb +158 -0
- data/spec/geo_ruby/simple_features/ewkt_parser_spec.rb +179 -0
- data/spec/geo_ruby/simple_features/geometry_collection_spec.rb +55 -0
- data/spec/geo_ruby/simple_features/geometry_factory_spec.rb +11 -0
- data/spec/geo_ruby/simple_features/geometry_spec.rb +32 -0
- data/spec/geo_ruby/simple_features/georss_parser_spec.rb +218 -0
- data/spec/geo_ruby/simple_features/line_string_spec.rb +245 -0
- data/spec/geo_ruby/simple_features/linear_ring_spec.rb +14 -0
- data/spec/geo_ruby/simple_features/multi_line_string_spec.rb +54 -0
- data/spec/geo_ruby/simple_features/multi_point_spec.rb +35 -0
- data/spec/geo_ruby/simple_features/multi_polygon_spec.rb +50 -0
- data/spec/geo_ruby/simple_features/point_spec.rb +356 -0
- data/spec/geo_ruby/simple_features/polygon_spec.rb +108 -0
- data/spec/geo_ruby_spec.rb +27 -0
- data/spec/spec.opts +6 -0
- data/spec/spec_helper.rb +65 -0
- 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
|