GeoRuby 1.2.4 → 1.3.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.
- data/README +37 -5
- data/lib/geo_ruby/shp4r/dbf.rb +5 -59
- data/lib/geo_ruby/shp4r/shp.rb +432 -34
- data/lib/geo_ruby/simple_features/geometry.rb +4 -0
- data/lib/geo_ruby/simple_features/geometry_collection.rb +14 -0
- data/lib/geo_ruby/simple_features/line_string.rb +13 -0
- data/lib/geo_ruby/simple_features/point.rb +4 -0
- data/lib/geo_ruby/simple_features/polygon.rb +14 -0
- data/rakefile.rb +1 -1
- data/test/data/multipoint.dbf +0 -0
- data/test/data/multipoint.shp +0 -0
- data/test/data/multipoint.shx +0 -0
- data/test/data/point.dbf +0 -0
- data/test/data/polyline2.dbf +0 -0
- data/test/data/polyline2.shp +0 -0
- data/test/data/polyline2.shx +0 -0
- data/test/test_shp_write.rb +150 -0
- metadata +10 -2
data/README
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
=GeoRuby
|
2
2
|
|
3
|
-
This is GeoRuby 1.
|
3
|
+
This is GeoRuby 1.3.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). It also supports various output and input formats (GeoRSS, KML, Shapefile).
|
4
4
|
|
5
5
|
===Available data types
|
6
6
|
The following geometric data types are provided :
|
@@ -24,22 +24,54 @@ GeoRSS Simple, GeoRSS W3CGeo, GeoRSS GML can also be input and output. Note that
|
|
24
24
|
|
25
25
|
On top of that, there is now support for KML output and input. 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. For the output, the following geometric types are supported : Point, LineString, Polygon.
|
26
26
|
|
27
|
-
===SHP
|
28
|
-
|
27
|
+
===SHP reading et writing
|
28
|
+
Georuby has support for reading ESRI shapefiles (http://www.esri.com/library/whitepapers/pdfs/shapefile.pdf). 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.
|
29
|
+
|
30
|
+
Here is an example of Shapefile reading, that goes through all the geometries in a file and disaply the values of the attributes :
|
31
|
+
ShpFile.open(shpfile) do |shp|
|
32
|
+
shp.each do |shape|
|
33
|
+
geom = shape.geometry #a GeoRuby SimpleFeature
|
34
|
+
att_data = shape.data #a Hash
|
35
|
+
shp.fields.each do |field|
|
36
|
+
puts att_data[field.name]
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
Support for ESRI shapefile creation and modification has been added as well. New shapefiles can be created given a geometry type and specifications for the DBF fields. Data can be added and removed from an existing shapefile. An update operation is also provided for convenience : it just performs a 'delete' and an 'add', which means the index of the modified record will change. Note that once a shapefile has been created, GeoRuby does not allow the modification of the schema (it will probably be done in a subsequent version).
|
42
|
+
|
43
|
+
Here is an example of how to create a new Shapefile with 2 fields :
|
44
|
+
shpfile = ShpFile.create('hello.shp',ShpType::POINT,[Dbf::Field.new("Hoyoyo","C",10),Dbf::Field.new("Boyoul","N",10,0)])
|
45
|
+
The file is then open for reading and writing.
|
46
|
+
|
47
|
+
Here is an example of how to write to a shapefile (created or not with GeoRuby) :
|
48
|
+
shpfile = ShpFile.open('places.shp')
|
49
|
+
shpfile.transaction do |tr|
|
50
|
+
tr.add(ShpRecord.new(Point.from_x_y(123.4,123.4),'Hoyoyo' => "AEZ",'Bouyoul' => 45))
|
51
|
+
tr.update(4,ShpRecord.new(Point.from_x_y(-16.67,16.41),'Hoyoyo' => "EatMe",'Bouyoul' => 42))
|
52
|
+
tr.delete(1)
|
53
|
+
end
|
54
|
+
shpfile.close
|
55
|
+
|
56
|
+
Note the transaction is just there so the operations on the files can be buffered. Nothing happens on the original files until the block has finished executing. Calling <tt>tr.rollback</tt> at anytime during the execution will prevent the modifications.
|
57
|
+
|
58
|
+
Also currently, error reporting is minimal and it has not been tested that thoroughly so caveat emptor and backup before performing any destructive operation.
|
29
59
|
|
30
60
|
===Installation
|
31
61
|
To install the latest version, just type :
|
32
62
|
gem install GeoRuby
|
33
63
|
|
34
64
|
===Changes since the last version
|
65
|
+
- Writing of ESRI shapefiles
|
35
66
|
- Reading of ESRI shapefiles
|
36
67
|
- Addition of a small tool to import spatial features in MySQL and PostGIS from a SHP file
|
37
68
|
|
38
69
|
===Coming in the next versions
|
39
|
-
-
|
70
|
+
- Schema modification of existing shapefiles
|
71
|
+
- More error reporting when writing shapefiles
|
72
|
+
- More tests on writing shapefiles ; tests on real-world shapefiles
|
40
73
|
- Better shp2sql import tool
|
41
74
|
- Documentation
|
42
|
-
- Geometric operators
|
43
75
|
|
44
76
|
===Acknowledgement
|
45
77
|
The SHP reading part uses a modified version of the DBF library (http://rubyforge.org/projects/dbf/) by Keith Morrison (http://infused.org). Thanks also to Pramukta Kumar and Pete Schwamb for their contributions.
|
data/lib/geo_ruby/shp4r/dbf.rb
CHANGED
@@ -6,8 +6,6 @@ module GeoRuby
|
|
6
6
|
module Dbf
|
7
7
|
|
8
8
|
DBF_HEADER_SIZE = 32
|
9
|
-
FPT_HEADER_SIZE = 512
|
10
|
-
FPT_BLOCK_HEADER_SIZE = 8
|
11
9
|
DATE_REGEXP = /([\d]{4})([\d]{2})([\d]{2})/
|
12
10
|
VERSION_DESCRIPTIONS = {
|
13
11
|
"02" => "FoxBase",
|
@@ -33,10 +31,11 @@ module GeoRuby
|
|
33
31
|
attr_reader :record_count
|
34
32
|
attr_reader :version
|
35
33
|
attr_reader :last_updated
|
34
|
+
attr_reader :header_length
|
35
|
+
attr_reader :record_length
|
36
36
|
|
37
37
|
def initialize(file)
|
38
38
|
@data_file = File.open(file, 'rb')
|
39
|
-
@memo_file = open_memo(file)
|
40
39
|
reload!
|
41
40
|
end
|
42
41
|
|
@@ -56,52 +55,13 @@ module GeoRuby
|
|
56
55
|
|
57
56
|
def reload!
|
58
57
|
get_header_info
|
59
|
-
get_memo_header_info if @memo_file
|
60
58
|
get_field_descriptors
|
61
59
|
end
|
62
60
|
|
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
61
|
def field(field_name)
|
79
62
|
@fields.detect {|f| f.name == field_name.to_s}
|
80
63
|
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
|
-
|
64
|
+
|
105
65
|
# An array of all the records contained in the database file
|
106
66
|
def records
|
107
67
|
seek_to_record(0)
|
@@ -114,7 +74,6 @@ module GeoRuby
|
|
114
74
|
end
|
115
75
|
end
|
116
76
|
end
|
117
|
-
|
118
77
|
alias_method :rows, :records
|
119
78
|
|
120
79
|
# Jump to record
|
@@ -152,9 +111,6 @@ module GeoRuby
|
|
152
111
|
record[field.name] = Date.new(*raw.match(DATE_REGEXP).to_a.slice(1,3).map {|n| n.to_i}) rescue nil
|
153
112
|
end
|
154
113
|
end
|
155
|
-
when 'M'
|
156
|
-
starting_block = unpack_integer(field)
|
157
|
-
record[field.name] = starting_block == 0 ? nil : memo(starting_block) rescue nil
|
158
114
|
when 'L'
|
159
115
|
record[field.name] = unpack_string(field) =~ /^(y|t)$/i ? true : false rescue false
|
160
116
|
when 'C'
|
@@ -175,17 +131,7 @@ module GeoRuby
|
|
175
131
|
def get_field_descriptors
|
176
132
|
@fields = Array.new(@field_count) {|i| Field.new(*@data_file.read(32).unpack('a10xax4CC'))}
|
177
133
|
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
|
-
|
134
|
+
|
189
135
|
def seek(offset)
|
190
136
|
@data_file.seek(@header_length + offset)
|
191
137
|
end
|
@@ -217,7 +163,7 @@ module GeoRuby
|
|
217
163
|
class Field
|
218
164
|
attr_reader :name, :type, :length, :decimal
|
219
165
|
|
220
|
-
def initialize(name, type, length, decimal)
|
166
|
+
def initialize(name, type, length, decimal = 0)
|
221
167
|
raise FieldError, "field length must be greater than 0" unless length > 0
|
222
168
|
if type == 'N' and decimal != 0
|
223
169
|
type = 'F'
|
data/lib/geo_ruby/shp4r/shp.rb
CHANGED
@@ -1,50 +1,57 @@
|
|
1
|
+
require 'date'
|
2
|
+
require 'fileutils' if !defined?(FileUtils)
|
1
3
|
require File.dirname(__FILE__) + '/dbf'
|
2
4
|
|
5
|
+
|
3
6
|
module GeoRuby
|
4
7
|
module Shp4r
|
5
8
|
|
6
9
|
#Enumerates all the types of SHP geometries. The MULTIPATCH one is the only one not currently supported by GeoRuby.
|
7
10
|
module ShpType
|
8
11
|
NULL_SHAPE = 0
|
9
|
-
POINT = 1
|
10
|
-
POLYLINE = 3
|
11
|
-
POLYGON = 5
|
12
|
+
POINT = 1
|
13
|
+
POLYLINE = 3
|
14
|
+
POLYGON = 5
|
12
15
|
MULTIPOINT = 8
|
13
|
-
POINTZ = 11
|
16
|
+
POINTZ = 11
|
14
17
|
POLYLINEZ = 13
|
15
|
-
POLYGONZ = 15
|
18
|
+
POLYGONZ = 15
|
16
19
|
MULTIPOINTZ = 18
|
17
|
-
POINTM = 21
|
20
|
+
POINTM = 21
|
18
21
|
POLYLINEM = 23
|
19
|
-
POLYGONM = 25
|
20
|
-
MULTIPOINTM = 28
|
21
|
-
MULTIPATCH = 31 #not supported here
|
22
|
+
POLYGONM = 25
|
23
|
+
MULTIPOINTM = 28
|
22
24
|
end
|
23
|
-
|
25
|
+
|
24
26
|
#An interface to an ESRI shapefile (actually 3 files : shp, shx and dbf). Currently supports only the reading of geometries.
|
25
27
|
class ShpFile
|
26
|
-
attr_reader :shp_type, :record_count, :xmin, :ymin, :xmax, :ymax, :zmin, :zmax, :mmin, :mmax
|
28
|
+
attr_reader :shp_type, :record_count, :xmin, :ymin, :xmax, :ymax, :zmin, :zmax, :mmin, :mmax, :file_root, :file_length
|
27
29
|
|
28
30
|
include Enumerable
|
29
31
|
|
30
32
|
#Opens a SHP file. Both "abc.shp" and "abc" are accepted. The files "abc.shp", "abc.shx" and "abc.dbf" must be present
|
31
|
-
def initialize(file
|
33
|
+
def initialize(file)
|
32
34
|
#strip the shp out of the file if present
|
33
|
-
|
35
|
+
@file_root = file.gsub(/.shp$/i,"")
|
34
36
|
#check existence of shp, dbf and shx files
|
35
|
-
unless File.exists?(
|
36
|
-
raise MalformedShpException.new("Missing one of shp, dbf or shx for: #{file}")
|
37
|
+
unless File.exists?(@file_root + ".shp") and File.exists?(@file_root + ".dbf") and File.exists?(@file_root + ".shx")
|
38
|
+
raise MalformedShpException.new("Missing one of shp, dbf or shx for: #{@file}")
|
37
39
|
end
|
38
40
|
|
39
|
-
@dbf = Dbf::Reader.open(
|
40
|
-
@shx = File.open(
|
41
|
-
@shp = File.open(
|
41
|
+
@dbf = Dbf::Reader.open(@file_root + ".dbf")
|
42
|
+
@shx = File.open(@file_root + ".shx","rb")
|
43
|
+
@shp = File.open(@file_root + ".shp","rb")
|
42
44
|
read_index
|
43
45
|
end
|
46
|
+
|
47
|
+
#force the reopening of the files compsing the shp. Close before calling this.
|
48
|
+
def reload!
|
49
|
+
initialize(@file_root)
|
50
|
+
end
|
44
51
|
|
45
52
|
#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>.
|
46
|
-
def self.open(file
|
47
|
-
shpfile = ShpFile.new(file
|
53
|
+
def self.open(file)
|
54
|
+
shpfile = ShpFile.new(file)
|
48
55
|
if block_given?
|
49
56
|
yield shpfile
|
50
57
|
shpfile.close
|
@@ -52,6 +59,30 @@ module GeoRuby
|
|
52
59
|
shpfile
|
53
60
|
end
|
54
61
|
end
|
62
|
+
|
63
|
+
#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.
|
64
|
+
def self.create(file,shp_type,fields,&proc)
|
65
|
+
file_root = file.gsub(/.shp$/i,"")
|
66
|
+
shx_io = File.open(file_root + ".shx","wb")
|
67
|
+
shp_io = File.open(file_root + ".shp","wb")
|
68
|
+
dbf_io = File.open(file_root + ".dbf","wb")
|
69
|
+
str = [9994,0,0,0,0,0,50,1000,shp_type,0,0,0,0,0,0,0,0].pack("N7V2E8")
|
70
|
+
shp_io << str
|
71
|
+
shx_io << str
|
72
|
+
rec_length = 1 + fields.inject(0) {|s,f| s + f.length} #+1 for the prefixed space (active record marker)
|
73
|
+
dbf_io << [3,107,7,7,0,33 + 32 * fields.length,rec_length ].pack("c4Vv2x20") #32 bytes for first part of header
|
74
|
+
fields.each do |field|
|
75
|
+
dbf_io << [field.name,field.type,field.length,field.decimal].pack("a10xax4CCx14")
|
76
|
+
end
|
77
|
+
dbf_io << ['0d'].pack("H2")
|
78
|
+
|
79
|
+
shx_io.close
|
80
|
+
shp_io.close
|
81
|
+
dbf_io.close
|
82
|
+
|
83
|
+
open(file,&proc)
|
84
|
+
|
85
|
+
end
|
55
86
|
|
56
87
|
#Closes a shapefile
|
57
88
|
def close
|
@@ -59,6 +90,21 @@ module GeoRuby
|
|
59
90
|
@shx.close
|
60
91
|
@shp.close
|
61
92
|
end
|
93
|
+
|
94
|
+
#starts a transaction, to buffer physical file operations on the shapefile components.
|
95
|
+
def transaction
|
96
|
+
trs = ShpTransaction.new(self,@dbf)
|
97
|
+
if block_given?
|
98
|
+
answer = yield trs
|
99
|
+
if answer == :rollback
|
100
|
+
trs.rollback
|
101
|
+
elsif !trs.rollbacked
|
102
|
+
trs.commit
|
103
|
+
end
|
104
|
+
else
|
105
|
+
trs
|
106
|
+
end
|
107
|
+
end
|
62
108
|
|
63
109
|
#return the description of data fields
|
64
110
|
def fields
|
@@ -92,8 +138,12 @@ module GeoRuby
|
|
92
138
|
|
93
139
|
private
|
94
140
|
def read_index
|
95
|
-
file_length, @shp_type, @xmin, @ymin, @xmax, @ymax, @zmin, @zmax, @mmin,@mmax = @shx.read(100).unpack("x24Nx4VE8")
|
96
|
-
@record_count = (file_length - 50) / 4
|
141
|
+
@file_length, @shp_type, @xmin, @ymin, @xmax, @ymax, @zmin, @zmax, @mmin,@mmax = @shx.read(100).unpack("x24Nx4VE8")
|
142
|
+
@record_count = (@file_length - 50) / 4
|
143
|
+
if @record_count == 0
|
144
|
+
#initialize the bboxes to default values so if data added, they will be replaced
|
145
|
+
@xmin, @ymin, @xmax, @ymax, @zmin, @zmax, @mmin,@mmax = Float::MAX, Float::MAX, -Float::MAX, -Float::MAX, Float::MAX, -Float::MAX, Float::MAX, -Float::MAX
|
146
|
+
end
|
97
147
|
unless @record_count == @dbf.record_count
|
98
148
|
raise MalformedShpException.new("Not the same number of records in SHP and DBF")
|
99
149
|
end
|
@@ -112,27 +162,19 @@ module GeoRuby
|
|
112
162
|
when ShpType::POINT
|
113
163
|
x, y = @shp.read(16).unpack("E2")
|
114
164
|
geometry = GeoRuby::SimpleFeatures::Point.from_x_y(x,y)
|
115
|
-
|
116
|
-
|
117
165
|
when ShpType::POLYLINE #actually creates a multi_polyline
|
118
166
|
@shp.seek(32,IO::SEEK_CUR) #extent
|
119
167
|
num_parts, num_points = @shp.read(8).unpack("V2")
|
120
|
-
|
121
168
|
parts = @shp.read(num_parts * 4).unpack("V" + num_parts.to_s)
|
122
169
|
parts << num_points #indexes for LS of idx i go to parts of idx i to idx i +1
|
123
|
-
|
124
170
|
points = Array.new(num_points) do
|
125
171
|
x, y = @shp.read(16).unpack("E2")
|
126
172
|
GeoRuby::SimpleFeatures::Point.from_x_y(x,y)
|
127
173
|
end
|
128
|
-
|
129
174
|
line_strings = Array.new(num_parts) do |i|
|
130
175
|
GeoRuby::SimpleFeatures::LineString.from_points(points[(parts[i])...(parts[i+1])])
|
131
176
|
end
|
132
|
-
|
133
177
|
geometry = GeoRuby::SimpleFeatures::MultiLineString.from_line_strings(line_strings)
|
134
|
-
|
135
|
-
|
136
178
|
when ShpType::POLYGON
|
137
179
|
#TODO : TO CORRECT
|
138
180
|
#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
|
@@ -149,8 +191,6 @@ module GeoRuby
|
|
149
191
|
GeoRuby::SimpleFeatures::LinearRing.from_points(points[(parts[i])...(parts[i+1])])
|
150
192
|
end
|
151
193
|
geometry = GeoRuby::SimpleFeatures::MultiPolygon.from_polygons([GeoRuby::SimpleFeatures::Polygon.from_linear_rings(linear_rings)])
|
152
|
-
|
153
|
-
|
154
194
|
when ShpType::MULTIPOINT
|
155
195
|
@shp.seek(32,IO::SEEK_CUR)
|
156
196
|
num_points = @shp.read(4).unpack("V")[0]
|
@@ -289,15 +329,373 @@ module GeoRuby
|
|
289
329
|
@geometry = geometry
|
290
330
|
@data = data
|
291
331
|
end
|
292
|
-
|
332
|
+
|
293
333
|
#Tests if the geometry is a NULL SHAPE
|
294
334
|
def has_null_shape?
|
295
335
|
@geometry.nil?
|
296
336
|
end
|
297
337
|
end
|
298
338
|
|
339
|
+
#An object returned from ShpFile#transaction. Buffers updates to a Shapefile
|
340
|
+
class ShpTransaction
|
341
|
+
attr_reader :rollbacked
|
342
|
+
|
343
|
+
def initialize(shp, dbf)
|
344
|
+
@deleted = Hash.new
|
345
|
+
@added = Array.new
|
346
|
+
@shp = shp
|
347
|
+
@dbf = dbf
|
348
|
+
end
|
349
|
+
|
350
|
+
#delete a record. Does not take into account the records added in the current transaction
|
351
|
+
def delete(i)
|
352
|
+
raise UnexistantRecordException.new("Invalid index : #{i}") if @shp.record_count <= i
|
353
|
+
@deleted[i] = true
|
354
|
+
end
|
355
|
+
|
356
|
+
#Update a record. In effect just a delete followed by an add.
|
357
|
+
def update(i, record)
|
358
|
+
delete(i)
|
359
|
+
add(record)
|
360
|
+
end
|
361
|
+
|
362
|
+
#add a ShpRecord at the end
|
363
|
+
def add(record)
|
364
|
+
record_type = to_shp_type(record.geometry)
|
365
|
+
raise IncompatibleGeometryException.new("Incompatible type") unless record_type==@shp.shp_type
|
366
|
+
@added << record
|
367
|
+
end
|
368
|
+
|
369
|
+
#updates the physical files
|
370
|
+
def commit
|
371
|
+
@shp.close
|
372
|
+
@shp_r = open(@shp.file_root + ".shp", "rb")
|
373
|
+
@dbf_r = open(@shp.file_root + ".dbf", "rb")
|
374
|
+
@shp_io = open(@shp.file_root + ".shp.tmp.shp", "wb")
|
375
|
+
@shx_io = open(@shp.file_root + ".shx.tmp.shx", "wb")
|
376
|
+
@dbf_io = open(@shp.file_root + ".dbf.tmp.dbf", "wb")
|
377
|
+
index = commit_delete
|
378
|
+
min_x,max_x,min_y,max_y,min_z,max_z,min_m,max_m = commit_add(index)
|
379
|
+
commit_finalize(min_x,max_x,min_y,max_y,min_z,max_z,min_m,max_m)
|
380
|
+
@shp_r.close
|
381
|
+
@dbf_r.close
|
382
|
+
@dbf_io.close
|
383
|
+
@shp_io.close
|
384
|
+
@shx_io.close
|
385
|
+
FileUtils.move(@shp.file_root + ".shp.tmp.shp", @shp.file_root + ".shp")
|
386
|
+
FileUtils.move(@shp.file_root + ".shx.tmp.shx", @shp.file_root + ".shx")
|
387
|
+
FileUtils.move(@shp.file_root + ".dbf.tmp.dbf", @shp.file_root + ".dbf")
|
388
|
+
|
389
|
+
@deleted = Hash.new
|
390
|
+
@added = Array.new
|
391
|
+
|
392
|
+
@shp.reload!
|
393
|
+
end
|
394
|
+
|
395
|
+
#prevents the udpate from taking place
|
396
|
+
def rollback
|
397
|
+
@deleted = Hash.new
|
398
|
+
@added = Array.new
|
399
|
+
@rollbacked = true
|
400
|
+
end
|
401
|
+
|
402
|
+
private
|
403
|
+
|
404
|
+
def to_shp_type(geom)
|
405
|
+
root = if geom.is_a? GeoRuby::SimpleFeatures::Point
|
406
|
+
"POINT"
|
407
|
+
elsif geom.is_a? GeoRuby::SimpleFeatures::LineString
|
408
|
+
"POLYLINE"
|
409
|
+
elsif geom.is_a? GeoRuby::SimpleFeatures::Polygon
|
410
|
+
"POLYGON"
|
411
|
+
elsif geom.is_a? GeoRuby::SimpleFeatures::MultiPoint
|
412
|
+
"MULTIPOINT"
|
413
|
+
elsif geom.is_a? GeoRuby::SimpleFeatures::MultiLineString
|
414
|
+
"POLYLINE"
|
415
|
+
elsif geom.is_a? GeoRuby::SimpleFeatures::MultiPolygon
|
416
|
+
"POLYGON"
|
417
|
+
else
|
418
|
+
false
|
419
|
+
end
|
420
|
+
return false if !root
|
421
|
+
|
422
|
+
if geom.with_z
|
423
|
+
root = root + "Z"
|
424
|
+
elsif geom.with_m
|
425
|
+
root = root + "M"
|
426
|
+
end
|
427
|
+
eval "ShpType::" + root
|
428
|
+
end
|
429
|
+
|
430
|
+
def commit_add(index)
|
431
|
+
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
|
432
|
+
@added.each do |record|
|
433
|
+
@dbf_io << ['20'].pack('H2')
|
434
|
+
@dbf.fields.each do |field|
|
435
|
+
data = record.data[field.name]
|
436
|
+
str = if field.type == 'D'
|
437
|
+
sprintf("%04i%02i%02i",data.year,data.month,data.mday)
|
438
|
+
elsif field.type == 'L'
|
439
|
+
if data
|
440
|
+
"T"
|
441
|
+
else
|
442
|
+
"F"
|
443
|
+
end
|
444
|
+
else
|
445
|
+
data.to_s
|
446
|
+
end
|
447
|
+
@dbf_io << [str].pack("a#{field.length}")
|
448
|
+
end
|
449
|
+
|
450
|
+
shp_str,min_xp,max_xp,min_yp,max_yp,min_zp,max_zp,min_mp,max_mp = build_shp_geometry(record.geometry)
|
451
|
+
max_x = max_xp if max_xp > max_x
|
452
|
+
min_x = min_xp if min_xp < min_x
|
453
|
+
max_y = max_yp if max_yp > max_y
|
454
|
+
min_y = min_yp if min_yp < min_y
|
455
|
+
max_z = max_zp if max_zp > max_z
|
456
|
+
min_z = min_zp if min_zp < min_z
|
457
|
+
max_m = max_mp if max_mp > max_m
|
458
|
+
min_m = min_mp if min_mp < min_m
|
459
|
+
length = (shp_str.length/2 + 2).to_i #num of 16-bit words; geom type is included (+2)
|
460
|
+
@shx_io << [(@shp_io.pos/2).to_i,length].pack("N2")
|
461
|
+
@shp_io << [index,length,@shp.shp_type].pack("N2V")
|
462
|
+
@shp_io << shp_str
|
463
|
+
index += 1
|
464
|
+
end
|
465
|
+
@shp_io.flush
|
466
|
+
@shx_io.flush
|
467
|
+
@dbf_io.flush
|
468
|
+
[min_x,max_x,min_y,max_y,min_z,max_z,min_m,max_m]
|
469
|
+
end
|
470
|
+
|
471
|
+
def commit_delete
|
472
|
+
@shp_r.rewind
|
473
|
+
header = @shp_r.read(100)
|
474
|
+
@shp_io << header
|
475
|
+
@shx_io << header
|
476
|
+
index = 1
|
477
|
+
while(!@shp_r.eof?)
|
478
|
+
icur,length = @shp_r.read(8).unpack("N2")
|
479
|
+
unless(@deleted[icur-1])
|
480
|
+
@shx_io << [(@shp_io.pos/2).to_i,length].pack("N2")
|
481
|
+
@shp_io << [index,length].pack("N2")
|
482
|
+
@shp_io << @shp_r.read(length * 2)
|
483
|
+
index += 1
|
484
|
+
else
|
485
|
+
@shp_r.seek(length * 2,IO::SEEK_CUR)
|
486
|
+
end
|
487
|
+
end
|
488
|
+
@shp_io.flush
|
489
|
+
@shx_io.flush
|
490
|
+
|
491
|
+
@dbf_r.rewind
|
492
|
+
@dbf_io << @dbf_r.read(@dbf.header_length)
|
493
|
+
icur = 0
|
494
|
+
while(!@dbf_r.eof?)
|
495
|
+
unless(@deleted[icur])
|
496
|
+
@dbf_io << @dbf_r.read(@dbf.record_length)
|
497
|
+
else
|
498
|
+
@dbf_r.seek(@dbf.record_length,IO::SEEK_CUR)
|
499
|
+
end
|
500
|
+
icur += 1
|
501
|
+
end
|
502
|
+
@dbf_io.flush
|
503
|
+
index
|
504
|
+
end
|
505
|
+
|
506
|
+
def commit_finalize(min_x,max_x,min_y,max_y,min_z,max_z,min_m,max_m)
|
507
|
+
#update size in shp and dbf + extent and num records in dbf
|
508
|
+
@shp_io.seek(0,IO::SEEK_END)
|
509
|
+
shp_size = @shp_io.pos / 2
|
510
|
+
@shx_io.seek(0,IO::SEEK_END)
|
511
|
+
shx_size= @shx_io.pos / 2
|
512
|
+
@shp_io.seek(24)
|
513
|
+
@shp_io.write([shp_size].pack("N"))
|
514
|
+
@shx_io.seek(24)
|
515
|
+
@shx_io.write([shx_size].pack("N"))
|
516
|
+
@shp_io.seek(36)
|
517
|
+
@shx_io.seek(36)
|
518
|
+
str = [min_x,min_y,max_x,max_y,min_z,max_z,min_m,max_m].pack("E8")
|
519
|
+
@shp_io.write(str)
|
520
|
+
@shx_io.write(str)
|
521
|
+
|
522
|
+
@dbf_io.seek(4)
|
523
|
+
@dbf_io.write([@dbf.record_count + @added.length - @deleted.length].pack("V"))
|
524
|
+
end
|
525
|
+
|
526
|
+
def build_shp_geometry(geometry)
|
527
|
+
m_range = nil
|
528
|
+
answer =
|
529
|
+
case @shp.shp_type
|
530
|
+
when ShpType::POINT
|
531
|
+
bbox = geometry.bounding_box
|
532
|
+
[geometry.x,geometry.y].pack("E2")
|
533
|
+
when ShpType::POLYLINE
|
534
|
+
str,bbox = create_bbox(geometry)
|
535
|
+
build_polyline(geometry,str)
|
536
|
+
when ShpType::POLYGON
|
537
|
+
str,bbox = create_bbox(geometry)
|
538
|
+
build_polygon(geometry,str)
|
539
|
+
when ShpType::MULTIPOINT
|
540
|
+
str,bbox = create_bbox(geometry)
|
541
|
+
build_multi_point(geometry,str)
|
542
|
+
when ShpType::POINTZ
|
543
|
+
bbox = geometry.bounding_box
|
544
|
+
[geometry.x,geometry.y,geometry.z,geometry.m].pack("E4")
|
545
|
+
when ShpType::POLYLINEZ
|
546
|
+
str,bbox = create_bbox(geometry)
|
547
|
+
m_range = geometry.m_range
|
548
|
+
build_polyline(geometry,str)
|
549
|
+
build_polyline_zm(geometry,:@z,[bbox[0].z,bbox[1].z],str)
|
550
|
+
build_polyline_zm(geometry,:@m,m_range,str)
|
551
|
+
when ShpType::POLYGONZ
|
552
|
+
str,bbox = create_bbox(geometry)
|
553
|
+
m_range = geometry.m_range
|
554
|
+
build_polygon(geometry,str)
|
555
|
+
build_polygon_zm(geometry,:@z,[bbox[0].z,bbox[1].z],str)
|
556
|
+
build_polygon_zm(geometry,:@m,m_range,str)
|
557
|
+
when ShpType::MULTIPOINTZ
|
558
|
+
str,bbox = create_bbox(geometry)
|
559
|
+
m_range = geometry.m_range
|
560
|
+
build_multi_point(geometry,str)
|
561
|
+
build_multi_point_zm(geometry,:@z,[bbox[0].z,bbox[1].z],str)
|
562
|
+
build_multi_point_zm(geometry,:@m,m_range,str)
|
563
|
+
when ShpType::POINTM
|
564
|
+
bbox = geometry.bounding_box
|
565
|
+
[geometry.x,geometry.y,geometry.m].pack("E3")
|
566
|
+
when ShpType::POLYLINEM
|
567
|
+
str,bbox = create_bbox(geometry)
|
568
|
+
m_range = geometry.m_range
|
569
|
+
build_polyline(geometry,str)
|
570
|
+
build_polyline_zm(geometry,:@m,m_range,str)
|
571
|
+
when ShpType::POLYGONM
|
572
|
+
str,bbox = create_bbox(geometry)
|
573
|
+
m_range = geometry.m_range
|
574
|
+
build_polygon(geometry,str)
|
575
|
+
build_polygon_zm(geometry,:@m,m_range,str)
|
576
|
+
when ShpType::MULTIPOINTM
|
577
|
+
str,bbox = create_bbox(geometry)
|
578
|
+
m_range = geometry.m_range
|
579
|
+
build_multi_point(geometry,str)
|
580
|
+
build_multi_point_zm(geometry,:@m,m_range,str)
|
581
|
+
end
|
582
|
+
m_range ||= [0,0]
|
583
|
+
[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]]
|
584
|
+
end
|
585
|
+
|
586
|
+
def create_bbox(geometry)
|
587
|
+
bbox = geometry.bounding_box
|
588
|
+
[[bbox[0].x,bbox[0].y,bbox[1].x,bbox[1].y].pack("E4"),bbox]
|
589
|
+
end
|
590
|
+
|
591
|
+
def build_polyline(geometry,str)
|
592
|
+
if geometry.is_a? GeoRuby::SimpleFeatures::LineString
|
593
|
+
str << [1,geometry.length,0].pack("V3")
|
594
|
+
geometry.each do |point|
|
595
|
+
str << [point.x,point.y].pack("E2")
|
596
|
+
end
|
597
|
+
else
|
598
|
+
#multilinestring
|
599
|
+
str << [geometry.length,geometry.inject(0) {|l, ls| l + ls.length}].pack("V2")
|
600
|
+
str << geometry.inject([0]) {|a,ls| a << (a.last + ls.length)}.pack("V#{geometry.length}") #last element of the previous array is dropped
|
601
|
+
geometry.each do |ls|
|
602
|
+
ls.each do |point|
|
603
|
+
str << [point.x,point.y].pack("E2")
|
604
|
+
end
|
605
|
+
end
|
606
|
+
end
|
607
|
+
str
|
608
|
+
end
|
609
|
+
|
610
|
+
def build_polyline_zm(geometry,zm,range,str)
|
611
|
+
str << range.pack("E2")
|
612
|
+
if geometry.is_a? GeoRuby::SimpleFeatures::LineString
|
613
|
+
geometry.each do |point|
|
614
|
+
str << [point.instance_variable_get(zm)].pack("E")
|
615
|
+
end
|
616
|
+
else
|
617
|
+
#multilinestring
|
618
|
+
geometry.each do |ls|
|
619
|
+
ls.each do |point|
|
620
|
+
str << [point.instance_variable_get(zm)].pack("E")
|
621
|
+
end
|
622
|
+
end
|
623
|
+
end
|
624
|
+
str
|
625
|
+
end
|
626
|
+
|
627
|
+
def build_polygon(geometry,str)
|
628
|
+
if geometry.is_a? GeoRuby::SimpleFeatures::Polygon
|
629
|
+
str << [geometry.length,geometry.inject(0) {|l, lr| l + lr.length}].pack("V2")
|
630
|
+
str << geometry.inject([0]) {|a,lr| a << (a.last + lr.length)}.pack("V#{geometry.length}") #last element of the previous array is dropped
|
631
|
+
geometry.each do |lr|
|
632
|
+
lr.each do |point|
|
633
|
+
str << [point.x,point.y].pack("E2")
|
634
|
+
end
|
635
|
+
end
|
636
|
+
else
|
637
|
+
#multipolygon
|
638
|
+
num_rings = geometry.inject(0) {|l, poly| l + poly.length}
|
639
|
+
str << [num_rings, geometry.inject(0) {|l, poly| l + poly.inject(0) {|l2,lr| l2 + lr.length} }].pack("V2")
|
640
|
+
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
|
641
|
+
geometry.each do |poly|
|
642
|
+
poly.each do |lr|
|
643
|
+
lr.each do |point|
|
644
|
+
str << [point.x,point.y].pack("E2")
|
645
|
+
end
|
646
|
+
end
|
647
|
+
end
|
648
|
+
end
|
649
|
+
str
|
650
|
+
end
|
651
|
+
|
652
|
+
def build_polygon_zm(geometry,zm,range,str)
|
653
|
+
str << range.pack("E2")
|
654
|
+
if geometry.is_a? GeoRuby::SimpleFeatures::Polygon
|
655
|
+
geometry.each do |lr|
|
656
|
+
lr.each do |point|
|
657
|
+
str << [point.instance_variable_get(zm)].pack("E")
|
658
|
+
end
|
659
|
+
end
|
660
|
+
else
|
661
|
+
geometry.each do |poly|
|
662
|
+
poly.each do |lr|
|
663
|
+
lr.each do |point|
|
664
|
+
str << [point.instance_variable_get(zm)].pack("E")
|
665
|
+
end
|
666
|
+
end
|
667
|
+
end
|
668
|
+
end
|
669
|
+
str
|
670
|
+
end
|
671
|
+
|
672
|
+
def build_multi_point(geometry,str)
|
673
|
+
str << [geometry.length].pack("V")
|
674
|
+
geometry.each do |point|
|
675
|
+
str << [point.x,point.y].pack("E2")
|
676
|
+
end
|
677
|
+
str
|
678
|
+
end
|
679
|
+
|
680
|
+
def build_multi_point_zm(geometry,zm,range,str)
|
681
|
+
str << range.pack("E2")
|
682
|
+
geometry.each do |point|
|
683
|
+
str << [point.instance_variable_get(zm)].pack("E")
|
684
|
+
end
|
685
|
+
str
|
686
|
+
end
|
687
|
+
end
|
688
|
+
|
299
689
|
class MalformedShpException < StandardError
|
300
690
|
end
|
301
|
-
|
691
|
+
|
692
|
+
class UnexistantRecordException < StandardError
|
693
|
+
end
|
694
|
+
|
695
|
+
class IncompatibleGeometryException < StandardError
|
696
|
+
end
|
697
|
+
|
698
|
+
class IncompatibleDataException < StandardError
|
699
|
+
end
|
302
700
|
end
|
303
701
|
end
|
@@ -49,6 +49,20 @@ module GeoRuby
|
|
49
49
|
end
|
50
50
|
end
|
51
51
|
|
52
|
+
def m_range
|
53
|
+
if with_m
|
54
|
+
max_m, min_m = -Float::MAX, Float::MAX
|
55
|
+
each do |lr|
|
56
|
+
lrmr = lr.m_range
|
57
|
+
max_m = lrmr[1] if lrmr[1] > max_m
|
58
|
+
min_m = lrmr[0] if lrmr[0] < min_m
|
59
|
+
end
|
60
|
+
[min_m,max_m]
|
61
|
+
else
|
62
|
+
[0,0]
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
52
66
|
#tests the equality of geometry collections
|
53
67
|
def ==(other_collection)
|
54
68
|
if(other_collection.class != self.class)
|
@@ -47,6 +47,19 @@ module GeoRuby
|
|
47
47
|
[Point.from_x_y(min_x,min_y),Point.from_x_y(max_x,max_y)]
|
48
48
|
end
|
49
49
|
end
|
50
|
+
|
51
|
+
def m_range
|
52
|
+
if with_m
|
53
|
+
max_m, min_m = -Float::MAX, Float::MAX
|
54
|
+
each do |point|
|
55
|
+
max_m = point.m if point.m > max_m
|
56
|
+
min_m = point.m if point.m < min_m
|
57
|
+
end
|
58
|
+
[min_m,max_m]
|
59
|
+
else
|
60
|
+
[0,0]
|
61
|
+
end
|
62
|
+
end
|
50
63
|
|
51
64
|
#Tests the equality of line strings
|
52
65
|
def ==(other_line_string)
|
@@ -36,6 +36,20 @@ module GeoRuby
|
|
36
36
|
end
|
37
37
|
end
|
38
38
|
|
39
|
+
def m_range
|
40
|
+
if with_m
|
41
|
+
max_m, min_m = -Float::MAX, Float::MAX
|
42
|
+
each do |lr|
|
43
|
+
lrmr = lr.m_range
|
44
|
+
max_m = lrmr[1] if lrmr[1] > max_m
|
45
|
+
min_m = lrmr[0] if lrmr[0] < min_m
|
46
|
+
end
|
47
|
+
[min_m,max_m]
|
48
|
+
else
|
49
|
+
[0,0]
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
39
53
|
#tests for other equality. The SRID is not taken into account.
|
40
54
|
def ==(other_polygon)
|
41
55
|
if other_polygon.class != self.class or
|
data/rakefile.rb
CHANGED
@@ -24,7 +24,7 @@ spec = Gem::Specification::new do |s|
|
|
24
24
|
s.platform = Gem::Platform::RUBY
|
25
25
|
|
26
26
|
s.name = 'GeoRuby'
|
27
|
-
s.version = "1.
|
27
|
+
s.version = "1.3.0"
|
28
28
|
s.summary = "Ruby data holder for OGC Simple Features"
|
29
29
|
s.description = <<EOF
|
30
30
|
GeoRuby is intended as a holder for data returned from PostGIS and MySQL Spatial queries. The data model roughly follows the OGC "Simple Features for SQL" specification (see www.opengis.org/docs/99-049.pdf), although without any kind of advanced functionalities (such as geometric operators or reprojections)
|
Binary file
|
Binary file
|
Binary file
|
data/test/data/point.dbf
CHANGED
Binary file
|
Binary file
|
Binary file
|
Binary file
|
@@ -0,0 +1,150 @@
|
|
1
|
+
$:.unshift(File.dirname(__FILE__) + '/../lib')
|
2
|
+
|
3
|
+
require 'geo_ruby'
|
4
|
+
require 'test/unit'
|
5
|
+
|
6
|
+
include GeoRuby::SimpleFeatures
|
7
|
+
include GeoRuby::Shp4r
|
8
|
+
|
9
|
+
class TestShp < Test::Unit::TestCase
|
10
|
+
|
11
|
+
def cp_all_shp(file1,file2)
|
12
|
+
FileUtils.copy(file1 + ".shp",file2 + ".shp")
|
13
|
+
FileUtils.copy(file1 + ".shx",file2 + ".shx")
|
14
|
+
FileUtils.copy(file1 + ".dbf",file2 + ".dbf")
|
15
|
+
end
|
16
|
+
|
17
|
+
def rm_all_shp(file)
|
18
|
+
FileUtils.rm(file + ".shp")
|
19
|
+
FileUtils.rm(file + ".shx")
|
20
|
+
FileUtils.rm(file + ".dbf")
|
21
|
+
end
|
22
|
+
|
23
|
+
def test_point
|
24
|
+
cp_all_shp(File.dirname(__FILE__) + '/data/point',
|
25
|
+
File.dirname(__FILE__) + '/data/point2')
|
26
|
+
shpfile = ShpFile.open(File.dirname(__FILE__) + '/data/point2.shp')
|
27
|
+
|
28
|
+
shpfile.transaction do |tr|
|
29
|
+
assert(tr.instance_of?(ShpTransaction))
|
30
|
+
tr.add(ShpRecord.new(Point.from_x_y(123.4,123.4),'Hoyoyo' => 5))
|
31
|
+
tr.add(ShpRecord.new(Point.from_x_y(-16.67,16.41),'Hoyoyo' => -7))
|
32
|
+
tr.delete(1)
|
33
|
+
end
|
34
|
+
|
35
|
+
assert_equal(3,shpfile.record_count)
|
36
|
+
|
37
|
+
shpfile.close
|
38
|
+
rm_all_shp(File.dirname(__FILE__) + '/data/point2')
|
39
|
+
end
|
40
|
+
|
41
|
+
def test_linestring
|
42
|
+
cp_all_shp(File.dirname(__FILE__) + '/data/polyline',
|
43
|
+
File.dirname(__FILE__) + '/data/polyline2')
|
44
|
+
|
45
|
+
shpfile = ShpFile.open(File.dirname(__FILE__) + '/data/polyline2.shp')
|
46
|
+
|
47
|
+
shpfile.transaction do |tr|
|
48
|
+
assert(tr.instance_of?(ShpTransaction))
|
49
|
+
tr.add(ShpRecord.new(LineString.from_coordinates([[123.4,123.4],[45.6,12.3]]),'Chipoto' => 5.6778))
|
50
|
+
tr.add(ShpRecord.new(LineString.from_coordinates([[23.4,13.4],[45.6,12.3],[12,-67]]),'Chipoto' => -7.1))
|
51
|
+
tr.delete(0)
|
52
|
+
end
|
53
|
+
|
54
|
+
assert_equal(2,shpfile.record_count)
|
55
|
+
shpfile.close
|
56
|
+
#rm_all_shp(File.dirname(__FILE__) + '/data/polyline2')
|
57
|
+
end
|
58
|
+
|
59
|
+
def test_polygon
|
60
|
+
cp_all_shp(File.dirname(__FILE__) + '/data/polygon',
|
61
|
+
File.dirname(__FILE__) + '/data/polygon2')
|
62
|
+
shpfile = ShpFile.open(File.dirname(__FILE__) + '/data/polygon2.shp')
|
63
|
+
|
64
|
+
shpfile.transaction do |tr|
|
65
|
+
assert(tr.instance_of?(ShpTransaction))
|
66
|
+
tr.delete(0)
|
67
|
+
tr.add(ShpRecord.new(Polygon.from_coordinates([[[0,0],[40,0],[40,40],[0,40],[0,0]],[[10,10],[10,20],[20,20],[10,10]]]),'Hello' => "oook"))
|
68
|
+
end
|
69
|
+
|
70
|
+
assert_equal(1,shpfile.record_count)
|
71
|
+
|
72
|
+
shpfile.close
|
73
|
+
rm_all_shp(File.dirname(__FILE__) + '/data/polygon2')
|
74
|
+
end
|
75
|
+
|
76
|
+
def test_multipoint
|
77
|
+
cp_all_shp(File.dirname(__FILE__) + '/data/multipoint',
|
78
|
+
File.dirname(__FILE__) + '/data/multipoint2')
|
79
|
+
shpfile = ShpFile.open(File.dirname(__FILE__) + '/data/multipoint2.shp')
|
80
|
+
|
81
|
+
shpfile.transaction do |tr|
|
82
|
+
assert(tr.instance_of?(ShpTransaction))
|
83
|
+
tr.add(ShpRecord.new(MultiPoint.from_coordinates([[45.6,-45.1],[12.4,98.2],[51.2,-0.12],[156.12345,56.109]]),'Hello' => 5,"Hoyoyo" => "AEZAE"))
|
84
|
+
end
|
85
|
+
|
86
|
+
assert_equal(2,shpfile.record_count)
|
87
|
+
|
88
|
+
shpfile.close
|
89
|
+
rm_all_shp(File.dirname(__FILE__) + '/data/multipoint2')
|
90
|
+
end
|
91
|
+
|
92
|
+
def test_multi_polygon
|
93
|
+
cp_all_shp(File.dirname(__FILE__) + '/data/polygon',
|
94
|
+
File.dirname(__FILE__) + '/data/polygon4')
|
95
|
+
|
96
|
+
shpfile = ShpFile.open(File.dirname(__FILE__) + '/data/polygon4.shp')
|
97
|
+
|
98
|
+
shpfile.transaction do |tr|
|
99
|
+
assert(tr.instance_of?(ShpTransaction))
|
100
|
+
tr.add(ShpRecord.new(MultiPolygon.from_polygons([Polygon.from_coordinates([[[0,0],[40,0],[40,40],[0,40],[0,0]],[[10,10],[10,20],[20,20],[10,10]]])]),'Hello' => "oook"))
|
101
|
+
end
|
102
|
+
|
103
|
+
assert_equal(2,shpfile.record_count)
|
104
|
+
|
105
|
+
shpfile.close
|
106
|
+
|
107
|
+
rm_all_shp(File.dirname(__FILE__) + '/data/polygon4')
|
108
|
+
end
|
109
|
+
|
110
|
+
def test_rollback
|
111
|
+
cp_all_shp(File.dirname(__FILE__) + '/data/polygon',
|
112
|
+
File.dirname(__FILE__) + '/data/polygon5')
|
113
|
+
|
114
|
+
shpfile = ShpFile.open(File.dirname(__FILE__) + '/data/polygon5.shp')
|
115
|
+
|
116
|
+
shpfile.transaction do |tr|
|
117
|
+
assert(tr.instance_of?(ShpTransaction))
|
118
|
+
tr.add(ShpRecord.new(MultiPolygon.from_polygons([Polygon.from_coordinates([[[0,0],[40,0],[40,40],[0,40],[0,0]],[[10,10],[10,20],[20,20],[10,10]]])]),'Hello' => "oook"))
|
119
|
+
tr.rollback
|
120
|
+
end
|
121
|
+
assert_equal(1,shpfile.record_count)
|
122
|
+
|
123
|
+
shpfile.close
|
124
|
+
|
125
|
+
rm_all_shp(File.dirname(__FILE__) + '/data/polygon5')
|
126
|
+
|
127
|
+
end
|
128
|
+
|
129
|
+
def test_creation
|
130
|
+
shpfile = ShpFile.create(File.dirname(__FILE__) + '/data/point3.shp',ShpType::POINT,[Dbf::Field.new("Hoyoyo","C",10,0)])
|
131
|
+
shpfile.transaction do |tr|
|
132
|
+
tr.add(ShpRecord.new(Point.from_x_y(123,123.4),'Hoyoyo' => "HJHJJ"))
|
133
|
+
end
|
134
|
+
assert(1,shpfile.record_count)
|
135
|
+
shpfile.close
|
136
|
+
rm_all_shp(File.dirname(__FILE__) + '/data/point3')
|
137
|
+
end
|
138
|
+
|
139
|
+
def test_creation_multipoint
|
140
|
+
shpfile = ShpFile.create(File.dirname(__FILE__) + '/data/multipoint3.shp',ShpType::MULTIPOINT,[Dbf::Field.new("Hoyoyo","C",10),Dbf::Field.new("Hello","N",10)])
|
141
|
+
shpfile.transaction do |tr|
|
142
|
+
tr.add(ShpRecord.new(MultiPoint.from_coordinates([[123,123.4],[345,12.2]]),'Hoyoyo' => "HJHJJ","Hello" => 5))
|
143
|
+
end
|
144
|
+
assert(1,shpfile.record_count)
|
145
|
+
shpfile.close
|
146
|
+
rm_all_shp(File.dirname(__FILE__) + '/data/multipoint3')
|
147
|
+
end
|
148
|
+
|
149
|
+
|
150
|
+
end
|
metadata
CHANGED
@@ -3,8 +3,8 @@ rubygems_version: 0.9.0
|
|
3
3
|
specification_version: 1
|
4
4
|
name: GeoRuby
|
5
5
|
version: !ruby/object:Gem::Version
|
6
|
-
version: 1.
|
7
|
-
date: 2007-
|
6
|
+
version: 1.3.0
|
7
|
+
date: 2007-08-26 00:00:00 +02:00
|
8
8
|
summary: Ruby data holder for OGC Simple Features
|
9
9
|
require_paths:
|
10
10
|
- lib
|
@@ -51,19 +51,26 @@ files:
|
|
51
51
|
- test/test_ewkt_parser.rb
|
52
52
|
- test/test_georss_kml.rb
|
53
53
|
- test/test_shp.rb
|
54
|
+
- test/test_shp_write.rb
|
54
55
|
- test/test_simple_features.rb
|
55
56
|
- README
|
56
57
|
- MIT-LICENSE
|
57
58
|
- rakefile.rb
|
59
|
+
- test/data/multipoint.shp
|
58
60
|
- test/data/point.shp
|
59
61
|
- test/data/polygon.shp
|
60
62
|
- test/data/polyline.shp
|
63
|
+
- test/data/polyline2.shp
|
64
|
+
- test/data/multipoint.dbf
|
61
65
|
- test/data/point.dbf
|
62
66
|
- test/data/polygon.dbf
|
63
67
|
- test/data/polyline.dbf
|
68
|
+
- test/data/polyline2.dbf
|
69
|
+
- test/data/multipoint.shx
|
64
70
|
- test/data/point.shx
|
65
71
|
- test/data/polygon.shx
|
66
72
|
- test/data/polyline.shx
|
73
|
+
- test/data/polyline2.shx
|
67
74
|
- tools/db.yml
|
68
75
|
- tools/lib/spatial_adapter/test/db/database_mysql.yml
|
69
76
|
- tools/lib/spatial_adapter/test/db/database_postgis.yml
|
@@ -99,6 +106,7 @@ test_files:
|
|
99
106
|
- test/test_ewkt_parser.rb
|
100
107
|
- test/test_georss_kml.rb
|
101
108
|
- test/test_shp.rb
|
109
|
+
- test/test_shp_write.rb
|
102
110
|
- test/test_simple_features.rb
|
103
111
|
rdoc_options:
|
104
112
|
- --main
|