GeoRuby 1.1.2 → 1.2.0
Sign up to get free protection for your applications and to get access to all the features.
- data/README +16 -13
- data/lib/geo_ruby/shp4r/dbf.rb +234 -0
- data/lib/geo_ruby/shp4r/shp.rb +301 -0
- data/lib/geo_ruby/simple_features/geometry.rb +1 -0
- data/lib/geo_ruby.rb +1 -1
- data/rakefile.rb +4 -4
- data/test/data/point.dbf +0 -0
- data/test/data/point.shp +0 -0
- data/test/data/point.shx +0 -0
- data/test/data/polygon.dbf +0 -0
- data/test/data/polygon.shp +0 -0
- data/test/data/polygon.shx +0 -0
- data/test/data/polyline.dbf +0 -0
- data/test/data/polyline.shp +0 -0
- data/test/data/polyline.shx +0 -0
- data/test/test_shp.rb +77 -0
- data/tools/db.yml +6 -0
- data/tools/lib/spatial_adapter/MIT-LICENSE +7 -0
- data/tools/lib/spatial_adapter/README +116 -0
- data/tools/lib/spatial_adapter/init.rb +10 -0
- data/tools/lib/spatial_adapter/lib/common_spatial_adapter.rb +175 -0
- data/tools/lib/spatial_adapter/lib/mysql_spatial_adapter.rb +143 -0
- data/tools/lib/spatial_adapter/lib/post_gis_adapter.rb +333 -0
- data/tools/lib/spatial_adapter/rakefile.rb +35 -0
- data/tools/lib/spatial_adapter/test/access_mysql_test.rb +87 -0
- data/tools/lib/spatial_adapter/test/access_postgis_test.rb +151 -0
- data/tools/lib/spatial_adapter/test/common/common_mysql.rb +18 -0
- data/tools/lib/spatial_adapter/test/common/common_postgis.rb +19 -0
- data/tools/lib/spatial_adapter/test/db/database_mysql.yml +5 -0
- data/tools/lib/spatial_adapter/test/db/database_postgis.yml +4 -0
- data/tools/lib/spatial_adapter/test/find_mysql_test.rb +64 -0
- data/tools/lib/spatial_adapter/test/find_postgis_test.rb +65 -0
- data/tools/lib/spatial_adapter/test/migration_mysql_test.rb +136 -0
- data/tools/lib/spatial_adapter/test/migration_postgis_test.rb +170 -0
- data/tools/lib/spatial_adapter/test/models/models_mysql.rb +25 -0
- data/tools/lib/spatial_adapter/test/models/models_postgis.rb +41 -0
- data/tools/lib/spatial_adapter/test/schema/schema_mysql.rb +40 -0
- data/tools/lib/spatial_adapter/test/schema/schema_postgis.rb +69 -0
- data/tools/shp2sql.rb +91 -0
- metadata +47 -4
data/README
CHANGED
@@ -1,5 +1,5 @@
|
|
1
1
|
=GeoRuby
|
2
|
-
This is GeoRuby 1.
|
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
|
-
-
|
32
|
-
- Addition of
|
33
|
-
|
34
|
-
|
35
|
-
-
|
36
|
-
-
|
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
|
-
-
|
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
|
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
|