Class: RGeo::GeoJSON::Coder

Inherits:
Object
  • Object
show all
Defined in:
lib/rgeo/geo_json/coder.rb

Overview

This object encapsulates encoding and decoding settings (principally the RGeo::Feature::Factory and the RGeo::GeoJSON::EntityFactory to be used) so that you can encode and decode without specifying those settings every time.

Constant Summary collapse

@@json_available =
nil
@@yajl_available =
nil
@@activesupport_available =
nil

Instance Method Summary collapse

Constructor Details

#initialize(opts_ = {}) ⇒ Coder

Create a new coder settings object. The geo factory is passed as a required argument.

Options include:

:geo_factory

Specifies the geo factory to use to create geometry objects. Defaults to the preferred cartesian factory.

:entity_factory

Specifies an entity factory, which lets you override the types of GeoJSON entities that are created. It defaults to the default RGeo::GeoJSON::EntityFactory, which generates objects of type RGeo::GeoJSON::Feature or RGeo::GeoJSON::FeatureCollection. See RGeo::GeoJSON::EntityFactory for more information.

:json_parser

Specifies a JSON parser to use when decoding a String or IO object. The value may be a Proc object taking the string as the sole argument and returning the JSON hash, or it may be one of the special values :json, :yajl, or :active_support. Setting one of those special values will require the corresponding library to be available. Note that the :json library is present in the standard library in Ruby 1.9, but requires the “json” gem in Ruby 1.8. If a parser is not specified, then the decode method will not accept a String or IO object; it will require a Hash.


45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
# File 'lib/rgeo/geo_json/coder.rb', line 45

def initialize(opts_={})
  @geo_factory = opts_[:geo_factory] || ::RGeo::Cartesian.preferred_factory
  @entity_factory = opts_[:entity_factory] || EntityFactory.instance
  @json_parser = opts_[:json_parser]
  case @json_parser
  when :json
    if @@json_available.nil?
      begin
        require 'json'
        @@json_available = true
      rescue ::LoadError
        @@json_available = false
      end
    end
    if @@json_available
      @json_parser = ::Proc.new{ |str_| ::JSON.parse(str_) }
    else
      raise Error::RGeoError, "JSON library is not available. You may need to install the 'json' gem."
    end
  when :yajl
    if @@yajl_available.nil?
      begin
        require 'yajl'
        @@yajl_available = true
      rescue ::LoadError
        @@yajl_available = false
      end
    end
    if @@yajl_available
      @json_parser = ::Proc.new{ |str_| ::Yajl::Parser.new.parse(str_) }
    else
      raise Error::RGeoError, "Yajl library is not available. You may need to install the 'yajl' gem."
    end
  when :active_support
    if @@activesupport_available.nil?
      begin
        require 'active_support/json'
        @@activesupport_available = true
      rescue ::LoadError
        @@activesupport_available = false
      end
    end
    if @@activesupport_available
      @json_parser = ::Proc.new{ |str_| ::ActiveSupport::JSON.decode(str_) }
    else
      raise Error::RGeoError, "ActiveSupport::JSON library is not available. You may need to install the 'activesupport' gem."
    end
  when ::Proc, nil
    # Leave as is
  else
    raise ::ArgumentError, "Unrecognzied json_parser: #{@json_parser.inspect}"
  end
  @num_coordinates = 2
  @num_coordinates += 1 if @geo_factory.property(:has_z_coordinate)
  @num_coordinates += 1 if @geo_factory.property(:has_m_coordinate)
end

Instance Method Details

#_decode_feature(input_) ⇒ Object

:nodoc:


249
250
251
252
253
254
255
256
# File 'lib/rgeo/geo_json/coder.rb', line 249

def _decode_feature(input_)  # :nodoc:
  geometry_ = input_['geometry']
  if geometry_
    geometry_ = _decode_geometry(geometry_)
    return nil unless geometry_
  end
  @entity_factory.feature(geometry_, input_['id'], input_['properties'])
end

#_decode_geometry(input_) ⇒ Object

:nodoc:


259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
# File 'lib/rgeo/geo_json/coder.rb', line 259

def _decode_geometry(input_)  # :nodoc:
  case input_['type']
  when 'GeometryCollection'
    _decode_geometry_collection(input_)
  when 'Point'
    _decode_point_coords(input_['coordinates'])
  when 'LineString'
    _decode_line_string_coords(input_['coordinates'])
  when 'Polygon'
    _decode_polygon_coords(input_['coordinates'])
  when 'MultiPoint'
    _decode_multi_point_coords(input_['coordinates'])
  when 'MultiLineString'
    _decode_multi_line_string_coords(input_['coordinates'])
  when 'MultiPolygon'
    _decode_multi_polygon_coords(input_['coordinates'])
  else
    nil
  end
