Class: RGeo::CoordSys::Proj4

Inherits:
Object
  • Object
show all
Defined in:
lib/rgeo/coord_sys/proj4.rb,
ext/proj4_c_impl/main.c

Overview

This is a Ruby wrapper around a Proj4 coordinate system. It represents a single geographic coordinate system, which may be a flat projection, a geocentric (3-dimensional) coordinate system, or a geographic (latitude-longitude) coordinate system.

Generally, these are used to define the projection for a Feature::Factory. You can then convert between coordinate systems by casting geometries between such factories using the :project option. You may also use this object directly to perform low-level coordinate transformations.

Class Method Summary collapse

Instance Method Summary collapse

Class Method Details

._createObject


293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
# File 'ext/proj4_c_impl/main.c', line 293

static VALUE cmethod_proj4_create(VALUE klass, VALUE str, VALUE uses_radians)
{
  VALUE result;
  RGeo_Proj4Data* data;

  result = Qnil;
  Check_Type(str, T_STRING);
  data = ALLOC(RGeo_Proj4Data);
  if (data) {
    data->pj = pj_init_plus(RSTRING_PTR(str));
    data->original_str = str;
    data->uses_radians = RTEST(uses_radians) ? 1 : 0;
    result = Data_Wrap_Struct(klass, mark_proj4_func, destroy_proj4_func, data);
  }
  return result;
}

._proj_versionObject


255
256
257
258
# File 'ext/proj4_c_impl/main.c', line 255

static VALUE cmethod_proj4_version(VALUE module)
{
  return INT2NUM(PJ_VERSION);
}

._transform_coordsObject


261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
# File 'ext/proj4_c_impl/main.c', line 261

static VALUE cmethod_proj4_transform(VALUE module, VALUE from, VALUE to, VALUE x, VALUE y, VALUE z)
{
  VALUE result;
  projPJ from_pj;
  projPJ to_pj;
  double xval, yval, zval;
  int err;

  result = Qnil;
  from_pj = RGEO_PROJ4_DATA_PTR(from)->pj;
  to_pj = RGEO_PROJ4_DATA_PTR(to)->pj;
  if (from_pj && to_pj) {
    xval = rb_num2dbl(x);
    yval = rb_num2dbl(y);
    zval = 0.0;
    if (!NIL_P(z)) {
      zval = rb_num2dbl(z);
    }
    err = pj_transform(from_pj, to_pj, 1, 1, &xval, &yval, NIL_P(z) ? NULL : &zval);
    if (!err && xval != HUGE_VAL && yval != HUGE_VAL && (NIL_P(z) || zval != HUGE_VAL)) {
      result = rb_ary_new2(NIL_P(z) ? 2 : 3);
      rb_ary_push(result, rb_float_new(xval));
      rb_ary_push(result, rb_float_new(yval));
      if (!NIL_P(z)) {
        rb_ary_push(result, rb_float_new(zval));
      }
    }
  }
  return result;
}

._transform_linear_ring(from_proj_, from_ring_, to_proj_, to_factory_) ⇒ Object

:nodoc:


360
361
362
# File 'lib/rgeo/coord_sys/proj4.rb', line 360

def _transform_linear_ring(from_proj_, from_ring_, to_proj_, to_factory_)  # :nodoc:
  to_factory_.linear_ring(from_ring_.points[0..-2].map{ |p_| _transform_point(from_proj_, p_, to_proj_, to_factory_) })
end

._transform_point(from_proj_, from_point_, to_proj_, to_factory_) ⇒ Object

:nodoc:


332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
# File 'lib/rgeo/coord_sys/proj4.rb', line 332

def _transform_point(from_proj_, from_point_, to_proj_, to_factory_)  # :nodoc:
  from_factory_ = from_point_.factory
  from_has_z_ = from_factory_.property(:has_z_coordinate)
  from_has_m_ = from_factory_.property(:has_m_coordinate)
  to_has_z_ = to_factory_.property(:has_z_coordinate)
  to_has_m_ = to_factory_.property(:has_m_coordinate)
  x_ = from_point_.x
  y_ = from_point_.y
  if !from_proj_._radians? && from_proj_._geographic?
    x_ *= ImplHelper::Math::RADIANS_PER_DEGREE
    y_ *= ImplHelper::Math::RADIANS_PER_DEGREE
  end
  coords_ = _transform_coords(from_proj_, to_proj_, x_, y_, from_has_z_ ? from_point_.z : nil)
  if coords_
    if !to_proj_._radians? && to_proj_._geographic?
      coords_[0] *= ImplHelper::Math::DEGREES_PER_RADIAN
      coords_[1] *= ImplHelper::Math::DEGREES_PER_RADIAN
    end
    extras_ = []
    extras_ << coords_[2].to_f if to_has_z_
    extras_ << from_has_m_ ? from_point_.m : 0.0 if to_has_m_
    to_factory_.point(coords_[0], coords_[1], *extras_)
  else
    nil
  end
