GeoRuby 1.1.2 → 1.2.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (40) hide show
  1. data/README +16 -13
  2. data/lib/geo_ruby/shp4r/dbf.rb +234 -0
  3. data/lib/geo_ruby/shp4r/shp.rb +301 -0
  4. data/lib/geo_ruby/simple_features/geometry.rb +1 -0
  5. data/lib/geo_ruby.rb +1 -1
  6. data/rakefile.rb +4 -4
  7. data/test/data/point.dbf +0 -0
  8. data/test/data/point.shp +0 -0
  9. data/test/data/point.shx +0 -0
  10. data/test/data/polygon.dbf +0 -0
  11. data/test/data/polygon.shp +0 -0
  12. data/test/data/polygon.shx +0 -0
  13. data/test/data/polyline.dbf +0 -0
  14. data/test/data/polyline.shp +0 -0
  15. data/test/data/polyline.shx +0 -0
  16. data/test/test_shp.rb +77 -0
  17. data/tools/db.yml +6 -0
  18. data/tools/lib/spatial_adapter/MIT-LICENSE +7 -0
  19. data/tools/lib/spatial_adapter/README +116 -0
  20. data/tools/lib/spatial_adapter/init.rb +10 -0
  21. data/tools/lib/spatial_adapter/lib/common_spatial_adapter.rb +175 -0
  22. data/tools/lib/spatial_adapter/lib/mysql_spatial_adapter.rb +143 -0
  23. data/tools/lib/spatial_adapter/lib/post_gis_adapter.rb +333 -0
  24. data/tools/lib/spatial_adapter/rakefile.rb +35 -0
  25. data/tools/lib/spatial_adapter/test/access_mysql_test.rb +87 -0
  26. data/tools/lib/spatial_adapter/test/access_postgis_test.rb +151 -0
  27. data/tools/lib/spatial_adapter/test/common/common_mysql.rb +18 -0
  28. data/tools/lib/spatial_adapter/test/common/common_postgis.rb +19 -0
  29. data/tools/lib/spatial_adapter/test/db/database_mysql.yml +5 -0
  30. data/tools/lib/spatial_adapter/test/db/database_postgis.yml +4 -0
  31. data/tools/lib/spatial_adapter/test/find_mysql_test.rb +64 -0
  32. data/tools/lib/spatial_adapter/test/find_postgis_test.rb +65 -0
  33. data/tools/lib/spatial_adapter/test/migration_mysql_test.rb +136 -0
  34. data/tools/lib/spatial_adapter/test/migration_postgis_test.rb +170 -0
  35. data/tools/lib/spatial_adapter/test/models/models_mysql.rb +25 -0
  36. data/tools/lib/spatial_adapter/test/models/models_postgis.rb +41 -0
  37. data/tools/lib/spatial_adapter/test/schema/schema_mysql.rb +40 -0
  38. data/tools/lib/spatial_adapter/test/schema/schema_postgis.rb +69 -0
  39. data/tools/shp2sql.rb +91 -0
  40. metadata +47 -4
data/README CHANGED
@@ -1,5 +1,5 @@
1
1
  =GeoRuby
