nofxx-georuby 1.7.3 → 1.9.0

Sign up to get free protection for your applications and to get access to all the features.
data/README.rdoc ADDED
@@ -0,0 +1,184 @@
1
+ = GeoRuby
2
+
3
+
4
+ This is a fork of GeoRuby with some updates and extra funcionality.
5
+
6
+ It is intended as a holder for geometric data. The data model roughly
7
+ follows the OGC "Simple Features for SQL" specification, so it should
8
+ play nice with data returned from PostGIS or any Spatial Extensions (MongoDB, MySQL...).
9
+ Although without any kind of advanced functionalities (such as geometric operators or reprojections).
10
+ It also supports various output and input formats (GeoRSS, KML, Shapefile).
11
+
12
+ OGC:"http://www.opengis.org/docs/99-049.pdf"
13
+
14
+
15
+ == Available data types
16
+
17
+ The following geometric data types are provided :
18
+ - Point
19
+ - Line String
20
+ - Linear Ring
21
+ - Polygon
22
+ - Multi Point
23
+ - Multi Line String
24
+ - Multi Polygon
25
+ - Geometry Collection
26
+
27
+ They can be in 2D, 3DZ, 3DM, and 4D.
28
+
29
+ On top of this an Envelope class is available, to contain the bounding box of a geometry.
30
+
31
+ == Installation
32
+
33
+ To install the latest version, just type:
34
+
35
+ gem install nofxx-georuby
36
+
37
+
38
+ == Use
39
+
40
+
41
+ === Simple Examples
42
+
43
+ Creating a 3D Point:
44
+
45
+ Point.from_x_y_z(10.0, 20.0, 5.0)
46
+
47
+
48
+ Creating a LineString:
49
+
50
+ LineString.from_coordinates([[1,1],[2,2]],4326))
51
+
52
+
53
+ === Input and output
54
+
55
+ These geometries can be input and output in WKB/EWKB/WKT/EWKT format as well as
56
+ the related HexWKB and HexEWKB formats. HexEWKB and WKB are the default form under
57
+ which geometric data is returned respectively from PostGIS and MySql.
58
+
59
+ GeoRSS Simple, GeoRSS W3CGeo, GeoRSS GML can also be input and output.
60
+ Note that they will not output valid RSS, but just the part strictly concerning
61
+ the geometry as outlined in http://www.georss.org/1/ . Since the model does
62
+ not allow multiple geometries, for geometry collections, only the first geometry
63
+ will be output. Similarly, for polygons, the GeoRSS output will only contain the outer ring.
64
+ As for W3CGeo output, only points can be output, so the first point of the geometry is chosen.
65
+ By default the Simple format is output. Envelope can also be output in these formats:
66
+ The box geometric type is chosen (except for W3CGeo, where the center of the envelope is chose).
67
+ These formats can also be input and a GeoRuby geometry will be created.
68
+ Note that it will not read a valid RSS file, only a geometry string.
69
+
70
+ On top of that, there is now support for KML output and input.
71
+ As for GeoRSS, a valid KML file will not be output, but only the geometric data.
72
+ Options <tt>:id</tt>, <tt>:extrude</tt>, <tt>:tesselate</tt> and <tt>:altitude_mode</tt> can be given.
73
+ Note that if the <tt>:altitude_mode</tt> option is not passed or set to <tt>clampToGround</tt>,
74
+ the altitude data will not be output even if present. Envelopes output a LatLonAltBox instead of a geometry.
75
+ For the output, the following geometric types are supported : Point, LineString, Polygon.
76
+
77
+
78
+ === SHP reading et writing
79
+
80
+ Georuby has support for reading ESRI shapefiles (http://www.esri.com/library/whitepapers/pdfs/shapefile.pdf).
81
+ A tool called <tt>shp2sql.rb</tt> is also provided: it shows how to use the SHP reading functionality together
82
+ with the spatial adapter plugin for Rails to import spatial features into MySQL and PostGIS.
83
+
84
+ Here is an example of Shapefile reading, that goes through all the geometries
85
+ in a file and disaply the values of the attributes :
86
+
87
+ require 'geo_ruby/shp'
88
+
89
+ ShpFile.open(shpfile) do |shp|
90
+ shp.each do |shape|
91
+ geom = shape.geometry #a GeoRuby SimpleFeature
92
+ att_data = shape.data #a Hash
93
+ shp.fields.each do |field|
94
+ puts att_data[field.name]
95
+ end
96
+ end
97
+ end
98
+
99
+ Support for ESRI shapefile creation and modification has been added as well.
100
+ New shapefiles can be created given a geometry type and specifications for the DBF fields.
101
+ Data can be added and removed from an existing shapefile.
102
+ An update operation is also provided for convenience: it just performs a 'delete' and an 'add',
103
+ which means the index of the modified record will change. Note that once a shapefile has been created,
104
+ GeoRuby does not allow the modification of the schema (it will probably be done in a subsequent version).
105
+
106
+ Here is an example of how to create a new Shapefile with 2 fields :
107
+
108
+ shpfile = ShpFile.create('hello.shp',ShpType::POINT,[Dbf::Field.new("Hoyoyo","C",10),Dbf::Field.new("Boyoul","N",10,0)])
109
+
110
+ The file is then open for reading and writing.
111
+
112
+ Here is an example of how to write to a shapefile (created or not with GeoRuby) :
113
+
114
+ shpfile = ShpFile.open('places.shp')
115
+ shpfile.transaction do |tr|
116
+ tr.add(ShpRecord.new(Point.from_x_y(123.4,123.4),'Hoyoyo' => "AEZ",'Bouyoul' => 45))
117
+ tr.update(4,ShpRecord.new(Point.from_x_y(-16.67,16.41),'Hoyoyo' => "EatMe",'Bouyoul' => 42))
118
+ tr.delete(1)
119
+ end
120
+ shpfile.close
121
+
122
+ Note the transaction is just there so the operations on the files can be buffered.
123
+ Nothing happens on the original files until the block has finished executing.
124
+ Calling <tt>tr.rollback</tt> at anytime during the execution will prevent the modifications.
125
+
126
+ Also currently, error reporting is minimal and it has not been tested that
127
+ thoroughly so caveat emptor and backup before performing any destructive operation.
128
+
129
+
130
+ === GPX Reading
131
+
132
+ You can read and convert GPX Files to LineString/Polygon:
133
+
134
+ gpxfile = GpxFile.open(tour.gpx')
135
+ gpxfile.as_line_string
136
+ => GeoRuby::SimpleFeatures::LineString..
137
+
138
+ === GeoJSON Support
139
+ Basic GeoJSON support has been implemented per v1.0 of the {spec}[http://geojson.org/geojson-spec.html].
140
+
141
+ USAGE:
142
+
143
+ input - GeoRuby::SimpleFeatures::Geometry.from_geojson(geojson_string)
144
+ output - call #as_geojson or #to_json on any SimpleFeature Geometry instance
145
+
146
+ TODO:
147
+ * Refactor to support extremely large GeoJSON input streams / files. Currently
148
+ the entire GeoJSON representation must be loaded into memory as a String
149
+ * Improve srid/crs support on input and add support on output
150
+ * Implement bounding-box spport per spec on input/output?
151
+ * Improved / more tests
152
+
153
+ GeoJSON support implemented by {Marcus Mateus}[http://github.com/marcusmateus] and released courtesy {SimpliTex}[http://simplitex.com].
154
+
155
+ === Acknowledgement
156
+
157
+ The SHP reading part uses the DBF library (http://rubyforge.org/projects/dbf/) by Keith Morrison (http://infused.org).
158
+ Thanks also to Pramukta Kumar and Pete Schwamb for their contributions.
159
+
160
+ === License
161
+
162
+ GeoRuby is released under the MIT license.
163
+
164
+
165
+ == Support (Original GeoRuby gem)
166
+
167
+ Any questions, enhancement proposals, bug notifications or corrections
168
+ can be sent to mailto:guilhem.vellut@gmail.com.
169
+
170
+
171
+ === Changes since the last version
172
+
173
+ - Writing of ESRI shapefiles
174
+ - Reading of ESRI shapefiles
175
+ - Addition of a small tool to import spatial features in MySQL and PostGIS from a SHP file
176
+
177
+
178
+ === Coming in the next versions
179
+
180
+ - Schema modification of existing shapefiles
181
+ - More error reporting when writing shapefiles
182
+ - More tests on writing shapefiles ; tests on real-world shapefiles
183
+ - Better shp2sql import tool
184
+ - Documentation
data/Rakefile CHANGED
@@ -7,11 +7,12 @@ begin
7
7
  gem.name = "nofxx-georuby"
8
8
  gem.summary = "Ruby data holder for OGC Simple Features"
9
9
  gem.description = "GeoRuby provides geometric data types from the OGC 'Simple Features' specification."
10
- gem.email = "x@nofxx.com"
10
+ gem.email = "georuby@simplitex.com"
11
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"
12
+ gem.authors = ["Guilhem Vellut", "Marcos Piccinini", "Marcus Mateus", "Doug Cole"]
13
+ gem.add_runtime_dependency "json_pure", ">=1.4.6"
14
+ gem.add_development_dependency "rspec", ">= 2.0.0"
15
+ gem.add_development_dependency "dbf", ">= 1.2.9"
15
16
  end
16
17
  rescue LoadError
17
18
  puts "Jeweler not available. Install it with: sudo gem install technicalpickles-jeweler -s http://gems.github.com"
data/VERSION CHANGED
@@ -1 +1 @@
1
- 1.7.3
1
+ 1.9.0
data/lib/geo_ruby.rb CHANGED
@@ -16,6 +16,7 @@ require 'geo_ruby/simple_features/geometry_collection'
16
16
  require 'geo_ruby/simple_features/envelope'
17
17
  require 'geo_ruby/simple_features/geometry_factory'
18
18
  require 'geo_ruby/simple_features/georss_parser'
19
+ require 'geo_ruby/simple_features/geojson_parser'
19
20
 
20
21
  # Include if you need
21
22
  # require 'geo_ruby/shp4r/shp'
@@ -50,7 +50,7 @@ module GeoRuby
50
50
 
51
51
  #Sends back the center of the envelope
52
52
  def center
53
- Point.from_x_y((lower_corner.x + upper_corner.x)/2,(lower_corner.y + upper_corner.y)/2)
53
+ Point.from_x_y((lower_corner.x + upper_corner.x)/2,(lower_corner.y + upper_corner.y)/2, srid)
54
54
  end
55
55
 
56
56
  #Zoom level
@@ -27,15 +27,6 @@ module GeoRuby
27
27
 
28
28
  def initialize(factory)
29
29
  @factory = factory
30
- @parse_options ={
31
- 1 => method(:parse_point),
32
- 2 => method(:parse_line_string),
33
- 3 => method(:parse_polygon),
34
- 4 => method(:parse_multi_point),
35
- 5 => method(:parse_multi_line_string),
36
- 6 => method(:parse_multi_polygon),
37
- 7 => method(:parse_geometry_collection)
38
- }
39
30
  end
40
31
 
41
32
  #Parses the ewkb string passed as argument and notifies the factory of events
@@ -70,8 +61,21 @@ module GeoRuby
70
61
  @srid= @srid || DEFAULT_SRID
71
62
  end
72
63
 
73
- if @parse_options.has_key? @geometry_type
74
- @parse_options[@geometry_type].call
64
+ case @geometry_type
65
+ when 1
66
+ parse_point
67
+ when 2
68
+ parse_line_string
69
+ when 3
70
+ parse_polygon
71
+ when 4
72
+ parse_multi_point
73
+ when 5
74
+ parse_multi_line_string
75
+ when 6
76
+ parse_multi_polygon
77
+ when 7
78
+ parse_geometry_collection
75
79
  else
76
80
  raise EWKBFormatError::new("Unknown geometry type")
77
81
  end
@@ -156,12 +160,7 @@ module GeoRuby
156
160
  end
157
161
  #transforms a HexEWKB string into an EWKB string
158
162
  def decode_hex(hexewkb)
159
- result=""
160
- num_bytes = (hexewkb.size + 1) / 2
161
- 0.upto(num_bytes-1) do |i|
162
- result << hexewkb[i*2,2].hex
163
- end
164
- result
163
+ [hexewkb].pack("H*")
165
164
  end
166
165
  end
167
166
 
@@ -0,0 +1,132 @@
1
+ # GeoJSON parser based on the v1.0 spec at http://geojson.org/geojson-spec.html
2
+ require 'json/pure'
3
+
4
+ module GeoRuby
5
+ module SimpleFeatures
6
+ #Raised when an error in the GeoJSON string is detected
7
+ class GeojsonFormatError < StandardError
8
+ end
9
+
10
+ # Class added to support geojson 'Feature' objects
11
+ class GeojsonFeature
12
+ attr_accessor :geometry, :properties, :id
13
+
14
+ def initialize(geometry, properties = {}, id = nil)
15
+ @geometry = geometry
16
+ @properties = properties
17
+ @id = id
18
+ end
19
+
20
+ def ==(other)
21
+ if (self.class != other.class)
22
+ false
23
+ else
24
+ (self.id == other.id) && (self.geometry == other.geometry) && (self.properties == other.properties)
25
+ end
26
+ end
27
+
28
+ def to_json(options={})
29
+ output = {}
30
+ output[:type] = 'Feature'
31
+ output[:geometry] = geometry
32
+ output[:properties] = properties
33
+ output[:id] = id unless id.nil?
34
+ output.to_json(options)
35
+ end
36
+ alias :as_geojson :to_json
37
+ end
38
+
39
+ # Class added to support geojson 'Feature Collection' objects
40
+ class GeojsonFeatureCollection
41
+ attr_accessor :features
42
+
43
+ def initialize(features)
44
+ @features = features
45
+ end
46
+
47
+ def ==(other)
48
+ if (self.class != other.class) || (features.size != other.features.size)
49
+ return false
50
+ else
51
+ features.each_index do |index|
52
+ return false if self.features[index] != other.features[index]
53
+ end
54
+ end
55
+ true
56
+ end
57
+
58
+ def to_json(options = {})
59
+ {:type => 'FeatureCollection',
60
+ :features => features}.to_json(options)
61
+ end
62
+ alias :as_geojson :to_json
63
+ end
64
+
65
+
66
+ class GeojsonParser
67
+ attr_reader :geometry
68
+
69
+ def parse(geojson, srid=DEFAULT_SRID)
70
+ @geometry = nil
71
+ geohash = JSON.parse(geojson)
72
+ parse_geohash(geohash, srid)
73
+ end
74
+
75
+ private
76
+
77
+ def parse_geohash(geohash, srid)
78
+ srid = srid_from_crs(geohash['crs']) || srid
79
+ case geohash['type']
80
+ when 'Point', 'MultiPoint', 'LineString', 'MultiLineString', 'Polygon', 'MultiPolygon', 'GeometryCollection'
81
+ @geometry = parse_geometry(geohash, srid)
82
+ when 'Feature'
83
+ @geometry = parse_geojson_feature(geohash, srid)
84
+ when 'FeatureCollection'
85
+ @geometry = parse_geojson_feature_collection(geohash, srid)
86
+ else
87
+ GeojsonFormatError.new('Unknown GeoJSON type')
88
+ end
89
+ end
90
+
91
+ def parse_geometry(geohash, srid)
92
+ srid = srid_from_crs(geohash['crs']) || srid
93
+ if geohash['type'] == 'GeometryCollection'
94
+ parse_geometry_collection(geohash, srid)
95
+ else
96
+ klass = GeoRuby::SimpleFeatures.const_get(geohash['type'])
97
+ klass.from_coordinates(geohash['coordinates'], srid, false, false)
98
+ end
99
+ end
100
+
101
+ def parse_geometry_collection(geohash, srid)
102
+ srid = srid_from_crs(geohash['crs']) || srid
103
+ geometries = geohash['geometries'].map{|g| parse_geometry(g,srid)}
104
+ GeometryCollection.from_geometries(geometries,srid)
105
+ end
106
+
107
+ def parse_geojson_feature(geohash, srid)
108
+ srid = srid_from_crs(geohash['crs']) || srid
109
+ geometry = parse_geometry(geohash['geometry'],srid)
110
+ GeojsonFeature.new(geometry, geohash['properties'], geohash['id'])
111
+ end
112
+
113
+ def parse_geojson_feature_collection(geohash, srid)
114
+ srid = srid_from_crs(geohash['crs']) || srid
115
+ features = []
116
+ geohash['features'].each do |feature|
117
+ features << parse_geojson_feature(feature, srid)
118
+ end
119
+ GeojsonFeatureCollection.new(features)
120
+ end
121
+
122
+ def srid_from_crs(crs)
123
+ # We somehow need to map crs to srid, currently only support for EPSG
124
+ if crs && crs['type'] == 'OGC'
125
+ urn = crs['properties']['urn'].split(':')
126
+ return urn.last if urn[4] == 'EPSG'
127
+ end
128
+ return nil
129
+ end
130
+ end
131
+ end
132
+ end
@@ -215,6 +215,14 @@ module GeoRuby#:nodoc:
215
215
  end
216
216
  return wkt
217
217
  end
218
+
219
+ # Some GeoJSON files do not include srid info, so
220
+ # we provide an optional parameter
221
+ def self.from_geojson(geojson, srid=DEFAULT_SRID)
222
+ geojson_parser= GeojsonParser::new
223
+ geojson_parser.parse(geojson, srid)
224
+ geojson_parser.geometry
225
+ end
218
226
 
219
227
  private
220
228
 
@@ -102,6 +102,14 @@ module GeoRuby
102
102
  "GEOMETRYCOLLECTION"
103
103
  end
104
104
 
105
+ # simple geojson representation
106
+ # TODO add CRS / SRID support?
107
+ def to_json(options = {})
108
+ {:type => 'GeometryCollection',
109
+ :geometries => self.geometries}.to_json(options)
110
+ end
111
+ alias :as_geojson :to_json
112
+
105
113
  #georss simple representation : outputs only the first geometry of the collection
106
114
  def georss_simple_representation(options)#:nodoc:
107
115
  self[0].georss_simple_representation(options)
@@ -188,6 +188,18 @@ module GeoRuby
188
188
  end
189
189
  end
190
190
 
191
+ def to_coordinates
192
+ points.map{|p| p.to_coordinates }
193
+ end
194
+
195
+ # simple geojson representation
196
+ # TODO add CRS / SRID support?
197
+ def to_json(options = {})
198
+ {:type => 'LineString',
199
+ :coordinates => self.to_coordinates}.to_json(options)
200
+ end
201
+ alias :as_geojson :to_json
202
+
191
203
  #Creates a new line string. Accept an array of points as argument
192
204
  def self.from_points(points,srid=DEFAULT_SRID,with_z=false,with_m=false)
193
205
  line_string = new(srid,with_z,with_m)