end

._transform_polygon(from_proj_, from_polygon_, to_proj_, to_factory_) ⇒ Object

:nodoc:


365
366
367
368
369
# File 'lib/rgeo/coord_sys/proj4.rb', line 365

def _transform_polygon(from_proj_, from_polygon_, to_proj_, to_factory_)  # :nodoc:
  ext_ = _transform_linear_ring(from_proj_, from_polygon_.exterior_ring, to_proj_, to_factory_)
  int_ = from_polygon_.interior_rings.map{ |r_| _transform_linear_ring(from_proj_, r_, to_proj_, to_factory_) }
  to_factory_.polygon(ext_, int_)
end

.create(defn_, opts_ = {}) ⇒ Object

Create a new Proj4 object, given a definition, which may be either a string or a hash. Returns nil if the given definition is invalid or Proj4 is not supported.

Recognized options include:

:radians

If set to true, then this proj4 will represent geographic (latitude/longitude) coordinates in radians rather than degrees. If this is a geographic coordinate system, then its units will be in radians. If this is a projected coordinate system, then its units will be unchanged, but any geographic coordinate system obtained using get_geographic will use radians as its units. If this is a geocentric or other type of coordinate system, this has no effect. Default is false. (That is all coordinates are in degrees by default.)


241
242
243
244
245
246
247
248
249
250
251
252
253
254
# File 'lib/rgeo/coord_sys/proj4.rb', line 241

def create(defn_, opts_={})
  result_ = nil
  if supported?
    if defn_.kind_of?(::Hash)
      defn_ = defn_.map{ |k_, v_| v_ ? "+#{k_}=#{v_}" : "+#{k_}" }.join(' ')
    end
    unless defn_ =~ /^\s*\+/
      defn_ = defn_.sub(/^(\s*)/, '\1+').gsub(/(\s+)([^+\s])/, '\1+\2')
    end
    result_ = _create(defn_, opts_[:radians])
    result_ = nil unless result_._valid?
  end
  result_
end

.new(defn_, opts_ = {}) ⇒ Object

Create a new Proj4 object, given a definition, which may be either a string or a hash. Raises Error::UnsupportedOperation if the given definition is invalid or Proj4 is not supported.

Recognized options include:

:radians

If set to true, then this proj4 will represent geographic (latitude/longitude) coordinates in radians rather than degrees. If this is a geographic coordinate system, then its units will be in radians. If this is a projected coordinate system, then its units will be unchanged, but any geographic coordinate system obtained using get_geographic will use radians as its units. If this is a geocentric or other type of coordinate system, this has no effect. Default is false. (That is all coordinates are in degrees by default.)


274
275
276
277
278
279
280
# File 'lib/rgeo/coord_sys/proj4.rb', line 274

def new(defn_, opts_={})
  result_ = create(defn_, opts_)
  unless result_
    raise Error::UnsupportedOperation, "Proj4 not supported in this installation"
  end
  result_
end

.supported?Boolean

Returns true if Proj4 is supported in this installation. If this returns false, the other methods such as create will not work.

Returns:

  • (Boolean)

194
195
196
# File 'lib/rgeo/coord_sys/proj4.rb', line 194

def supported?
  respond_to?(:_create)
end

.transform(from_proj_, from_geometry_, to_proj_, to_factory_) ⇒ Object

Low-level geometry transform method. Transforms the given geometry between the given two projections. The resulting geometry is constructed using the to_factory. Any projections associated with the factories themselves are ignored.


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