end

#_decode_geometry_collection(input_) ⇒ Object

:nodoc:


281
282
283
284
285
286
287
288
289
290
# File 'lib/rgeo/geo_json/coder.rb', line 281

def _decode_geometry_collection(input_)  # :nodoc:
  geometries_ = input_['geometries']
  geometries_ = [] unless geometries_.kind_of?(::Array)
  decoded_geometries_ = []
  geometries_.each do |g_|
    g_ = _decode_geometry(g_)
    decoded_geometries_ << g_ if g_
  end
  @geo_factory.collection(decoded_geometries_)
end

#_decode_line_string_coords(line_coords_) ⇒ Object

:nodoc:


299
300
301
302
303
304
305
306
307
# File 'lib/rgeo/geo_json/coder.rb', line 299

def _decode_line_string_coords(line_coords_)  # :nodoc:
  return nil unless line_coords_.kind_of?(::Array)
  points_ = []
  line_coords_.each do |point_coords_|
    point_ = _decode_point_coords(point_coords_)
    points_ << point_ if point_
  end
  @geo_factory.line_string(points_)
end

#_decode_multi_line_string_coords(multi_line_coords_) ⇒ Object

:nodoc:


342
343
344
345
346
347
348
349
350
# File 'lib/rgeo/geo_json/coder.rb', line 342

def _decode_multi_line_string_coords(multi_line_coords_)  # :nodoc:
  return nil unless multi_line_coords_.kind_of?(::Array)
  lines_ = []
  multi_line_coords_.each do |line_coords_|
    line_ = _decode_line_string_coords(line_coords_)
    lines_ << line_ if line_
  end
  @geo_factory.multi_line_string(lines_)
end

#_decode_multi_point_coords(multi_point_coords_) ⇒ Object

:nodoc:


331
332
333
334
335
336
337
338
339
# File 'lib/rgeo/geo_json/coder.rb', line 331

def _decode_multi_point_coords(multi_point_coords_)  # :nodoc:
  return nil unless multi_point_coords_.kind_of?(::Array)
  points_ = []
  multi_point_coords_.each do |point_coords_|
    point_ = _decode_point_coords(point_coords_)
    points_ << point_ if point_
  end
  @geo_factory.multi_point(points_)
end

#_decode_multi_polygon_coords(multi_polygon_coords_) ⇒ Object

:nodoc:


353
354
355
356
357
358
359
360
361
# File 'lib/rgeo/geo_json/coder.rb', line 353

def _decode_multi_polygon_coords(multi_polygon_coords_)  # :nodoc:
  return nil unless multi_polygon_coords_.kind_of?(::Array)
  polygons_ = []
  multi_polygon_coords_.each do |poly_coords_|
    poly_ = _decode_polygon_coords(poly_coords_)
    polygons_ << poly_ if poly_
  end
  @geo_factory.multi_polygon(polygons_)
end

#_decode_point_coords(point_coords_) ⇒ Object

:nodoc:


293
294
295
296
# File 'lib/rgeo/geo_json/coder.rb', line 293

def _decode_point_coords(point_coords_)  # :nodoc:
  return nil unless point_coords_.kind_of?(::Array)
  @geo_factory.point(*(point_coords_[0...@num_coordinates].map{ |c_| c_.to_f })) rescue nil
end

#_decode_polygon_coords(poly_coords_) ⇒ Object

:nodoc:


310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
# File 'lib/rgeo/geo_json/coder.rb', line 310

def _decode_polygon_coords(poly_coords_)  # :nodoc:
  return nil unless poly_coords_.kind_of?(::Array)
  rings_ = []
  poly_coords_.each do |ring_coords_|
    return nil unless ring_coords_.kind_of?(::Array)
    points_ = []
    ring_coords_.each do |point_coords_|
      point_ = _decode_point_coords(point_coords_)
      points_ << point_ if point_
    end
    ring_ = @geo_factory.linear_ring(points_)
    rings_ << ring_ if ring_
  end
  if rings_.size == 0
    nil
  else
    @geo_factory.polygon(rings_[0], rings_[1..-1])
  end
end

#_encode_feature(object_) ⇒ Object

:nodoc:


179
180
181
182
183
184
185
186
187
188
# File 'lib/rgeo/geo_json/coder.rb', line 179

def _encode_feature(object_)  # :nodoc:
  json_ = {
    'type' => 'Feature',
    'geometry' => _encode_geometry(@entity_factory.get_feature_geometry(object_)),
    'properties' => @entity_factory.get_feature_properties(object_).dup,
  }
  id_ = @entity_factory.get_feature_id(object_)
  json_['id'] = id_ if id_
  json_
end

#_encode_geometry(object_, point_encoder_ = nil) ⇒ Object

