ppe-georuby 1.7.2

Sign up to get free protection for your applications and to get access to all the features.
Files changed (71) hide show
  1. data/.gitignore +6 -0
  2. data/History.txt +4 -0
  3. data/LICENSE +21 -0
  4. data/README.txt +118 -0
  5. data/Rakefile +48 -0
  6. data/VERSION +1 -0
  7. data/lib/geo_ruby.rb +22 -0
  8. data/lib/geo_ruby/gpx.rb +1 -0
  9. data/lib/geo_ruby/gpx4r/gpx.rb +117 -0
  10. data/lib/geo_ruby/shp.rb +1 -0
  11. data/lib/geo_ruby/shp4r/dbf.rb +41 -0
  12. data/lib/geo_ruby/shp4r/shp.rb +697 -0
  13. data/lib/geo_ruby/simple_features/envelope.rb +167 -0
  14. data/lib/geo_ruby/simple_features/ewkb_parser.rb +216 -0
  15. data/lib/geo_ruby/simple_features/ewkt_parser.rb +336 -0
  16. data/lib/geo_ruby/simple_features/geometry.rb +228 -0
  17. data/lib/geo_ruby/simple_features/geometry_collection.rb +136 -0
  18. data/lib/geo_ruby/simple_features/geometry_factory.rb +81 -0
  19. data/lib/geo_ruby/simple_features/georss_parser.rb +135 -0
  20. data/lib/geo_ruby/simple_features/helper.rb +18 -0
  21. data/lib/geo_ruby/simple_features/line_string.rb +206 -0
  22. data/lib/geo_ruby/simple_features/linear_ring.rb +12 -0
  23. data/lib/geo_ruby/simple_features/multi_line_string.rb +51 -0
  24. data/lib/geo_ruby/simple_features/multi_point.rb +46 -0
  25. data/lib/geo_ruby/simple_features/multi_polygon.rb +52 -0
  26. data/lib/geo_ruby/simple_features/point.rb +364 -0
  27. data/lib/geo_ruby/simple_features/polygon.rb +157 -0
  28. data/ppe-georuby.gemspec +133 -0
  29. data/script/console +10 -0
  30. data/script/destroy +14 -0
  31. data/script/generate +14 -0
  32. data/script/txt2html +82 -0
  33. data/spec/data/gpx/fells_loop.gpx +1077 -0
  34. data/spec/data/gpx/long.gpx +1642 -0
  35. data/spec/data/gpx/long.kml +31590 -0
  36. data/spec/data/gpx/long.nmea +2220 -0
  37. data/spec/data/gpx/short.gpx +13634 -0
  38. data/spec/data/gpx/short.kml +130 -0
  39. data/spec/data/gpx/tracktreks.gpx +706 -0
  40. data/spec/data/multipoint.dbf +0 -0
  41. data/spec/data/multipoint.shp +0 -0
  42. data/spec/data/multipoint.shx +0 -0
  43. data/spec/data/point.dbf +0 -0
  44. data/spec/data/point.shp +0 -0
  45. data/spec/data/point.shx +0 -0
  46. data/spec/data/polygon.dbf +0 -0
  47. data/spec/data/polygon.shp +0 -0
  48. data/spec/data/polygon.shx +0 -0
  49. data/spec/data/polyline.dbf +0 -0
  50. data/spec/data/polyline.shp +0 -0
  51. data/spec/data/polyline.shx +0 -0
  52. data/spec/geo_ruby/gpx4r/gpx_spec.rb +106 -0
  53. data/spec/geo_ruby/shp4r/shp_spec.rb +240 -0
  54. data/spec/geo_ruby/simple_features/envelope_spec.rb +45 -0
  55. data/spec/geo_ruby/simple_features/ewkb_parser_spec.rb +158 -0
  56. data/spec/geo_ruby/simple_features/ewkt_parser_spec.rb +179 -0
  57. data/spec/geo_ruby/simple_features/geometry_collection_spec.rb +55 -0
  58. data/spec/geo_ruby/simple_features/geometry_factory_spec.rb +11 -0
  59. data/spec/geo_ruby/simple_features/geometry_spec.rb +32 -0
  60. data/spec/geo_ruby/simple_features/georss_parser_spec.rb +218 -0
  61. data/spec/geo_ruby/simple_features/line_string_spec.rb +245 -0
  62. data/spec/geo_ruby/simple_features/linear_ring_spec.rb +14 -0
  63. data/spec/geo_ruby/simple_features/multi_line_string_spec.rb +54 -0
  64. data/spec/geo_ruby/simple_features/multi_point_spec.rb +35 -0
  65. data/spec/geo_ruby/simple_features/multi_polygon_spec.rb +50 -0
  66. data/spec/geo_ruby/simple_features/point_spec.rb +356 -0
  67. data/spec/geo_ruby/simple_features/polygon_spec.rb +108 -0
  68. data/spec/geo_ruby_spec.rb +27 -0
  69. data/spec/spec.opts +6 -0
  70. data/spec/spec_helper.rb +65 -0
  71. metadata +162 -0
@@ -0,0 +1,6 @@
1
+ .DS_Store
2
+ pkg/*.gem
3
+ pkg/*.tgz
4
+ pkg/**/*
5
+ coverage/*
6
+ doc/*
@@ -0,0 +1,4 @@
1
+ == 0.0.1 2008-08-14
2
+
3
+ * 1 major enhancement:
4
+ * Initial release
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.
@@ -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.
@@ -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
@@ -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'
@@ -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
@@ -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