GeoRuby 1.2.4 → 1.3.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|