:nodoc:


191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
# File 'lib/rgeo/geo_json/coder.rb', line 191

def _encode_geometry(object_, point_encoder_=nil)  # :nodoc:
  unless point_encoder_
    if object_.factory.property(:has_z_coordinate)
      if object_.factory.property(:has_m_coordinate)
        point_encoder_ = ::Proc.new{ |p_| [p_.x, p_.y, p_.z, p_.m] }
      else
        point_encoder_ = ::Proc.new{ |p_| [p_.x, p_.y, p_.z] }
      end
    else
      if object_.factory.property(:has_m_coordinate)
        point_encoder_ = ::Proc.new{ |p_| [p_.x, p_.y, p_.m] }
      else
        point_encoder_ = ::Proc.new{ |p_| [p_.x, p_.y] }
      end
    end
  end
  case object_
  when ::RGeo::Feature::Point
    {
      'type' => 'Point',
      'coordinates' => point_encoder_.call(object_),
    }
  when ::RGeo::Feature::LineString
    {
      'type' => 'LineString',
      'coordinates' => object_.points.map(&point_encoder_),
    }
  when ::RGeo::Feature::Polygon
    {
      'type' => 'Polygon',
      'coordinates' => [object_.exterior_ring.points.map(&point_encoder_)] + object_.interior_rings.map{ |r_| r_.points.map(&point_encoder_) }
    }
  when ::RGeo::Feature::MultiPoint
    {
      'type' => 'MultiPoint',
      'coordinates' => object_.map(&point_encoder_),
    }
  when ::RGeo::Feature::MultiLineString
    {
      'type' => 'MultiLineString',
      'coordinates' => object_.map{ |ls_| ls_.points.map(&point_encoder_) },
    }
  when ::RGeo::Feature::MultiPolygon
    {
      'type' => 'MultiPolygon',
      'coordinates' => object_.map{ |poly_| [poly_.exterior_ring.points.map(&point_encoder_)] + poly_.interior_rings.map{ |r_| r_.points.map(&point_encoder_) } },
    }
  when ::RGeo::Feature::GeometryCollection
    {
      'type' => 'GeometryCollection',
      'geometries' => object_.map{ |geom_| _encode_geometry(geom_, point_encoder_) },
    }
  else
    nil
  end
end

#decode(input_) ⇒ Object

Decode an object from GeoJSON. The input may be a JSON hash, a String, or an IO object from which to read the JSON string. If an error occurs, nil is returned.


135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
# File 'lib/rgeo/geo_json/coder.rb', line 135

def decode(input_)
  if input_.kind_of?(::IO)
    input_ = input_.read rescue nil
  end
  if input_.kind_of?(::String)
    input_ = @json_parser.call(input_) rescue nil
  end
  unless input_.kind_of?(::Hash)
    return nil
  end
  case input_['type']
  when 'FeatureCollection'
    features_ = input_['features']
    features_ = [] unless features_.kind_of?(::Array)
    decoded_features_ = []
    features_.each do |f_|
      if f_['type'] == 'Feature'
        decoded_features_ << _decode_feature(f_)
      end
    end
    @entity_factory.feature_collection(decoded_features_)
  when 'Feature'
    _decode_feature(input_)
  else
    _decode_geometry(input_)
  end
end

#encode(object_) ⇒ Object

Encode the given object as GeoJSON. The object may be one of the geometry objects specified in RGeo::Feature, or an appropriate GeoJSON wrapper entity supported by this coder's entity factory.

This method returns a JSON object (i.e. a hash). In order to generate a string suitable for transmitting to a service, you will need to JSON-encode it. This is usually accomplished by calling to_json on the hash object, if you have the appropriate JSON library installed.

Returns nil if nil is passed in as the object.


115
116
117
118
119
120
121
122
123
124
125
126
127
128
# File 'lib/rgeo/geo_json/coder.rb', line 115

def encode(object_)
  if @entity_factory.is_feature_collection?(object_)
    {
      'type' => 'FeatureCollection',
      'features' => @entity_factory.map_feature_collection(object_){ |f_| _encode_feature(f_) },
    }
  elsif @entity_factory.is_feature?(object_)
    _encode_feature(object_)
  elsif object_.nil?
    nil
  else
    _encode_geometry(object_)
  end
end

#entity_factoryObject

Returns the RGeo::GeoJSON::EntityFactory used to generate GeoJSON wrapper entities.


174
175
176
# File 'lib/rgeo/geo_json/coder.rb', line 174

def entity_factory
  @entity_factory
end

#geo_factoryObject

Returns the RGeo::Feature::Factory used to generate geometry objects.


166
167
168
# File 'lib/rgeo/geo_json/coder.rb', line 166

def geo_factory
  @geo_factory
end