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

._create(str, uses_radians) ⇒ Object



227
228
229
230
231
232
233
234
235
236
237
238
239
# File 'ext/proj4_c_impl/main.c', line 227

static VALUE cmethod_proj4_create(VALUE klass, VALUE str, VALUE uses_radians)
{
  VALUE result = Qnil;
  Check_Type(str, T_STRING);
  RGeo_Proj4Data* 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;
}

._transform_coords(from, to, x, y, z) ⇒ Object



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

static VALUE cmethod_proj4_transform(VALUE method, VALUE from, VALUE to, VALUE x, VALUE y, VALUE z)
{
  VALUE result = Qnil;
  projPJ from_pj = RGEO_PROJ4_DATA_PTR(from)->pj;
  projPJ to_pj = RGEO_PROJ4_DATA_PTR(to)->pj;
  if (from_pj && to_pj) {
    double xval = rb_num2dbl(x);
    double yval = rb_num2dbl(y);
    double zval = 0.0;
    if (!NIL_P(z)) {
      zval = rb_num2dbl(z);
    }
    int 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:



292
293
294
# File 'lib/rgeo/coord_sys/proj4.rb', line 292

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:



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
# File 'lib/rgeo/coord_sys/proj4.rb', line 264

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:



297
298
299
300
301
# File 'lib/rgeo/coord_sys/proj4.rb', line 297

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.)



186
187
188
189
190
191
192
193
194
195
196
197
198
199
# File 'lib/rgeo/coord_sys/proj4.rb', line 186

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.



206
207
208
209
210
211
212
# File 'lib/rgeo/coord_sys/proj4.rb', line 206

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)


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

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.



240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
# File 'lib/rgeo/coord_sys/proj4.rb', line 240

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.



220
221
222
223
224
225
226
227
228
229
230
231
# File 'lib/rgeo/coord_sys/proj4.rb', line 220

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

Instance Method Details

#_canonical_strObject



158
159
160
161
162
163
164
165
166
167
168
169
170
# File 'ext/proj4_c_impl/main.c', line 158

static VALUE method_proj4_canonical_str(VALUE self)
{
  VALUE result = Qnil;
  projPJ pj = RGEO_PROJ4_DATA_PTR(self)->pj;
  if (pj) {
    char* str = pj_get_def(pj, 0);
    if (str) {
      result = rb_str_new2(str);
      pj_dalloc(str);
    }
  }
  return result;
}

#_geocentric?Boolean

Returns:

  • (Boolean)


184
185
186
187
188
189
190
191
192
# File 'ext/proj4_c_impl/main.c', line 184

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

#_geographic?Boolean

Returns:

  • (Boolean)


173
174
175
176
177
178
179
180
181
# File 'ext/proj4_c_impl/main.c', line 173

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

#_get_geographicObject



131
132
133
134
135
136
137
138
139
140
141
142
143
# File 'ext/proj4_c_impl/main.c', line 131

static VALUE method_proj4_get_geographic(VALUE self)
{
  VALUE result = Qnil;
  RGeo_Proj4Data* new_data = ALLOC(RGeo_Proj4Data);
  if (new_data) {
    RGeo_Proj4Data* 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



146
147
148
149
# File 'ext/proj4_c_impl/main.c', line 146

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

#_radians?Boolean

Returns:

  • (Boolean)


152
153
154
155
# File 'ext/proj4_c_impl/main.c', line 152

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

#_valid?Boolean

Returns:

  • (Boolean)


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

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.



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

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.



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

def canonical_str
  unless defined?(@canonical_str)
    @canonical_str = _canonical_str
  end
  @canonical_str
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)


135
136
137
# File 'lib/rgeo/coord_sys/proj4.rb', line 135

def geocentric?
  _geocentric?
end

#geographic?Boolean

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

Returns:

  • (Boolean)


127
128
129
# File 'lib/rgeo/coord_sys/proj4.rb', line 127

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.



152
153
154
# File 'lib/rgeo/coord_sys/proj4.rb', line 152

def get_geographic
  _get_geographic
end

#hashObject

:nodoc:



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

def hash  # :nodoc:
  canonical_hash.hash
end

#initialize_copy(orig) ⇒ Object



110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
# File 'ext/proj4_c_impl/main.c', line 110

static VALUE method_proj4_initialize_copy(VALUE self, VALUE orig)
{
  // Clear out any existing value
  RGeo_Proj4Data* self_data = RGEO_PROJ4_DATA_PTR(self);
  projPJ pj = self_data->pj;
  if (pj) {
    pj_free(pj);
    self_data->pj = NULL;
    self_data->original_str = Qnil;
  }
  
  // Copy value from orig
  RGeo_Proj4Data* orig_data = RGEO_PROJ4_DATA_PTR(orig);
  self_data->pj = orig_data->pj;
  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

#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.



119
120
121
# File 'lib/rgeo/coord_sys/proj4.rb', line 119

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)


143
144
145
# File 'lib/rgeo/coord_sys/proj4.rb', line 143

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