def transform(from_proj_, from_geometry_, to_proj_, to_factory_)
  case from_geometry_
  when Feature::Point
    _transform_point(from_proj_, from_geometry_, to_proj_, to_factory_)
  when Feature::Line
    to_factory_.line(from_geometry_.points.map{ |p_| _transform_point(from_proj_, p_, to_proj_, to_factory_) })
  when Feature::LinearRing
    _transform_linear_ring(from_proj_, from_geometry_, to_proj_, to_factory_)
  when Feature::LineString
    to_factory_.line_string(from_geometry_.points.map{ |p_| _transform_point(from_proj_, p_, to_proj_, to_factory_) })
  when Feature::Polygon
    _transform_polygon(from_proj_, from_geometry_, to_proj_, to_factory_)
  when Feature::MultiPoint
    to_factory_.multi_point(from_geometry_.map{ |p_| _transform_point(from_proj_, p_, to_proj_, to_factory_) })
  when Feature::MultiLineString
    to_factory_.multi_line_string(from_geometry_.map{ |g_| transform(from_proj_, g_, to_proj_, to_factory_) })
  when Feature::MultiPolygon
    to_factory_.multi_polygon(from_geometry_.map{ |p_| _transform_polygon(from_proj_, p_, to_proj_, to_factory_) })
  when Feature::GeometryCollection
    to_factory_.collection(from_geometry_.map{ |g_| transform(from_proj_, g_, to_proj_, to_factory_) })
  end
end

.transform_coords(from_proj_, to_proj_, x_, y_, z_ = nil) ⇒ Object

Low-level coordinate transform method. Transforms the given coordinate (x, y, [z]) from one proj4 coordinate system to another. Returns an array with either two or three elements.


288
289
290
291
292
293
294
295
296
297
298
299
# File 'lib/rgeo/coord_sys/proj4.rb', line 288

def transform_coords(from_proj_, to_proj_, x_, y_, z_=nil)
  if !from_proj_._radians? && from_proj_._geographic?
    x_ *= ImplHelper::Math::RADIANS_PER_DEGREE
    y_ *= ImplHelper::Math::RADIANS_PER_DEGREE
  end
  result_ = _transform_coords(from_proj_, to_proj_, x_, y_, z_)
  if result_ && !to_proj_._radians? && to_proj_._geographic?
    result_[0] *= ImplHelper::Math::DEGREES_PER_RADIAN
    result_[1] *= ImplHelper::Math::DEGREES_PER_RADIAN
  end
  result_
end

.versionObject

Returns the Proj4 library version as a Versionomy object if the Versionomy library is available; otherwise as a string of the format “x.y.z”. Returns nil if Proj4 is not available.


215
216
217
218
219
220
221
# File 'lib/rgeo/coord_sys/proj4.rb', line 215

def version
  unless defined?(@version)
    str_ = version_string
    @version = str_ && defined?(::Versionomy) ? ::Versionomy.parse(str_) : str_
  end
  @version
end

.version_stringObject

Returns the Proj library version as a string of the format “x.y.z”. Returns nil if Proj4 is not available.


202
203
204
205
206
207
# File 'lib/rgeo/coord_sys/proj4.rb', line 202

def version_string
  unless defined?(@version_string)
    @version_string = respond_to?(:_proj_version) ? _proj_version.to_s.split('').join('.') : nil
  end
  @version_string
end

Instance Method Details

#_canonical_strObject


202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
# File 'ext/proj4_c_impl/main.c', line 202

static VALUE method_proj4_canonical_str(VALUE self)
{
  VALUE result;
  projPJ pj;
  char* str;

  result = Qnil;
  pj = RGEO_PROJ4_DATA_PTR(self)->pj;
  if (pj) {
    str = pj_get_def(pj, 0);
    if (str) {
      result = rb_str_new2(str);
      pj_dalloc(str);
    }
  }
  return result;
}

#_geocentric?Boolean

Returns:

  • (Boolean)

235
236
237
238
239
240
241
242
243
244
245
246
# File 'ext/proj4_c_impl/main.c', line 235

static VALUE method_proj4_is_geocentric(VALUE self)
{
  VALUE result;
  projPJ pj;

  result = Qnil;
  pj = RGEO_PROJ4_DATA_PTR(self)->pj;
  if (pj) {
    result = pj_is_geocent(pj) ? Qtrue : Qfalse;
  }
  return result;
}

#_geographic?Boolean

Returns:

  • (Boolean)

221
222
223
224
225
226
227
228
229
230
231
232
# File 'ext/proj4_c_impl/main.c', line 221