2
- This is GeoRuby 1.1.0. It is intended as a holder for data returned from PostGIS and the Spatial Extensions of MySql. The data model roughly follows the OGC "Simple Features for SQL" specification (see http://www.opengis.org/docs/99-049.pdf), although without any kind of advanced functionalities (such as geometric operators or reprojections).
2
+ This is GeoRuby 1.2.0. It is intended as a holder for data returned from PostGIS and the Spatial Extensions of MySql. The data model roughly follows the OGC "Simple Features for SQL" specification (see http://www.opengis.org/docs/99-049.pdf), although without any kind of advanced functionalities (such as geometric operators or reprojections).
3
3
 
4
4
  ===Available data types
5
5
  The following geometric data types are provided :
@@ -19,29 +19,32 @@ On top of this an Envelope class is available, to contain the bounding box of a
19
19
  ===Input and output
20
20
  These geometries can be input and output in WKB/EWKB/WKT/EWKT format (as well as the related HexWKB and HexEWKB formats). HexEWKB and WKB are the default form under which geometric data is returned respectively from PostGIS and MySql.
21
21
 
22
- GeoRSS Simple, GeoRSS W3CGeo, GeoRSS GML can also be input and output. Note that they will not output valid RSS, but just the part strictly concerning the geometry as outlined in http://www.georss.org/1/ . Since the model does not allow multiple geometries, for geometry collections, only the first geometry will be output. Similarly, for polygons, the GeoRSS output will only contain the outer ring. As for W3CGeo output, only points can be output, so the first point of the geometry is chosen. By default the Simple format is output. Envelope can also be output in these formats: The box geometric type is chosen (except for W3CGeo, where the center of the envelope is chose).
22
+ GeoRSS Simple, GeoRSS W3CGeo, GeoRSS GML can also be input and output. Note that they will not output valid RSS, but just the part strictly concerning the geometry as outlined in http://www.georss.org/1/ . Since the model does not allow multiple geometries, for geometry collections, only the first geometry will be output. Similarly, for polygons, the GeoRSS output will only contain the outer ring. As for W3CGeo output, only points can be output, so the first point of the geometry is chosen. By default the Simple format is output. Envelope can also be output in these formats: The box geometric type is chosen (except for W3CGeo, where the center of the envelope is chose). These formats can also be input and a GeoRuby geometry will be created. Note that it will not read a valid RSS file, only a geometry string.
23
23
 
24
24
  On top of that, there is now support for KML output. As for GeoRSS, a valid KML file will not be output, but only the geometric data. Options <tt>:id</tt>, <tt>:extrude</tt>, <tt>:tesselate</tt> and <tt>:altitude_mode</tt> can be given. Note that if the <tt>:altitude_mode</tt> option is not passed or set to <tt>clampToGround</tt>, the altitude data will not be output even if present. Envelopes output a LatLonAltBox instead of a geometry.
25
25
 
26
+ ===SHP Reading
27
+ Support for reading ESRI shapefiles (http://www.esri.com/library/whitepapers/pdfs/shapefile.pdf) has been added. A tool called <tt>shp2sql.rb</tt> is also provided : it shows how to use the SHP reading functionality together with the spatial adapter plugin for Rails to import spatial features into MySQL and PostGIS.
28
+
26
29
  ===Installation
27
30
  To install the latest version, just type :
28
31
  gem install GeoRuby
29
32
 
30
33
  ===Changes since the last version
31
- - Input of GeoRSS geometries (1.1.1)
32
- - Addition of the Vincenty ellipsoidal distance calculation method
33
- - The spherical distance now assumes the x and y are in degrees not radians
34
- - Addition of aliases to the +x+ and +y+ accessors, as well as the <tt>from_x_y_...</tt> constructors so the +x+ can be replaced by +lon+ and +y+ by +lat+.
35
- - Output of GeoRSS Simple, W3CGeo and GML formats
36
- - Output of KML format
37
-
38
- ===For version 1.2.0
39
- - Reading (and writing?) of SHP files
34
+ - Reading of ESRI shapefiles
35
+ - Addition of a small tool to import spatial features in MySQL and PostGIS from a SHP file
36
+
37
+ ===Coming in the next versions
38
+ - Writing of SHP files
39
+ - Better shp2sql import tool
40
40
  - Documentation
41
- - Update of the tutorials to show the GeoRSS and KML output, as well as the reading of SHP files
41
+ - Geometric operators
42
+
43
+ ===Acknowledgement
44
+ The SHP reading part uses a modified version of the DBF library (http://rubyforge.org/projects/dbf/) by Keith Morrison (http://infused.org).
42
45
 
43
46
  ===License
44
47
  GeoRuby is released under the MIT license.
45
48
 
46
49
  ===Support
47
- Any questions, enhancement proposals, bug notifications or corrections can be sent to mailto:guilhem.vellut+georuby@gmail.com.
50
+ Any questions, enhancement proposals, bug notifications or corrections can be sent to mailto:guilhem.vellut@gmail.com.
@@ -0,0 +1,234 @@
1
+ # Copyright 2006 Keith Morrison (http://infused.org)
2
+ # Modified version of his DBF library (http://rubyforge.org/projects/dbf/)
3
+
4
+ module GeoRuby
5
+ module Shp4r
6
+ module Dbf
7
+
8
+ DBF_HEADER_SIZE = 32
9
+ FPT_HEADER_SIZE = 512
10
+ FPT_BLOCK_HEADER_SIZE = 8
11
+ DATE_REGEXP = /([\d]{4})([\d]{2})([\d]{2})/
12
+ VERSION_DESCRIPTIONS = {
13
+ "02" => "FoxBase",
14
+ "03" => "dBase III without memo file",
15
+ "04" => "dBase IV without memo file",
16
+ "05" => "dBase V without memo file",
17
+ "30" => "Visual FoxPro",
18
+ "31" => "Visual FoxPro with AutoIncrement field",
19
+ "7b" => "dBase IV with memo file",
20
+ "83" => "dBase III with memo file",
21
+ "8b" => "dBase IV with memo file",
22
+ "8e" => "dBase IV with SQL table",
23
+ "f5" => "FoxPro with memo file",
24
+ "fb" => "FoxPro without memo file"
25
+ }
26
+
27
+ class DBFError < StandardError; end
28
+ class UnpackError < DBFError; end
29
+
30
+ class Reader
31
+ attr_reader :field_count
32
+ attr_reader :fields
33
+ attr_reader :record_count
34
+ attr_reader :version
35
+ attr_reader :last_updated
36
+
37
+ def initialize(file)
38
+ @data_file = File.open(file, 'rb')
39
+ @memo_file = open_memo(file)
40
+ reload!
41
+ end
42
+
43
+ def self.open(file)
44
+ reader = Reader.new(file)
45
+ if block_given?
46
+ yield reader
47
+ reader.close
48
+ else
49
+ reader
50
+ end
51
+ end
52
+
53
+ def close
54
+ @data_file.close
55
+ end
56
+
57
+ def reload!
58
+ get_header_info
59
+ get_memo_header_info if @memo_file
60
+ get_field_descriptors
61
+ end
62
+
63
+ def has_memo_file?
64
+ @memo_file ? true : false
65
+ end
66
+
67
+ def open_memo(file)
68
+ %w(fpt FPT dbt DBT).each do |extension|
69
+ filename = file.sub(/dbf$/i, extension)
70
+ if File.exists?(filename)
71
+ @memo_file_format = extension.downcase.to_sym
72
+ return File.open(filename, 'rb')
73
+ end
74
+ end
75
+ nil
76
+ end
77
+
78
+ def field(field_name)
79
+ @fields.detect {|f| f.name == field_name.to_s}
80
+ end
81
+
82
+ def memo(start_block)
83
+ @memo_file.rewind
84
+ @memo_file.seek(start_block * @memo_block_size)
85
+ if @memo_file_format == :fpt
86
+ memo_type, memo_size, memo_string = @memo_file.read(@memo_block_size).unpack("NNa56")
87
+ if memo_size > @memo_block_size - FPT_BLOCK_HEADER_SIZE
88
+ memo_string << @memo_file.read(memo_size - @memo_block_size + FPT_BLOCK_HEADER_SIZE)
89
+ end
90
+ else
91
+ if version == "83" # dbase iii
92
+ memo_string = ""
93
+ loop do
94
+ memo_string << block = @memo_file.read(512)
95
+ break if block.strip.size < 512
96
+ end
97
+ elsif version == "8b" # dbase iv
98
+ memo_type, memo_size = @memo_file.read(8).unpack("LL")
99
+ memo_string = @memo_file.read(memo_size)
100
+ end
101
+ end
102
+ memo_string
103
+ end
104
+
105
+ # An array of all the records contained in the database file
106
+ def records
107
+ seek_to_record(0)
108
+ @records ||= Array.new(@record_count) do |i|
109
+ if active_record?
110
+ build_record
111
+ else
112
+ seek_to_record(i + 1)
113
+ nil
114
+ end
115
+ end
116
+ end
117
+
118
+ alias_method :rows, :records
119
+
120
+ # Jump to record
121
+ def record(index)
122
+ seek_to_record(index)
123
+ active_record? ? build_record : nil
124
+ end
125
+
126
+ alias_method :row, :record
127
+
128
+ def version_description
129
+ VERSION_DESCRIPTIONS[version]
130
+ end
131
+
132
+ private
133
+
134
+ def active_record?
135
+ @data_file.read(1).unpack('H2').to_s == '20' rescue false
136
+ end
137
+
138
+ def build_record
139
+ record = DbfRecord.new
140
+ @fields.each do |field|
141
+ case field.type
142
+ when 'N'
143
+ record[field.name] = unpack_integer(field) rescue nil
144
+ when 'F'
145
+ record[field.name] = unpack_float(field) rescue nil
146
+ when 'D'
147
+ raw = unpack_string(field).to_s.strip
148
+ unless raw.empty?
149
+ begin
150
+ record[field.name] = Time.gm(*raw.match(DATE_REGEXP).to_a.slice(1,3).map {|n| n.to_i})
151
+ rescue
152
+ record[field.name] = Date.new(*raw.match(DATE_REGEXP).to_a.slice(1,3).map {|n| n.to_i}) rescue nil
153
+ end
154
+ end
155
+ when 'M'
156
+ starting_block = unpack_integer(field)
157
+ record[field.name] = starting_block == 0 ? nil : memo(starting_block) rescue nil
158
+ when 'L'
159
+ record[field.name] = unpack_string(field) =~ /^(y|t)$/i ? true : false rescue false
160
+ when 'C'
161
+ record[field.name] = unpack_string(field).strip
162
+ else
163
+ record[field.name] = unpack_string(field)
164
+ end
165
+ end
166
+ record
167
+ end
168
+
169
+ def get_header_info
170
+ @data_file.rewind
171
+ @version, @record_count, @header_length, @record_length = @data_file.read(DBF_HEADER_SIZE).unpack('H2xxxVvv')
172
+ @field_count = (@header_length - DBF_HEADER_SIZE + 1) / DBF_HEADER_SIZE
173
+ end
174
+
175
+ def get_field_descriptors
176
+ @fields = Array.new(@field_count) {|i| Field.new(*@data_file.read(32).unpack('a10xax4CC'))}
177
+ end
178
+
179
+ def get_memo_header_info
180
+ @memo_file.rewind
181
+ if @memo_file_format == :fpt
182
+ @memo_next_available_block, @memo_block_size = @memo_file.read(FPT_HEADER_SIZE).unpack('Nxxn')
183
+ else
184
+ @memo_block_size = 512
185
+ @memo_next_available_block = File.size(@memo_file.path) / @memo_block_size
186
+ end
187
+ end
188
+
189
+ def seek(offset)
190
+ @data_file.seek(@header_length + offset)
191
+ end
192
+
193
+ def seek_to_record(index)
194
+ seek(@record_length * index)
195
+ end
196
+
197
+ def unpack_field(field)
198
+ @data_file.read(field.length).unpack("a#{field.length}")
199
+ end
200
+
201
+ def unpack_string(field)
202
+ unpack_field(field).to_s
203
+ end
204
+
205
+ def unpack_integer(field)
206
+ unpack_string(field).to_i
207
+ end
208
+
209
+ def unpack_float(field)
210
+ unpack_string(field).to_f
211
+ end
212
+
213
+ end
214
+
215
+ class FieldError < StandardError; end
216
+
217
+ class Field
218
+ attr_reader :name, :type, :length, :decimal
219
+
220
+ def initialize(name, type, length, decimal)
221
+ raise FieldError, "field length must be greater than 0" unless length > 0
222
+ if type == 'N' and decimal != 0
223
+ type = 'F'
224
+ end
225
+ @name, @type, @length, @decimal = name.strip, type,length, decimal
226
+ end
227
+ end
228
+
229
+ class DbfRecord < Hash
230
+ end
231
+
232
+ end
233
+ end
234
+ end
@@ -0,0 +1,301 @@
1
+ require File.dirname(__FILE__) + '/dbf'
2
+
3
+ module GeoRuby
4
+ module Shp4r
5
+
6
+ #Enumerates all the types of SHP geometries. The MULTIPATCH one is the only one not currently supported by GeoRuby.
7
+ module ShpType
8
+ NULL_SHAPE = 0
9
+ POINT = 1
10
+ POLYLINE = 3
11
+ POLYGON = 5
12
+ MULTIPOINT = 8
13
+ POINTZ = 11
14
+ POLYLINEZ = 13
15
+ POLYGONZ = 15
16
+ MULTIPOINTZ = 18
17
+ POINTM = 21
18
+ POLYLINEM = 23
19
+ POLYGONM = 25
20
+ MULTIPOINTM = 28
21
+ MULTIPATCH = 31 #not supported here
22
+ end
23
+
24
+ #An interface to an ESRI shapefile (actually 3 files : shp, shx and dbf). Currently supports only the reading of geometries.
25
+ class ShpFile
26
+ attr_reader :shp_type, :record_count, :xmin, :ymin, :xmax, :ymax, :zmin, :zmax, :mmin, :mmax
27
+
28
+ #Opens a SHP file. Both "abc.shp" and "abc" are accepted. The files "abc.shp", "abc.shx" and "abc.dbf" must be present
29
+ def initialize(file, access = "r")
30
+ #strip the shp out of the file if present
31
+ file = file.gsub(/.shp$/i,"")
32
+ #check existence of shp, dbf and shx files
33
+ unless File.exists?(file + ".shp") and File.exists?(file + ".dbf") and File.exists?(file + ".shx")
34
+ raise MalformedShpException.new("Missing one of shp, dbf or shx for: #{file}")
35
+ end
36
+
37
+ @dbf = Dbf::Reader.open(file + ".dbf")
38
+ @shx = File.open(file + ".shx","rb")
39
+ @shp = File.open(file + ".shp","rb")
40
+ read_index
41
+ end
42
+
43
+ #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>.
44
+ def self.open(file,access = "r")
45
+ shpfile = ShpFile.new(file,access)
46
+ if block_given?
47
+ yield shpfile
48
+ shpfile.close
49
+ else
50
+ shpfile
51
+ end
52
+ end
53
+
54
+ #Closes a shapefile
55
+ def close
56
+ @dbf.close
57
+ @shx.close
58
+ @shp.close
59
+ end
60
+
61
+ #return the description of data fields
62
+ def fields
63
+ @dbf.fields
64
+ end
65
+
66
+ #Tests if the file has no record
67
+ def empty?
68
+ record_count == 0
69
+ end
70
+
71
+ #Goes through each record
72
+ def each
73
+ (0...record_count).each do |i|
74
+ yield get_record(i)
75
+ end
76
+ end
77
+ alias :each_record :each
78
+
79
+ #Returns record +i+
80
+ def [](i)
81
+ get_record(i)
82
+ end
83
+
84
+ #Returns all the records
85
+ def records
86
+ Array.new(record_count) do |i|
87
+ get_record(i)
88
+ end
89
+ end
90
+
91
+ private
92
+ def read_index
93
+ file_length, @shp_type, @xmin, @ymin, @xmax, @ymax, @zmin, @zmax, @mmin,@mmax = @shx.read(100).unpack("x24Nx4VE8")
94
+ @record_count = (file_length - 50) / 4
95
+ unless @record_count == @dbf.record_count
96
+ raise MalformedShpException.new("Not the same number of records in SHP and DBF")
97
+ end
98
+ end
99
+
100
+ #TODO : refactor to minimize redundant code
101
+ def get_record(i)
102
+ return nil if record_count <= i or i < 0
103
+ dbf_record = @dbf.record(i)
104
+ @shx.seek(100 + 8 * i) #100 is the header length
105
+ offset,length = @shx.read(8).unpack("N2")
106
+ @shp.seek(offset * 2 + 8)
107
+ rec_shp_type = @shp.read(4).unpack("V")[0]
108
+
109
+ case(rec_shp_type)
110
+ when ShpType::POINT
111
+ x, y = @shp.read(16).unpack("E2")
112
+ geometry = GeoRuby::SimpleFeatures::Point.from_x_y(x,y)
113
+
114
+
115
+ when ShpType::POLYLINE #actually creates a multi_polyline
116
+ @shp.seek(32,IO::SEEK_CUR) #extent
117
+ num_parts, num_points = @shp.read(8).unpack("V2")
118
+
119
+ parts = @shp.read(num_parts * 4).unpack("V" + num_parts.to_s)
120
+ parts << num_points #indexes for LS of idx i go to parts of idx i to idx i +1
121
+
122
+ points = Array.new(num_points) do
123
+ x, y = @shp.read(16).unpack("E2")
124
+ GeoRuby::SimpleFeatures::Point.from_x_y(x,y)
125
+ end
126
+
127
+ line_strings = Array.new(num_parts) do |i|
128
+ GeoRuby::SimpleFeatures::LineString.from_points(points[(parts[i])...(parts[i+1])])
129
+ end
130
+
131
+ geometry = GeoRuby::SimpleFeatures::MultiLineString.from_line_strings(line_strings)
132
+
133
+
134
+ when ShpType::POLYGON
135
+ #TODO : TO CORRECT
136
+ #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
137
+ #Still sends back a multi polygon (so the correction above won't change what gets sent back)
138
+ @shp.seek(32,IO::SEEK_CUR)
139
+ num_parts, num_points = @shp.read(8).unpack("V2")
140
+ parts = @shp.read(num_parts * 4).unpack("V" + num_parts.to_s)
141
+ parts << num_points #indexes for LS of idx i go to parts of idx i to idx i +1
142
+ points = Array.new(num_points) do
143
+ x, y = @shp.read(16).unpack("E2")
144
+ GeoRuby::SimpleFeatures::Point.from_x_y(x,y)
145
+ end
146
+ linear_rings = Array.new(num_parts) do |i|
147
+ GeoRuby::SimpleFeatures::LinearRing.from_points(points[(parts[i])...(parts[i+1])])
148
+ end
149
+ geometry = GeoRuby::SimpleFeatures::MultiPolygon.from_polygons([GeoRuby::SimpleFeatures::Polygon.from_linear_rings(linear_rings)])
150
+
151
+
152
+ when ShpType::MULTIPOINT
153
+ @shp.seek(32,IO::SEEK_CUR)
154
+ num_points = @shp.read(4).unpack("V")[0]
155
+ points = Array.new(num_points) do
156
+ x, y = @shp.read(16).unpack("E2")
157
+ GeoRuby::SimpleFeatures::Point.from_x_y(x,y)
158
+ end
159
+ geometry = GeoRuby::SimpleFeatures::MultiPoint.from_points(points)
160
+
161
+
162
+ when ShpType::POINTZ
163
+ x, y, z, m = @shp.read(24).unpack("E4")
164
+ geometry = GeoRuby::SimpleFeatures::Point.from_x_y_z_m(x,y,z,m)
165
+
166
+
167
+ when ShpType::POLYLINEZ
168
+ @shp.seek(32,IO::SEEK_CUR)
169
+ num_parts, num_points = @shp.read(8).unpack("V2")
170
+ parts = @shp.read(num_parts * 4).unpack("V" + num_parts.to_s)
171
+ parts << num_points #indexes for LS of idx i go to parts of idx i to idx i +1
172
+ xys = Array.new(num_points) { @shp.read(16).unpack("E2") }
173
+ @shp.seek(16,IO::SEEK_CUR)
174
+ zs = Array.new(num_points) {@shp.read(8).unpack("E")[0]}
175
+ @shp.seek(16,IO::SEEK_CUR)
176
+ ms = Array.new(num_points) {@shp.read(8).unpack("E")[0]}
177
+ points = Array.new(num_points) do |i|
178
+ Point.from_x_y_z_m(xys[i][0],xys[i][1],zs[i],ms[i])
179
+ end
180
+ line_strings = Array.new(num_parts) do |i|
181
+ GeoRuby::SimpleFeatures::LineString.from_points(points[(parts[i])...(parts[i+1])],DEFAULT_SRID,true,true)
182
+ end
183
+ geometry = GeoRuby::SimpleFeatures::MultiLineString.from_line_strings(line_strings,DEFAULT_SRID,true,true)
184
+
185
+
186
+ when ShpType::POLYGONZ
187
+ #TODO : CORRECT
188
+
189
+ @shp.seek(32,IO::SEEK_CUR)#extent
190
+ num_parts, num_points = @shp.read(8).unpack("V2")
191
+ parts = @shp.read(num_parts * 4).unpack("V" + num_parts.to_s)
192
+ parts << num_points #indexes for LS of idx i go to parts of idx i to idx i +1
193
+ xys = Array.new(num_points) { @shp.read(16).unpack("E2") }
194
+ @shp.seek(16,IO::SEEK_CUR)#extent
195
+ zs = Array.new(num_points) {@shp.read(8).unpack("E")[0]}
196
+ @shp.seek(16,IO::SEEK_CUR)#extent
197
+ ms = Array.new(num_points) {@shp.read(8).unpack("E")[0]}
198
+ points = Array.new(num_points) do |i|
199
+ Point.from_x_y_z_m(xys[i][0],xys[i][1],zs[i],ms[i])
200
+ end
201
+ linear_rings = Array.new(num_parts) do |i|
202
+ GeoRuby::SimpleFeatures::LinearRing.from_points(points[(parts[i])...(parts[i+1])],DEFAULT_SRID,true,true)
203
+ end
204
+ geometry = GeoRuby::SimpleFeatures::MultiPolygon.from_polygons([GeoRuby::SimpleFeatures::Polygon.from_linear_rings(linear_rings)],DEFAULT_SRID,true,true)
205
+
206
+
207
+ when ShpType::MULTIPOINTZ
208
+ @shp.seek(32,IO::SEEK_CUR)
209
+ num_points = @shp.read(4).unpack("V")[0]
210
+ xys = Array.new(num_points) { @shp.read(16).unpack("E2") }
211
+ @shp.seek(16,IO::SEEK_CUR)
212
+ zs = Array.new(num_points) {@shp.read(8).unpack("E")[0]}
213
+ @shp.seek(16,IO::SEEK_CUR)
214
+ ms = Array.new(num_points) {@shp.read(8).unpack("E")[0]}
215
+
216
+ points = Array.new(num_points) do |i|
217
+ Point.from_x_y_z_m(xys[i][0],xys[i][1],zs[i],ms[i])
218
+ end
219
+
220
+ geometry = GeoRuby::SimpleFeatures::MultiPoint.from_points(points,DEFAULT_SRID,true,true)
221
+
222
+ when ShpType::POINTM
223
+ x, y, m = @shp.read(24).unpack("E3")
224
+ geometry = GeoRuby::SimpleFeatures::Point.from_x_y_m(x,y,m)
225
+
226
+ when ShpType::POLYLINEM
227
+ @shp.seek(32,IO::SEEK_CUR)
228
+ num_parts, num_points = @shp.read(8).unpack("V2")
229
+ parts = @shp.read(num_parts * 4).unpack("V" + num_parts.to_s)
230
+ parts << num_points #indexes for LS of idx i go to parts of idx i to idx i +1
231
+ xys = Array.new(num_points) { @shp.read(16).unpack("E2") }
232
+ @shp.seek(16,IO::SEEK_CUR)
233
+ ms = Array.new(num_points) {@shp.read(8).unpack("E")[0]}
234
+ points = Array.new(num_points) do |i|
235
+ Point.from_x_y_m(xys[i][0],xys[i][1],ms[i])
236
+ end
237
+ line_strings = Array.new(num_parts) do |i|
238
+ GeoRuby::SimpleFeatures::LineString.from_points(points[(parts[i])...(parts[i+1])],DEFAULT_SRID,false,true)
239
+ end
240
+ geometry = GeoRuby::SimpleFeatures::MultiLineString.from_line_strings(line_strings,DEFAULT_SRID,false,true)
241
+
242
+
243
+ when ShpType::POLYGONM
244
+ #TODO : CORRECT
245
+
246
+ @shp.seek(32,IO::SEEK_CUR)
247
+ num_parts, num_points = @shp.read(8).unpack("V2")
248
+ parts = @shp.read(num_parts * 4).unpack("V" + num_parts.to_s)
249
+ parts << num_points #indexes for LS of idx i go to parts of idx i to idx i +1
250
+ xys = Array.new(num_points) { @shp.read(16).unpack("E2") }
251
+ @shp.seek(16,IO::SEEK_CUR)
252
+ ms = Array.new(num_points) {@shp.read(8).unpack("E")[0]}
253
+ points = Array.new(num_points) do |i|
254
+ Point.from_x_y_m(xys[i][0],xys[i][1],ms[i])
255
+ end
256
+ linear_rings = Array.new(num_parts) do |i|
257
+ GeoRuby::SimpleFeatures::LinearRing.from_points(points[(parts[i])...(parts[i+1])],DEFAULT_SRID,false,true)
258
+ end
259
+ geometry = GeoRuby::SimpleFeatures::MultiPolygon.from_polygons([GeoRuby::SimpleFeatures::Polygon.from_linear_rings(linear_rings)],DEFAULT_SRID,false,true)
260
+
261
+
262
+ when ShpType::MULTIPOINTM
263
+ @shp.seek(32,IO::SEEK_CUR)
264
+ num_points = @shp.read(4).unpack("V")[0]
265
+ xys = Array.new(num_points) { @shp.read(16).unpack("E2") }
266
+ @shp.seek(16,IO::SEEK_CUR)
267
+ ms = Array.new(num_points) {@shp.read(8).unpack("E")[0]}
268
+
269
+ points = Array.new(num_points) do |i|
270
+ Point.from_x_y_m(xys[i][0],xys[i][1],ms[i])
271
+ end
272
+
273
+ geometry = GeoRuby::SimpleFeatures::MultiPoint.from_points(points,DEFAULT_SRID,false,true)
274
+ else
275
+ geometry = nil
276
+ end
277
+
278
+ ShpRecord.new(geometry,dbf_record)
279
+ end
280
+ end
281
+
282
+ #A SHP record : contains both the geometry and the data fields (from the DBF)
283
+ class ShpRecord
284
+ attr_accessor :geometry , :data
285
+
286
+ def initialize(geometry, data)
287
+ @geometry = geometry
288
+ @data = data
289
+ end
290
+
291
+ #Tests if the geometry is a NULL SHAPE
292
+ def has_null_shape?
293
+ @geometry.nil?
294
+ end
295
+ end
296
+
297
+ class MalformedShpException < StandardError
298
+ end
299
+
300
+ end
301
+ end