ppe-georuby 1.7.2
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +6 -0
- data/History.txt +4 -0
- data/LICENSE +21 -0
- data/README.txt +118 -0
- data/Rakefile +48 -0
- data/VERSION +1 -0
- data/lib/geo_ruby.rb +22 -0
- data/lib/geo_ruby/gpx.rb +1 -0
- data/lib/geo_ruby/gpx4r/gpx.rb +117 -0
- data/lib/geo_ruby/shp.rb +1 -0
- data/lib/geo_ruby/shp4r/dbf.rb +41 -0
- data/lib/geo_ruby/shp4r/shp.rb +697 -0
- data/lib/geo_ruby/simple_features/envelope.rb +167 -0
- data/lib/geo_ruby/simple_features/ewkb_parser.rb +216 -0
- data/lib/geo_ruby/simple_features/ewkt_parser.rb +336 -0
- data/lib/geo_ruby/simple_features/geometry.rb +228 -0
- data/lib/geo_ruby/simple_features/geometry_collection.rb +136 -0
- data/lib/geo_ruby/simple_features/geometry_factory.rb +81 -0
- data/lib/geo_ruby/simple_features/georss_parser.rb +135 -0
- data/lib/geo_ruby/simple_features/helper.rb +18 -0
- data/lib/geo_ruby/simple_features/line_string.rb +206 -0
- data/lib/geo_ruby/simple_features/linear_ring.rb +12 -0
- data/lib/geo_ruby/simple_features/multi_line_string.rb +51 -0
- data/lib/geo_ruby/simple_features/multi_point.rb +46 -0
- data/lib/geo_ruby/simple_features/multi_polygon.rb +52 -0
- data/lib/geo_ruby/simple_features/point.rb +364 -0
- data/lib/geo_ruby/simple_features/polygon.rb +157 -0
- data/ppe-georuby.gemspec +133 -0
- data/script/console +10 -0
- data/script/destroy +14 -0
- data/script/generate +14 -0
- data/script/txt2html +82 -0
- data/spec/data/gpx/fells_loop.gpx +1077 -0
- data/spec/data/gpx/long.gpx +1642 -0
- data/spec/data/gpx/long.kml +31590 -0
- data/spec/data/gpx/long.nmea +2220 -0
- data/spec/data/gpx/short.gpx +13634 -0
- data/spec/data/gpx/short.kml +130 -0
- data/spec/data/gpx/tracktreks.gpx +706 -0
- data/spec/data/multipoint.dbf +0 -0
- data/spec/data/multipoint.shp +0 -0
- data/spec/data/multipoint.shx +0 -0
- data/spec/data/point.dbf +0 -0
- data/spec/data/point.shp +0 -0
- data/spec/data/point.shx +0 -0
- data/spec/data/polygon.dbf +0 -0
- data/spec/data/polygon.shp +0 -0
- data/spec/data/polygon.shx +0 -0
- data/spec/data/polyline.dbf +0 -0
- data/spec/data/polyline.shp +0 -0
- data/spec/data/polyline.shx +0 -0
- data/spec/geo_ruby/gpx4r/gpx_spec.rb +106 -0
- data/spec/geo_ruby/shp4r/shp_spec.rb +240 -0
- data/spec/geo_ruby/simple_features/envelope_spec.rb +45 -0
- data/spec/geo_ruby/simple_features/ewkb_parser_spec.rb +158 -0
- data/spec/geo_ruby/simple_features/ewkt_parser_spec.rb +179 -0
- data/spec/geo_ruby/simple_features/geometry_collection_spec.rb +55 -0
- data/spec/geo_ruby/simple_features/geometry_factory_spec.rb +11 -0
- data/spec/geo_ruby/simple_features/geometry_spec.rb +32 -0
- data/spec/geo_ruby/simple_features/georss_parser_spec.rb +218 -0
- data/spec/geo_ruby/simple_features/line_string_spec.rb +245 -0
- data/spec/geo_ruby/simple_features/linear_ring_spec.rb +14 -0
- data/spec/geo_ruby/simple_features/multi_line_string_spec.rb +54 -0
- data/spec/geo_ruby/simple_features/multi_point_spec.rb +35 -0
- data/spec/geo_ruby/simple_features/multi_polygon_spec.rb +50 -0
- data/spec/geo_ruby/simple_features/point_spec.rb +356 -0
- data/spec/geo_ruby/simple_features/polygon_spec.rb +108 -0
- data/spec/geo_ruby_spec.rb +27 -0
- data/spec/spec.opts +6 -0
- data/spec/spec_helper.rb +65 -0
- metadata +162 -0
data/.gitignore
ADDED
data/History.txt
ADDED
data/LICENSE
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
GeoRuby License
|
2
|
+
|
3
|
+
Copyright (c) 2006 Guilhem Vellut <guilhem.vellut+georuby@gmail.com>
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
6
|
+
of this software and associated documentation files (the "Software"), to
|
7
|
+
deal in the Software without restriction, including without limitation the
|
8
|
+
rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
|
9
|
+
permit persons to whom the Software is furnished to do so, subject to the
|
10
|
+
following conditions:
|
11
|
+
|
12
|
+
The above copyright notice and this permission notice shall be included in
|
13
|
+
all copies or substantial portions of the Software.
|
14
|
+
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
|
16
|
+
OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
|
18
|
+
THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR
|
19
|
+
OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
|
20
|
+
ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
|
21
|
+
OTHER DEALINGS IN THE SOFTWARE.
|
data/README.txt
ADDED
@@ -0,0 +1,118 @@
|
|
1
|
+
= GeoRuby
|
2
|
+
|
3
|
+
This is a fork of GeoRuby. 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
|
+
|
5
|
+
|
6
|
+
=== Available data types
|
7
|
+
|
8
|
+
The following geometric data types are provided :
|
9
|
+
- Point
|
10
|
+
- Line string
|
11
|
+
- Linear ring
|
12
|
+
- Polygon
|
13
|
+
- Multi point
|
14
|
+
- Multi line string
|
15
|
+
- Multi polygon
|
16
|
+
- Geometry collection
|
17
|
+
|
18
|
+
They can be in 2D, 3DZ, 3DM, and 4D.
|
19
|
+
|
20
|
+
On top of this an Envelope class is available, to contain the bounding box of a geometry.
|
21
|
+
|
22
|
+
|
23
|
+
=== Input and output
|
24
|
+
|
25
|
+
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.
|
26
|
+
|
27
|
+
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.
|
28
|
+
|
29
|
+
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.
|
30
|
+
|
31
|
+
|
32
|
+
=== SHP reading et writing
|
33
|
+
|
34
|
+
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.
|
35
|
+
|
36
|
+
Here is an example of Shapefile reading, that goes through all the geometries in a file and disaply the values of the attributes :
|
37
|
+
|
38
|
+
require 'geo_ruby/shp'
|
39
|
+
|
40
|
+
ShpFile.open(shpfile) do |shp|
|
41
|
+
shp.each do |shape|
|
42
|
+
geom = shape.geometry #a GeoRuby SimpleFeature
|
43
|
+
att_data = shape.data #a Hash
|
44
|
+
shp.fields.each do |field|
|
45
|
+
puts att_data[field.name]
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
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).
|
51
|
+
|
52
|
+
Here is an example of how to create a new Shapefile with 2 fields :
|
53
|
+
|
54
|
+
shpfile = ShpFile.create('hello.shp',ShpType::POINT,[Dbf::Field.new("Hoyoyo","C",10),Dbf::Field.new("Boyoul","N",10,0)])
|
55
|
+
|
56
|
+
The file is then open for reading and writing.
|
57
|
+
|
58
|
+
Here is an example of how to write to a shapefile (created or not with GeoRuby) :
|
59
|
+
|
60
|
+
shpfile = ShpFile.open('places.shp')
|
61
|
+
shpfile.transaction do |tr|
|
62
|
+
tr.add(ShpRecord.new(Point.from_x_y(123.4,123.4),'Hoyoyo' => "AEZ",'Bouyoul' => 45))
|
63
|
+
tr.update(4,ShpRecord.new(Point.from_x_y(-16.67,16.41),'Hoyoyo' => "EatMe",'Bouyoul' => 42))
|
64
|
+
tr.delete(1)
|
65
|
+
end
|
66
|
+
shpfile.close
|
67
|
+
|
68
|
+
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.
|
69
|
+
|
70
|
+
Also currently, error reporting is minimal and it has not been tested that thoroughly so caveat emptor and backup before performing any destructive operation.
|
71
|
+
|
72
|
+
|
73
|
+
=== GPX Reading
|
74
|
+
|
75
|
+
You can read and convert GPX Files to LineString/Polygon:
|
76
|
+
|
77
|
+
gpxfile = GpxFile.open(tour.gpx')
|
78
|
+
gpxfile.as_line_string
|
79
|
+
=> GeoRuby::SimpleFeatures::LineString..
|
80
|
+
|
81
|
+
|
82
|
+
=== Installation
|
83
|
+
|
84
|
+
To install the latest version, just type :
|
85
|
+
|
86
|
+
gem install nofxx-georuby
|
87
|
+
|
88
|
+
|
89
|
+
=== Changes since the last version
|
90
|
+
|
91
|
+
- Writing of ESRI shapefiles
|
92
|
+
- Reading of ESRI shapefiles
|
93
|
+
- Addition of a small tool to import spatial features in MySQL and PostGIS from a SHP file
|
94
|
+
|
95
|
+
|
96
|
+
=== Coming in the next versions
|
97
|
+
|
98
|
+
- Schema modification of existing shapefiles
|
99
|
+
- More error reporting when writing shapefiles
|
100
|
+
- More tests on writing shapefiles ; tests on real-world shapefiles
|
101
|
+
- Better shp2sql import tool
|
102
|
+
- Documentation
|
103
|
+
|
104
|
+
|
105
|
+
=== Acknowledgement
|
106
|
+
|
107
|
+
The SHP reading part uses the DBF library (http://rubyforge.org/projects/dbf/) by Keith Morrison (http://infused.org).
|
108
|
+
Thanks also to Pramukta Kumar and Pete Schwamb for their contributions.
|
109
|
+
|
110
|
+
|
111
|
+
=== License
|
112
|
+
|
113
|
+
GeoRuby is released under the MIT license.
|
114
|
+
|
115
|
+
=== Support
|
116
|
+
|
117
|
+
Any questions, enhancement proposals, bug notifications or corrections
|
118
|
+
can be sent to mailto:guilhem.vellut@gmail.com.
|
data/Rakefile
ADDED
@@ -0,0 +1,48 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'rake'
|
3
|
+
|
4
|
+
begin
|
5
|
+
require 'jeweler'
|
6
|
+
Jeweler::Tasks.new do |gem|
|
7
|
+
gem.name = "nofxx-georuby"
|
8
|
+
gem.summary = "Ruby data holder for OGC Simple Features"
|
9
|
+
gem.description = "GeoRuby provides geometric data types from the OGC 'Simple Features' specification."
|
10
|
+
gem.email = "x@nofxx.com"
|
11
|
+
gem.homepage = "http://github.com/nofxx/georuby"
|
12
|
+
gem.authors = ["Guilhem Vellut", "Marcos Piccinini"]
|
13
|
+
gem.add_development_dependency "rspec", ">= 1.2.9"
|
14
|
+
gem.add_development_dependency "dbf", ">= 1.1.2"
|
15
|
+
end
|
16
|
+
rescue LoadError
|
17
|
+
puts "Jeweler not available. Install it with: sudo gem install technicalpickles-jeweler -s http://gems.github.com"
|
18
|
+
end
|
19
|
+
|
20
|
+
require 'spec/rake/spectask'
|
21
|
+
Spec::Rake::SpecTask.new(:spec) do |spec|
|
22
|
+
spec.libs << 'lib' << 'spec'
|
23
|
+
spec.spec_files = FileList['spec/**/*_spec.rb']
|
24
|
+
end
|
25
|
+
|
26
|
+
Spec::Rake::SpecTask.new(:rcov) do |spec|
|
27
|
+
spec.libs << 'lib' << 'spec'
|
28
|
+
spec.pattern = 'spec/**/*_spec.rb'
|
29
|
+
spec.rcov = true
|
30
|
+
end
|
31
|
+
|
32
|
+
task :default => :spec
|
33
|
+
|
34
|
+
require 'rake/rdoctask'
|
35
|
+
Rake::RDocTask.new do |rdoc|
|
36
|
+
if File.exist?('VERSION.yml')
|
37
|
+
config = YAML.load(File.read('VERSION.yml'))
|
38
|
+
version = "#{config[:major]}.#{config[:minor]}.#{config[:patch]}"
|
39
|
+
else
|
40
|
+
version = ""
|
41
|
+
end
|
42
|
+
|
43
|
+
rdoc.rdoc_dir = 'rdoc'
|
44
|
+
rdoc.title = "geo_ruby #{version}"
|
45
|
+
rdoc.rdoc_files.include('README*')
|
46
|
+
rdoc.rdoc_files.include('lib/**/*.rb')
|
47
|
+
end
|
48
|
+
|
data/VERSION
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
1.7.2
|
data/lib/geo_ruby.rb
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
$:.unshift(File.dirname(__FILE__)) unless
|
2
|
+
$:.include?(File.dirname(__FILE__)) || $:.include?(File.expand_path(File.dirname(__FILE__)))
|
3
|
+
|
4
|
+
require 'geo_ruby/simple_features/helper'
|
5
|
+
require 'geo_ruby/simple_features/ewkt_parser'
|
6
|
+
require 'geo_ruby/simple_features/ewkb_parser'
|
7
|
+
require 'geo_ruby/simple_features/geometry'
|
8
|
+
require 'geo_ruby/simple_features/point'
|
9
|
+
require 'geo_ruby/simple_features/line_string'
|
10
|
+
require 'geo_ruby/simple_features/linear_ring'
|
11
|
+
require 'geo_ruby/simple_features/polygon'
|
12
|
+
require 'geo_ruby/simple_features/multi_point'
|
13
|
+
require 'geo_ruby/simple_features/multi_line_string'
|
14
|
+
require 'geo_ruby/simple_features/multi_polygon'
|
15
|
+
require 'geo_ruby/simple_features/geometry_collection'
|
16
|
+
require 'geo_ruby/simple_features/envelope'
|
17
|
+
require 'geo_ruby/simple_features/geometry_factory'
|
18
|
+
require 'geo_ruby/simple_features/georss_parser'
|
19
|
+
|
20
|
+
# Include if you need
|
21
|
+
# require 'geo_ruby/shp4r/shp'
|
22
|
+
# require 'geo_ruby/gpx4r/gpx'
|
data/lib/geo_ruby/gpx.rb
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
require 'geo_ruby/gpx4r/gpx'
|
@@ -0,0 +1,117 @@
|
|
1
|
+
require "rubygems"
|
2
|
+
require "nokogiri"
|
3
|
+
|
4
|
+
module GeoRuby
|
5
|
+
module Gpx4r
|
6
|
+
|
7
|
+
#An interface to GPX files
|
8
|
+
class GpxFile
|
9
|
+
attr_reader :record_count, :file_root #:xmin, :ymin, :xmax, :ymax, :zmin, :zmax, :mmin, :mmax, :file_length
|
10
|
+
|
11
|
+
include Enumerable
|
12
|
+
|
13
|
+
# Opens a GPX file. Both "abc.shp" and "abc" are accepted.
|
14
|
+
def initialize(file, *opts) #with_z = true, with_m = true)
|
15
|
+
@file_root = file.gsub(/\.gpx$/i,"")
|
16
|
+
raise MalformedGpxException.new("Missing GPX File") unless
|
17
|
+
File.exists? @file_root + ".gpx"
|
18
|
+
@points, @envelope = [], nil
|
19
|
+
@gpx = File.open(@file_root + ".gpx", "rb")
|
20
|
+
opt = opts.inject({}) { |o, h| h.merge(o) }
|
21
|
+
parse_file(opt[:with_z], opt[:with_m])
|
22
|
+
end
|
23
|
+
|
24
|
+
#force the reopening of the files compsing the shp. Close before calling this.
|
25
|
+
def reload!
|
26
|
+
initialize(@file_root)
|
27
|
+
end
|
28
|
+
|
29
|
+
#opens a GPX "file". If a block is given, the GpxFile object is yielded to it and is closed upon return. Else a call to <tt>open</tt> is equivalent to <tt>GpxFile.new(...)</tt>.
|
30
|
+
def self.open(file, *opts)
|
31
|
+
gpxfile = GpxFile.new(file, *opts)
|
32
|
+
if block_given?
|
33
|
+
yield gpxfile
|
34
|
+
gpxfile.close
|
35
|
+
else
|
36
|
+
gpxfile
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
#Closes a gpxfile
|
41
|
+
def close
|
42
|
+
@gpx.close
|
43
|
+
end
|
44
|
+
|
45
|
+
#Tests if the file has no record
|
46
|
+
def empty?
|
47
|
+
record_count == 0
|
48
|
+
end
|
49
|
+
|
50
|
+
#Goes through each record
|
51
|
+
def each
|
52
|
+
(0...record_count).each do |i|
|
53
|
+
yield get_record(i)
|
54
|
+
end
|
55
|
+
end
|
56
|
+
alias :each_record :each
|
57
|
+
|
58
|
+
#Returns record +i+
|
59
|
+
def [](i)
|
60
|
+
get_record(i)
|
61
|
+
end
|
62
|
+
|
63
|
+
#Returns all the records
|
64
|
+
def records
|
65
|
+
@points
|
66
|
+
end
|
67
|
+
|
68
|
+
# Return the GPX file as LineString
|
69
|
+
def as_line_string
|
70
|
+
LineString.from_points(@points)
|
71
|
+
end
|
72
|
+
alias :as_polyline :as_line_string
|
73
|
+
|
74
|
+
# Return the GPX file as a Polygon
|
75
|
+
# If the GPX isn't closed, a line from the first
|
76
|
+
# to the last point will be created to close it.
|
77
|
+
def as_polygon
|
78
|
+
Polygon.from_points([@points[0] == @points[-1] ? @points : @points.push(@points[0].clone)])
|
79
|
+
end
|
80
|
+
|
81
|
+
# Return GPX Envelope
|
82
|
+
def envelope
|
83
|
+
@envelope ||= as_polygon.envelope
|
84
|
+
end
|
85
|
+
|
86
|
+
private
|
87
|
+
|
88
|
+
def get_record(i)
|
89
|
+
@points[i]
|
90
|
+
end
|
91
|
+
|
92
|
+
# wpt => waypoint => TODO?
|
93
|
+
# rte(pt) => route
|
94
|
+
# trk(pt) => track /
|
95
|
+
def parse_file(with_z, with_m)
|
96
|
+
data = @gpx.read
|
97
|
+
@file_mode = data =~ /trkpt/ ? "//trkpt" : "//rtept"
|
98
|
+
Nokogiri.HTML(data).search(@file_mode).each do |tp|
|
99
|
+
z = z.inner_text.to_i if with_z && z = tp.at("ele")
|
100
|
+
m = m.inner_text if with_m && m = tp.at("time")
|
101
|
+
@points << Point.from_coordinates([tp["lon"].to_f, tp["lat"].to_f, z, m],nil,with_z, with_m)
|
102
|
+
end
|
103
|
+
close
|
104
|
+
@record_count = @points.length
|
105
|
+
rescue => e
|
106
|
+
raise MalformedGpxException.new("Bad GPX. Error: #{e}")
|
107
|
+
# trackpoint.at("gpxdata:hr").nil? # heartrate
|
108
|
+
end
|
109
|
+
|
110
|
+
end
|
111
|
+
|
112
|
+
class MalformedGpxException < StandardError
|
113
|
+
end
|
114
|
+
|
115
|
+
end
|
116
|
+
|
117
|
+
end
|
data/lib/geo_ruby/shp.rb
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
require 'geo_ruby/shp4r/shp'
|
@@ -0,0 +1,41 @@
|
|
1
|
+
# Uses the dbf lib, Copyright 2006 Keith Morrison (http://infused.org)
|
2
|
+
# Modified to work as external gem now
|
3
|
+
require 'rubygems'
|
4
|
+
begin
|
5
|
+
require 'dbf'
|
6
|
+
rescue LoadError
|
7
|
+
puts "Unable to find gem 'dbf'. Please install."
|
8
|
+
end
|
9
|
+
|
10
|
+
module GeoRuby
|
11
|
+
module Shp4r
|
12
|
+
Dbf = DBF
|
13
|
+
|
14
|
+
module Dbf
|
15
|
+
class Record
|
16
|
+
def [](v)
|
17
|
+
attributes[v.downcase]
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
class Field < Column
|
22
|
+
def initialize(name, type, length, decimal = 0)
|
23
|
+
super(name, type, length, decimal)
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
class Reader < Table
|
28
|
+
alias_method :fields, :columns
|
29
|
+
def header_length
|
30
|
+
@columns_count
|
31
|
+
end
|
32
|
+
|
33
|
+
def self.open(f)
|
34
|
+
new(f)
|
35
|
+
end
|
36
|
+
|
37
|
+
def close(); nil; end
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
@@ -0,0 +1,697 @@
|
|
1
|
+
require 'date'
|
2
|
+
require 'fileutils' if !defined?(FileUtils)
|
3
|
+
require File.dirname(__FILE__) + '/dbf'
|
4
|
+
|
5
|
+
|
6
|
+
module GeoRuby
|
7
|
+
module Shp4r
|
8
|
+
|
9
|
+
#Enumerates all the types of SHP geometries. The MULTIPATCH one is the only one not currently supported by Geo_ruby.
|
10
|
+
module ShpType
|
11
|
+
NULL_SHAPE = 0
|
12
|
+
POINT = 1
|
13
|
+
POLYLINE = 3
|
14
|
+
POLYGON = 5
|
15
|
+
MULTIPOINT = 8
|
16
|
+
POINTZ = 11
|
17
|
+
POLYLINEZ = 13
|
18
|
+
POLYGONZ = 15
|
19
|
+
MULTIPOINTZ = 18
|
20
|
+
POINTM = 21
|
21
|
+
POLYLINEM = 23
|
22
|
+
POLYGONM = 25
|
23
|
+
MULTIPOINTM = 28
|
24
|
+
end
|
25
|
+
|
26
|
+
#An interface to an ESRI shapefile (actually 3 files : shp, shx and dbf). Currently supports only the reading of geometries.
|
27
|
+
class ShpFile
|
28
|
+
attr_reader :shp_type, :record_count, :xmin, :ymin, :xmax, :ymax, :zmin, :zmax, :mmin, :mmax, :file_root, :file_length
|
29
|
+
|
30
|
+
include Enumerable
|
31
|
+
|
32
|
+
#Opens a SHP file. Both "abc.shp" and "abc" are accepted. The files "abc.shp", "abc.shx" and "abc.dbf" must be present
|
33
|
+
def initialize(file)
|
34
|
+
#strip the shp out of the file if present
|
35
|
+
@file_root = file.gsub(/.shp$/i,"")
|
36
|
+
#check existence of shp, dbf and shx files
|
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}")
|
39
|
+
end
|
40
|
+
|
41
|
+
@dbf = Dbf::Reader.open(@file_root + ".dbf")
|
42
|
+
@shx = File.open(@file_root + ".shx","rb")
|
43
|
+
@shp = File.open(@file_root + ".shp","rb")
|
44
|
+
read_index
|
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
|
51
|
+
|
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>.
|
53
|
+
def self.open(file)
|
54
|
+
shpfile = ShpFile.new(file)
|
55
|
+
if block_given?
|
56
|
+
yield shpfile
|
57
|
+
shpfile.close
|
58
|
+
else
|
59
|
+
shpfile
|
60
|
+
end
|
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
|
86
|
+
|
87
|
+
#Closes a shapefile
|
88
|
+
def close
|
89
|
+
@dbf.close
|
90
|
+
@shx.close
|
91
|
+
@shp.close
|
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
|
108
|
+
|
109
|
+
#return the description of data fields
|
110
|
+
def fields
|
111
|
+
@dbf.fields
|
112
|
+
end
|
113
|
+
|
114
|
+
#Tests if the file has no record
|
115
|
+
def empty?
|
116
|
+
record_count == 0
|
117
|
+
end
|
118
|
+
|
119
|
+
#Goes through each record
|
120
|
+
def each
|
121
|
+
(0...record_count).each do |i|
|
122
|
+
yield get_record(i)
|
123
|
+
end
|
124
|
+
end
|
125
|
+
alias :each_record :each
|
126
|
+
|
127
|
+
#Returns record +i+
|
128
|
+
def [](i)
|
129
|
+
get_record(i)
|
130
|
+
end
|
131
|
+
|
132
|
+
#Returns all the records
|
133
|
+
def records
|
134
|
+
Array.new(record_count) do |i|
|
135
|
+
get_record(i)
|
136
|
+
end
|
137
|
+
end
|
138
|
+
|
139
|
+
private
|
140
|
+
def read_index
|
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
|
147
|
+
unless @record_count == @dbf.record_count
|
148
|
+
raise MalformedShpException.new("Not the same number of records in SHP and DBF")
|
149
|
+
end
|
150
|
+
end
|
151
|
+
|
152
|
+
#TODO : refactor to minimize redundant code
|
153
|
+
def get_record(i)
|
154
|
+
return nil if record_count <= i or i < 0
|
155
|
+
dbf_record = @dbf.record(i)
|
156
|
+
@shx.seek(100 + 8 * i) #100 is the header length
|
157
|
+
offset,length = @shx.read(8).unpack("N2")
|
158
|
+
@shp.seek(offset * 2 + 8)
|
159
|
+
rec_shp_type = @shp.read(4).unpack("V")[0]
|
160
|
+
|
161
|
+
case(rec_shp_type)
|
162
|
+
when ShpType::POINT
|
163
|
+
x, y = @shp.read(16).unpack("E2")
|
164
|
+
geometry = GeoRuby::SimpleFeatures::Point.from_x_y(x,y)
|
165
|
+
when ShpType::POLYLINE #actually creates a multi_polyline
|
166
|
+
@shp.seek(32,IO::SEEK_CUR) #extent
|
167
|
+
num_parts, num_points = @shp.read(8).unpack("V2")
|
168
|
+
parts = @shp.read(num_parts * 4).unpack("V" + num_parts.to_s)
|
169
|
+
parts << num_points #indexes for LS of idx i go to parts of idx i to idx i +1
|
170
|
+
points = Array.new(num_points) do
|
171
|
+
x, y = @shp.read(16).unpack("E2")
|
172
|
+
GeoRuby::SimpleFeatures::Point.from_x_y(x,y)
|
173
|
+
end
|
174
|
+
line_strings = Array.new(num_parts) do |i|
|
175
|
+
GeoRuby::SimpleFeatures::LineString.from_points(points[(parts[i])...(parts[i+1])])
|
176
|
+
end
|
177
|
+
geometry = GeoRuby::SimpleFeatures::MultiLineString.from_line_strings(line_strings)
|
178
|
+
when ShpType::POLYGON
|
179
|
+
#TODO : TO CORRECT
|
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
|
181
|
+
#Still sends back a multi polygon (so the correction above won't change what gets sent back)
|
182
|
+
@shp.seek(32,IO::SEEK_CUR)
|
183
|
+
num_parts, num_points = @shp.read(8).unpack("V2")
|
184
|
+
parts = @shp.read(num_parts * 4).unpack("V" + num_parts.to_s)
|
185
|
+
parts << num_points #indexes for LS of idx i go to parts of idx i to idx i +1
|
186
|
+
points = Array.new(num_points) do
|
187
|
+
x, y = @shp.read(16).unpack("E2")
|
188
|
+
GeoRuby::SimpleFeatures::Point.from_x_y(x,y)
|
189
|
+
end
|
190
|
+
linear_rings = Array.new(num_parts) do |i|
|
191
|
+
GeoRuby::SimpleFeatures::LinearRing.from_points(points[(parts[i])...(parts[i+1])])
|
192
|
+
end
|
193
|
+
geometry = GeoRuby::SimpleFeatures::MultiPolygon.from_polygons([GeoRuby::SimpleFeatures::Polygon.from_linear_rings(linear_rings)])
|
194
|
+
when ShpType::MULTIPOINT
|
195
|
+
@shp.seek(32,IO::SEEK_CUR)
|
196
|
+
num_points = @shp.read(4).unpack("V")[0]
|
197
|
+
points = Array.new(num_points) do
|
198
|
+
x, y = @shp.read(16).unpack("E2")
|
199
|
+
GeoRuby::SimpleFeatures::Point.from_x_y(x,y)
|
200
|
+
end
|
201
|
+
geometry = GeoRuby::SimpleFeatures::MultiPoint.from_points(points)
|
202
|
+
|
203
|
+
|
204
|
+
when ShpType::POINTZ
|
205
|
+
x, y, z, m = @shp.read(24).unpack("E4")
|
206
|
+
geometry = GeoRuby::SimpleFeatures::Point.from_x_y_z_m(x,y,z,m)
|
207
|
+
|
208
|
+
|
209
|
+
when ShpType::POLYLINEZ
|
210
|
+
@shp.seek(32,IO::SEEK_CUR)
|
211
|
+
num_parts, num_points = @shp.read(8).unpack("V2")
|
212
|
+
parts = @shp.read(num_parts * 4).unpack("V" + num_parts.to_s)
|
213
|
+
parts << num_points #indexes for LS of idx i go to parts of idx i to idx i +1
|
214
|
+
xys = Array.new(num_points) { @shp.read(16).unpack("E2") }
|
215
|
+
@shp.seek(16,IO::SEEK_CUR)
|
216
|
+
zs = Array.new(num_points) {@shp.read(8).unpack("E")[0]}
|
217
|
+
@shp.seek(16,IO::SEEK_CUR)
|
218
|
+
ms = Array.new(num_points) {@shp.read(8).unpack("E")[0]}
|
219
|
+
points = Array.new(num_points) do |i|
|
220
|
+
GeoRuby::SimpleFeatures::Point.from_x_y_z_m(xys[i][0],xys[i][1],zs[i],ms[i])
|
221
|
+
end
|
222
|
+
line_strings = Array.new(num_parts) do |i|
|
223
|
+
GeoRuby::SimpleFeatures::LineString.from_points(points[(parts[i])...(parts[i+1])],GeoRuby::SimpleFeatures::default_srid,true,true)
|
224
|
+
end
|
225
|
+
geometry = GeoRuby::SimpleFeatures::MultiLineString.from_line_strings(line_strings,GeoRuby::SimpleFeatures::default_srid,true,true)
|
226
|
+
|
227
|
+
|
228
|
+
when ShpType::POLYGONZ
|
229
|
+
#TODO : CORRECT
|
230
|
+
|
231
|
+
@shp.seek(32,IO::SEEK_CUR)#extent
|
232
|
+
num_parts, num_points = @shp.read(8).unpack("V2")
|
233
|
+
parts = @shp.read(num_parts * 4).unpack("V" + num_parts.to_s)
|
234
|
+
parts << num_points #indexes for LS of idx i go to parts of idx i to idx i +1
|
235
|
+
xys = Array.new(num_points) { @shp.read(16).unpack("E2") }
|
236
|
+
@shp.seek(16,IO::SEEK_CUR)#extent
|
237
|
+
zs = Array.new(num_points) {@shp.read(8).unpack("E")[0]}
|
238
|
+
@shp.seek(16,IO::SEEK_CUR)#extent
|
239
|
+
ms = Array.new(num_points) {@shp.read(8).unpack("E")[0]}
|
240
|
+
points = Array.new(num_points) do |i|
|
241
|
+
Point.from_x_y_z_m(xys[i][0],xys[i][1],zs[i],ms[i])
|
242
|
+
end
|
243
|
+
linear_rings = Array.new(num_parts) do |i|
|
244
|
+
GeoRuby::SimpleFeatures::LinearRing.from_points(points[(parts[i])...(parts[i+1])],GeoRuby::SimpleFeatures::default_srid,true,true)
|
245
|
+
end
|
246
|
+
geometry = GeoRuby::SimpleFeatures::MultiPolygon.from_polygons([GeoRuby::SimpleFeatures::Polygon.from_linear_rings(linear_rings)],GeoRuby::SimpleFeatures::default_srid,true,true)
|
247
|
+
|
248
|
+
|
249
|
+
when ShpType::MULTIPOINTZ
|
250
|
+
@shp.seek(32,IO::SEEK_CUR)
|
251
|
+
num_points = @shp.read(4).unpack("V")[0]
|
252
|
+
xys = Array.new(num_points) { @shp.read(16).unpack("E2") }
|
253
|
+
@shp.seek(16,IO::SEEK_CUR)
|
254
|
+
zs = Array.new(num_points) {@shp.read(8).unpack("E")[0]}
|
255
|
+
@shp.seek(16,IO::SEEK_CUR)
|
256
|
+
ms = Array.new(num_points) {@shp.read(8).unpack("E")[0]}
|
257
|
+
|
258
|
+
points = Array.new(num_points) do |i|
|
259
|
+
Point.from_x_y_z_m(xys[i][0],xys[i][1],zs[i],ms[i])
|
260
|
+
end
|
261
|
+
|
262
|
+
geometry = GeoRuby::SimpleFeatures::MultiPoint.from_points(points,GeoRuby::SimpleFeatures::default_srid,true,true)
|
263
|
+
|
264
|
+
when ShpType::POINTM
|
265
|
+
x, y, m = @shp.read(24).unpack("E3")
|
266
|
+
geometry = GeoRuby::SimpleFeatures::Point.from_x_y_m(x,y,m)
|
267
|
+
|
268
|
+
when ShpType::POLYLINEM
|
269
|
+
@shp.seek(32,IO::SEEK_CUR)
|
270
|
+
num_parts, num_points = @shp.read(8).unpack("V2")
|
271
|
+
parts = @shp.read(num_parts * 4).unpack("V" + num_parts.to_s)
|
272
|
+
parts << num_points #indexes for LS of idx i go to parts of idx i to idx i +1
|
273
|
+
xys = Array.new(num_points) { @shp.read(16).unpack("E2") }
|
274
|
+
@shp.seek(16,IO::SEEK_CUR)
|
275
|
+
ms = Array.new(num_points) {@shp.read(8).unpack("E")[0]}
|
276
|
+
points = Array.new(num_points) do |i|
|
277
|
+
Point.from_x_y_m(xys[i][0],xys[i][1],ms[i])
|
278
|
+
end
|
279
|
+
line_strings = Array.new(num_parts) do |i|
|
280
|
+
GeoRuby::SimpleFeatures::LineString.from_points(points[(parts[i])...(parts[i+1])],GeoRuby::SimpleFeatures::default_srid,false,true)
|
281
|
+
end
|
282
|
+
geometry = GeoRuby::SimpleFeatures::MultiLineString.from_line_strings(line_strings,GeoRuby::SimpleFeatures::default_srid,false,true)
|
283
|
+
|
284
|
+
|
285
|
+
when ShpType::POLYGONM
|
286
|
+
#TODO : CORRECT
|
287
|
+
|
288
|
+
@shp.seek(32,IO::SEEK_CUR)
|
289
|
+
num_parts, num_points = @shp.read(8).unpack("V2")
|
290
|
+
parts = @shp.read(num_parts * 4).unpack("V" + num_parts.to_s)
|
291
|
+
parts << num_points #indexes for LS of idx i go to parts of idx i to idx i +1
|
292
|
+
xys = Array.new(num_points) { @shp.read(16).unpack("E2") }
|
293
|
+
@shp.seek(16,IO::SEEK_CUR)
|
294
|
+
ms = Array.new(num_points) {@shp.read(8).unpack("E")[0]}
|
295
|
+
points = Array.new(num_points) do |i|
|
296
|
+
Point.from_x_y_m(xys[i][0],xys[i][1],ms[i])
|
297
|
+
end
|
298
|
+
linear_rings = Array.new(num_parts) do |i|
|
299
|
+
GeoRuby::SimpleFeatures::LinearRing.from_points(points[(parts[i])...(parts[i+1])],GeoRuby::SimpleFeatures::default_srid,false,true)
|
300
|
+
end
|
301
|
+
geometry = GeoRuby::SimpleFeatures::MultiPolygon.from_polygons([GeoRuby::SimpleFeatures::Polygon.from_linear_rings(linear_rings)],GeoRuby::SimpleFeatures::default_srid,false,true)
|
302
|
+
|
303
|
+
|
304
|
+
when ShpType::MULTIPOINTM
|
305
|
+
@shp.seek(32,IO::SEEK_CUR)
|
306
|
+
num_points = @shp.read(4).unpack("V")[0]
|
307
|
+
xys = Array.new(num_points) { @shp.read(16).unpack("E2") }
|
308
|
+
@shp.seek(16,IO::SEEK_CUR)
|
309
|
+
ms = Array.new(num_points) {@shp.read(8).unpack("E")[0]}
|
310
|
+
|
311
|
+
points = Array.new(num_points) do |i|
|
312
|
+
Point.from_x_y_m(xys[i][0],xys[i][1],ms[i])
|
313
|
+
end
|
314
|
+
|
315
|
+
geometry = GeoRuby::SimpleFeatures::MultiPoint.from_points(points,GeoRuby::SimpleFeatures::default_srid,false,true)
|
316
|
+
else
|
317
|
+
geometry = nil
|
318
|
+
end
|
319
|
+
|
320
|
+
ShpRecord.new(geometry,dbf_record)
|
321
|
+
end
|
322
|
+
end
|
323
|
+
|
324
|
+
#A SHP record : contains both the geometry and the data fields (from the DBF)
|
325
|
+
class ShpRecord
|
326
|
+
attr_reader :geometry , :data
|
327
|
+
|
328
|
+
def initialize(geometry, data)
|
329
|
+
@geometry = geometry
|
330
|
+
@data = data
|
331
|
+
end
|
332
|
+
|
333
|
+
#Tests if the geometry is a NULL SHAPE
|
334
|
+
def has_null_shape?
|
335
|
+
@geometry.nil?
|
336
|
+
end
|
337
|
+
end
|
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
|
+
data ? "T" : "F"
|
440
|
+
else
|
441
|
+
data.to_s
|
442
|
+
end
|
443
|
+
@dbf_io << [str].pack("A#{field.length}")
|
444
|
+
end
|
445
|
+
|
446
|
+
shp_str,min_xp,max_xp,min_yp,max_yp,min_zp,max_zp,min_mp,max_mp = build_shp_geometry(record.geometry)
|
447
|
+
max_x = max_xp if max_xp > max_x
|
448
|
+
min_x = min_xp if min_xp < min_x
|
449
|
+
max_y = max_yp if max_yp > max_y
|
450
|
+
min_y = min_yp if min_yp < min_y
|
451
|
+
max_z = max_zp if max_zp > max_z
|
452
|
+
min_z = min_zp if min_zp < min_z
|
453
|
+
max_m = max_mp if max_mp > max_m
|
454
|
+
min_m = min_mp if min_mp < min_m
|
455
|
+
length = (shp_str.length/2 + 2).to_i #num of 16-bit words; geom type is included (+2)
|
456
|
+
@shx_io << [(@shp_io.pos/2).to_i,length].pack("N2")
|
457
|
+
@shp_io << [index,length,@shp.shp_type].pack("N2V")
|
458
|
+
@shp_io << shp_str
|
459
|
+
index += 1
|
460
|
+
end
|
461
|
+
@shp_io.flush
|
462
|
+
@shx_io.flush
|
463
|
+
@dbf_io.flush
|
464
|
+
[min_x,max_x,min_y,max_y,min_z,max_z,min_m,max_m]
|
465
|
+
end
|
466
|
+
|
467
|
+
def commit_delete
|
468
|
+
@shp_r.rewind
|
469
|
+
header = @shp_r.read(100)
|
470
|
+
@shp_io << header
|
471
|
+
@shx_io << header
|
472
|
+
index = 1
|
473
|
+
while(!@shp_r.eof?)
|
474
|
+
icur,length = @shp_r.read(8).unpack("N2")
|
475
|
+
unless(@deleted[icur-1])
|
476
|
+
@shx_io << [(@shp_io.pos/2).to_i,length].pack("N2")
|
477
|
+
@shp_io << [index,length].pack("N2")
|
478
|
+
@shp_io << @shp_r.read(length * 2)
|
479
|
+
index += 1
|
480
|
+
else
|
481
|
+
@shp_r.seek(length * 2,IO::SEEK_CUR)
|
482
|
+
end
|
483
|
+
end
|
484
|
+
@shp_io.flush
|
485
|
+
@shx_io.flush
|
486
|
+
|
487
|
+
@dbf_r.rewind
|
488
|
+
@dbf_io << @dbf_r.read(@dbf.header_length)
|
489
|
+
icur = 0
|
490
|
+
while(!@dbf_r.eof?)
|
491
|
+
unless(@deleted[icur])
|
492
|
+
@dbf_io << @dbf_r.read(@dbf.record_length)
|
493
|
+
else
|
494
|
+
@dbf_r.seek(@dbf.record_length,IO::SEEK_CUR)
|
495
|
+
end
|
496
|
+
icur += 1
|
497
|
+
end
|
498
|
+
@dbf_io.flush
|
499
|
+
index
|
500
|
+
end
|
501
|
+
|
502
|
+
def commit_finalize(min_x,max_x,min_y,max_y,min_z,max_z,min_m,max_m)
|
503
|
+
#update size in shp and dbf + extent and num records in dbf
|
504
|
+
@shp_io.seek(0,IO::SEEK_END)
|
505
|
+
shp_size = @shp_io.pos / 2
|
506
|
+
@shx_io.seek(0,IO::SEEK_END)
|
507
|
+
shx_size= @shx_io.pos / 2
|
508
|
+
@shp_io.seek(24)
|
509
|
+
@shp_io.write([shp_size].pack("N"))
|
510
|
+
@shx_io.seek(24)
|
511
|
+
@shx_io.write([shx_size].pack("N"))
|
512
|
+
@shp_io.seek(36)
|
513
|
+
@shx_io.seek(36)
|
514
|
+
str = [min_x,min_y,max_x,max_y,min_z,max_z,min_m,max_m].pack("E8")
|
515
|
+
@shp_io.write(str)
|
516
|
+
@shx_io.write(str)
|
517
|
+
|
518
|
+
@dbf_io.seek(4)
|
519
|
+
@dbf_io.write([@dbf.record_count + @added.length - @deleted.length].pack("V"))
|
520
|
+
end
|
521
|
+
|
522
|
+
def build_shp_geometry(geometry)
|
523
|
+
m_range = nil
|
524
|
+
answer =
|
525
|
+
case @shp.shp_type
|
526
|
+
when ShpType::POINT
|
527
|
+
bbox = geometry.bounding_box
|
528
|
+
[geometry.x,geometry.y].pack("E2")
|
529
|
+
when ShpType::POLYLINE
|
530
|
+
str,bbox = create_bbox(geometry)
|
531
|
+
build_polyline(geometry,str)
|
532
|
+
when ShpType::POLYGON
|
533
|
+
str,bbox = create_bbox(geometry)
|
534
|
+
build_polygon(geometry,str)
|
535
|
+
when ShpType::MULTIPOINT
|
536
|
+
str,bbox = create_bbox(geometry)
|
537
|
+
build_multi_point(geometry,str)
|
538
|
+
when ShpType::POINTZ
|
539
|
+
bbox = geometry.bounding_box
|
540
|
+
[geometry.x,geometry.y,geometry.z,geometry.m].pack("E4")
|
541
|
+
when ShpType::POLYLINEZ
|
542
|
+
str,bbox = create_bbox(geometry)
|
543
|
+
m_range = geometry.m_range
|
544
|
+
build_polyline(geometry,str)
|
545
|
+
build_polyline_zm(geometry,:@z,[bbox[0].z,bbox[1].z],str)
|
546
|
+
build_polyline_zm(geometry,:@m,m_range,str)
|
547
|
+
when ShpType::POLYGONZ
|
548
|
+
str,bbox = create_bbox(geometry)
|
549
|
+
m_range = geometry.m_range
|
550
|
+
build_polygon(geometry,str)
|
551
|
+
build_polygon_zm(geometry,:@z,[bbox[0].z,bbox[1].z],str)
|
552
|
+
build_polygon_zm(geometry,:@m,m_range,str)
|
553
|
+
when ShpType::MULTIPOINTZ
|
554
|
+
str,bbox = create_bbox(geometry)
|
555
|
+
m_range = geometry.m_range
|
556
|
+
build_multi_point(geometry,str)
|
557
|
+
build_multi_point_zm(geometry,:@z,[bbox[0].z,bbox[1].z],str)
|
558
|
+
build_multi_point_zm(geometry,:@m,m_range,str)
|
559
|
+
when ShpType::POINTM
|
560
|
+
bbox = geometry.bounding_box
|
561
|
+
[geometry.x,geometry.y,geometry.m].pack("E3")
|
562
|
+
when ShpType::POLYLINEM
|
563
|
+
str,bbox = create_bbox(geometry)
|
564
|
+
m_range = geometry.m_range
|
565
|
+
build_polyline(geometry,str)
|
566
|
+
build_polyline_zm(geometry,:@m,m_range,str)
|
567
|
+
when ShpType::POLYGONM
|
568
|
+
str,bbox = create_bbox(geometry)
|
569
|
+
m_range = geometry.m_range
|
570
|
+
build_polygon(geometry,str)
|
571
|
+
build_polygon_zm(geometry,:@m,m_range,str)
|
572
|
+
when ShpType::MULTIPOINTM
|
573
|
+
str,bbox = create_bbox(geometry)
|
574
|
+
m_range = geometry.m_range
|
575
|
+
build_multi_point(geometry,str)
|
576
|
+
build_multi_point_zm(geometry,:@m,m_range,str)
|
577
|
+
end
|
578
|
+
m_range ||= [0,0]
|
579
|
+
[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]]
|
580
|
+
end
|
581
|
+
|
582
|
+
def create_bbox(geometry)
|
583
|
+
bbox = geometry.bounding_box
|
584
|
+
[[bbox[0].x,bbox[0].y,bbox[1].x,bbox[1].y].pack("E4"),bbox]
|
585
|
+
end
|
586
|
+
|
587
|
+
def build_polyline(geometry,str)
|
588
|
+
if geometry.is_a? GeoRuby::SimpleFeatures::LineString
|
589
|
+
str << [1,geometry.length,0].pack("V3")
|
590
|
+
geometry.each do |point|
|
591
|
+
str << [point.x,point.y].pack("E2")
|
592
|
+
end
|
593
|
+
else
|
594
|
+
#multilinestring
|
595
|
+
str << [geometry.length,geometry.inject(0) {|l, ls| l + ls.length}].pack("V2")
|
596
|
+
str << geometry.inject([0]) {|a,ls| a << (a.last + ls.length)}.pack("V#{geometry.length}") #last element of the previous array is dropped
|
597
|
+
geometry.each do |ls|
|
598
|
+
ls.each do |point|
|
599
|
+
str << [point.x,point.y].pack("E2")
|
600
|
+
end
|
601
|
+
end
|
602
|
+
end
|
603
|
+
str
|
604
|
+
end
|
605
|
+
|
606
|
+
def build_polyline_zm(geometry,zm,range,str)
|
607
|
+
str << range.pack("E2")
|
608
|
+
if geometry.is_a? GeoRuby::SimpleFeatures::LineString
|
609
|
+
geometry.each do |point|
|
610
|
+
str << [point.instance_variable_get(zm)].pack("E")
|
611
|
+
end
|
612
|
+
else
|
613
|
+
#multilinestring
|
614
|
+
geometry.each do |ls|
|
615
|
+
ls.each do |point|
|
616
|
+
str << [point.instance_variable_get(zm)].pack("E")
|
617
|
+
end
|
618
|
+
end
|
619
|
+
end
|
620
|
+
str
|
621
|
+
end
|
622
|
+
|
623
|
+
def build_polygon(geometry,str)
|
624
|
+
if geometry.is_a? GeoRuby::SimpleFeatures::Polygon
|
625
|
+
str << [geometry.length,geometry.inject(0) {|l, lr| l + lr.length}].pack("V2")
|
626
|
+
str << geometry.inject([0]) {|a,lr| a << (a.last + lr.length)}.pack("V#{geometry.length}") #last element of the previous array is dropped
|
627
|
+
geometry.each do |lr|
|
628
|
+
lr.each do |point|
|
629
|
+
str << [point.x,point.y].pack("E2")
|
630
|
+
end
|
631
|
+
end
|
632
|
+
else
|
633
|
+
#multipolygon
|
634
|
+
num_rings = geometry.inject(0) {|l, poly| l + poly.length}
|
635
|
+
str << [num_rings, geometry.inject(0) {|l, poly| l + poly.inject(0) {|l2,lr| l2 + lr.length} }].pack("V2")
|
636
|
+
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
|
637
|
+
geometry.each do |poly|
|
638
|
+
poly.each do |lr|
|
639
|
+
lr.each do |point|
|
640
|
+
str << [point.x,point.y].pack("E2")
|
641
|
+
end
|
642
|
+
end
|
643
|
+
end
|
644
|
+
end
|
645
|
+
str
|
646
|
+
end
|
647
|
+
|
648
|
+
def build_polygon_zm(geometry,zm,range,str)
|
649
|
+
str << range.pack("E2")
|
650
|
+
if geometry.is_a? GeoRuby::SimpleFeatures::Polygon
|
651
|
+
geometry.each do |lr|
|
652
|
+
lr.each do |point|
|
653
|
+
str << [point.instance_variable_get(zm)].pack("E")
|
654
|
+
end
|
655
|
+
end
|
656
|
+
else
|
657
|
+
geometry.each do |poly|
|
658
|
+
poly.each do |lr|
|
659
|
+
lr.each do |point|
|
660
|
+
str << [point.instance_variable_get(zm)].pack("E")
|
661
|
+
end
|
662
|
+
end
|
663
|
+
end
|
664
|
+
end
|
665
|
+
str
|
666
|
+
end
|
667
|
+
|
668
|
+
def build_multi_point(geometry,str)
|
669
|
+
str << [geometry.length].pack("V")
|
670
|
+
geometry.each do |point|
|
671
|
+
str << [point.x,point.y].pack("E2")
|
672
|
+
end
|
673
|
+
str
|
674
|
+
end
|
675
|
+
|
676
|
+
def build_multi_point_zm(geometry,zm,range,str)
|
677
|
+
str << range.pack("E2")
|
678
|
+
geometry.each do |point|
|
679
|
+
str << [point.instance_variable_get(zm)].pack("E")
|
680
|
+
end
|
681
|
+
str
|
682
|
+
end
|
683
|
+
end
|
684
|
+
|
685
|
+
class MalformedShpException < StandardError
|
686
|
+
end
|
687
|
+
|
688
|
+
class UnexistantRecordException < StandardError
|
689
|
+
end
|
690
|
+
|
691
|
+
class IncompatibleGeometryException < StandardError
|
692
|
+
end
|
693
|
+
|
694
|
+
class IncompatibleDataException < StandardError
|
695
|
+
end
|
696
|
+
end
|
697
|
+
end
|