static VALUE method_proj4_is_geographic(VALUE self)
{
  VALUE result;
  projPJ pj;

  result = Qnil;
  pj = RGEO_PROJ4_DATA_PTR(self)->pj;
  if (pj) {
    result = pj_is_latlong(pj) ? Qtrue : Qfalse;
  }
  return result;
}

#_get_geographicObject


171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
# File 'ext/proj4_c_impl/main.c', line 171

static VALUE method_proj4_get_geographic(VALUE self)
{
  VALUE result;
  RGeo_Proj4Data* new_data;
  RGeo_Proj4Data* self_data;

  result = Qnil;
  new_data = ALLOC(RGeo_Proj4Data);
  if (new_data) {
    self_data = RGEO_PROJ4_DATA_PTR(self);
    new_data->pj = pj_latlong_from_proj(self_data->pj);
    new_data->original_str = Qnil;
    new_data->uses_radians = self_data->uses_radians;
    result = Data_Wrap_Struct(CLASS_OF(self), mark_proj4_func, destroy_proj4_func, new_data);
  }
  return result;
}

#_original_strObject


190
191
192
193
# File 'ext/proj4_c_impl/main.c', line 190

static VALUE method_proj4_original_str(VALUE self)
{
  return RGEO_PROJ4_DATA_PTR(self)->original_str;
}

#_radians?Boolean

Returns:

  • (Boolean)

196
197
198
199
# File 'ext/proj4_c_impl/main.c', line 196

static VALUE method_proj4_uses_radians(VALUE self)
{
  return RGEO_PROJ4_DATA_PTR(self)->uses_radians ? Qtrue : Qfalse;
}

#_set_valueObject


146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
# File 'ext/proj4_c_impl/main.c', line 146

static VALUE method_proj4_set_value(VALUE self, VALUE str, VALUE uses_radians)
{
  RGeo_Proj4Data* self_data;
  projPJ pj;

  Check_Type(str, T_STRING);

  // Clear out any existing value
  self_data = RGEO_PROJ4_DATA_PTR(self);
  pj = self_data->pj;
  if (pj) {
    pj_free(pj);
    self_data->pj = NULL;
    self_data->original_str = Qnil;
  }

  // Set new data
  self_data->pj = pj_init_plus(RSTRING_PTR(str));
  self_data->original_str = str;
  self_data->uses_radians = RTEST(uses_radians) ? 1 : 0;

  return self;
}

#_valid?Boolean

Returns:

  • (Boolean)

249
250
251
252
# File 'ext/proj4_c_impl/main.c', line 249

static VALUE method_proj4_is_valid(VALUE self)
{
  return RGEO_PROJ4_DATA_PTR(self)->pj ? Qtrue : Qfalse;
}

#canonical_hashObject

Returns the “canonical” hash definition for this coordinate system, as reported by Proj4. This may be slightly different from the definition used to construct this object.


132
133
134
135
136
137
138
139
140
141
142
# File 'lib/rgeo/coord_sys/proj4.rb', line 132

def canonical_hash
  unless defined?(@canonical_hash)
    @canonical_hash = {}
    canonical_str.strip.split(/\s+/).each do |elem_|
      if elem_ =~ /^\+(\w+)(=(\S+))?$/
        @canonical_hash[$1] = $3
      end
    end
  end
  @canonical_hash
end

#canonical_strObject

Returns the “canonical” string definition for this coordinate system, as reported by Proj4. This may be slightly different from the definition used to construct this object.


117
118
119
120
121
122
123
124
125
# File 'lib/rgeo/coord_sys/proj4.rb', line 117

def canonical_str
  unless defined?(@canonical_str)
    @canonical_str = _canonical_str
    if @canonical_str.respond_to?(:force_encoding)
      @canonical_str.force_encoding('US-ASCII')
    end
  end
  @canonical_str
end

#encode_with(coder_) ⇒ Object

Psych support


99
100
101
102
# File 'lib/rgeo/coord_sys/proj4.rb', line 99

def encode_with(coder_)  # :nodoc:
  coder_['proj4'] = original_str || canonical_str
  coder_['radians'] = radians?
end

#eql?(rhs_) ⇒ Boolean Also known as: ==

Returns true if this Proj4 is equivalent to the given Proj4.

Note: this tests for equivalence by comparing only the hash definitions of the Proj4 objects, and returning true if those definitions are equivalent. In some cases, this may still return false even if the actual coordinate systems are identical, since there are sometimes multiple ways to express a given coordinate system.

Returns:

  • (Boolean)

80
81
82
# File 'lib/rgeo/coord_sys/proj4.rb', line 80

def eql?(rhs_)
  rhs_.class == self.class && rhs_.canonical_hash == canonical_hash && rhs_._radians? == _radians?
end

#geocentric?Boolean

Returns true if this Proj4 object is a geocentric (3dz) coordinate system.

Returns:

  • (Boolean)

165
166
167
# File 'lib/rgeo/coord_sys/proj4.rb', line 165

def geocentric?
  _geocentric?
end

#geographic?Boolean

Returns true if this Proj4 object is a geographic (lat-long) coordinate system.

Returns:

  • (Boolean)

157
158
159
# File 'lib/rgeo/coord_sys/proj4.rb', line 157

def geographic?
  _geographic?
end

#get_geographicObject

Get the geographic (unprojected lat-long) coordinate system corresponding to this coordinate system; i.e. the one that uses the same ellipsoid and datum.


182
183
184
# File 'lib/rgeo/coord_sys/proj4.rb', line 182

def get_geographic
  _get_geographic
end

#hashObject

:nodoc:


66
67
68
# File 'lib/rgeo/coord_sys/proj4.rb', line 66

def hash  # :nodoc:
  @hash ||= canonical_hash.hash
end

#init_with(coder_) ⇒ Object

:nodoc:


104
105
106
107
108
109
110
# File 'lib/rgeo/coord_sys/proj4.rb', line 104

def init_with(coder_)  # :nodoc:
  if coder_.type == :scalar
    _set_value(coder_.scalar, false)
  else
    _set_value(coder_['proj4'], coder_['radians'])
  end
end

#initialize_copyObject


113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
# File 'ext/proj4_c_impl/main.c', line 113

static VALUE method_proj4_initialize_copy(VALUE self, VALUE orig)
{
  RGeo_Proj4Data* self_data;
  projPJ pj;
  RGeo_Proj4Data* orig_data;
  char* str;

  // Clear out any existing value
  self_data = RGEO_PROJ4_DATA_PTR(self);
  pj = self_data->pj;
  if (pj) {
    pj_free(pj);
    self_data->pj = NULL;
    self_data->original_str = Qnil;
  }

  // Copy value from orig
  orig_data = RGEO_PROJ4_DATA_PTR(orig);
  if (!NIL_P(orig_data->original_str)) {
    self_data->pj = pj_init_plus(RSTRING_PTR(orig_data->original_str));
  }
  else {
    str = pj_get_def(orig_data->pj, 0);
    self_data->pj = pj_init_plus(str);
    pj_dalloc(str);
  }
  self_data->original_str = orig_data->original_str;
  self_data->uses_radians = orig_data->uses_radians;

  return self;
}

#inspectObject

:nodoc:


56
57
58
# File 'lib/rgeo/coord_sys/proj4.rb', line 56

def inspect  # :nodoc:
  "#<#{self.class}:0x#{object_id.to_s(16)} #{canonical_str.inspect}>"
end

#marshal_dumpObject

Marshal support


88
89
90
# File 'lib/rgeo/coord_sys/proj4.rb', line 88

def marshal_dump  # :nodoc:
  {'rad' => radians?, 'str' => original_str || canonical_str}
end

#marshal_load(data_) ⇒ Object

:nodoc:


92
93
94
# File 'lib/rgeo/coord_sys/proj4.rb', line 92

def marshal_load(data_)  # :nodoc:
  _set_value(data_['str'], data_['rad'])
end

#original_strObject

Returns the string definition originally used to construct this object. Returns nil if this object wasn't created by a string definition; i.e. if it was created using get_geographic.


149
150
151
# File 'lib/rgeo/coord_sys/proj4.rb', line 149

def original_str
  _original_str
end

#radians?Boolean

Returns true if this Proj4 object uses radians rather than degrees if it is a geographic coordinate system.

Returns:

  • (Boolean)

173
174
175
# File 'lib/rgeo/coord_sys/proj4.rb', line 173

def radians?
  _radians?
end

#to_sObject

:nodoc:


61
62
63
# File 'lib/rgeo/coord_sys/proj4.rb', line 61

def to_s  # :nodoc:
  canonical_str
end