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



260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
# File 'ext/proj4_c_impl/main.c', line 260

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



222
223
224
225
# File 'ext/proj4_c_impl/main.c', line 222

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

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



228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
# File 'ext/proj4_c_impl/main.c', line 228

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:



281
282
283
# File 'lib/rgeo/coord_sys/proj4.rb', line 281

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:



256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
# File 'lib/rgeo/coord_sys/proj4.rb', line 256

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_)
  end
end

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

:nodoc:



285
286
287
288
289
# File 'lib/rgeo/coord_sys/proj4.rb', line 285

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



169
170
171
172
173
174
175
176
177
178
179
180
181
182
# File 'lib/rgeo/coord_sys/proj4.rb', line 169

def create(defn_, opts_ = {})
  result_ = nil
  if supported?
    if defn_.is_a?(::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.)



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

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)


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

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.



233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
# File 'lib/rgeo/coord_sys/proj4.rb', line 233

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.



214
215
216
217
218
219
220
221
222
223
224
225
# File 'lib/rgeo/coord_sys/proj4.rb', line 214

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 Proj library version as a string of the format “x.y.z”.



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

def version
  ::RGeo::VERSION
end

Instance Method Details

#_canonical_strObject



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

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)


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

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)


188
189
190
191
192
193
194
195
196
197
198
199
# File 'ext/proj4_c_impl/main.c', line 188

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



138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
# File 'ext/proj4_c_impl/main.c', line 138

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



157
158
159
160
# File 'ext/proj4_c_impl/main.c', line 157

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

#_radians?Boolean

Returns:

  • (Boolean)


163
164
165
166
# File 'ext/proj4_c_impl/main.c', line 163

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

#_set_value(str, uses_radians) ⇒ Object



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

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)


216
217
218
219
# File 'ext/proj4_c_impl/main.c', line 216

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.



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

def canonical_hash
  unless defined?(@canonical_hash)
    @canonical_hash = {}
    canonical_str.strip.split(/\s+/).each do |elem_|
      @canonical_hash[Regexp.last_match(1)] = Regexp.last_match(3) if elem_ =~ /^\+(\w+)(=(\S+))?$/
    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.



76
77
78
79
80
81
82
83
84
# File 'lib/rgeo/coord_sys/proj4.rb', line 76

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



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

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)


42
43
44
# File 'lib/rgeo/coord_sys/proj4.rb', line 42

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)


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

def geocentric?
  _geocentric?
end

#geographic?Boolean

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

Returns:

  • (Boolean)


111
112
113
# File 'lib/rgeo/coord_sys/proj4.rb', line 111

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.



133
134
135
# File 'lib/rgeo/coord_sys/proj4.rb', line 133

def get_geographic
  _get_geographic
end

#hashObject

:nodoc:



29
30
31
# File 'lib/rgeo/coord_sys/proj4.rb', line 29

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

#init_with(coder_) ⇒ Object

:nodoc:



64
65
66
67
68
69
70
# File 'lib/rgeo/coord_sys/proj4.rb', line 64

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

#initialize_copy(orig) ⇒ Object



80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
# File 'ext/proj4_c_impl/main.c', line 80

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:



21
22
23
# File 'lib/rgeo/coord_sys/proj4.rb', line 21

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

#marshal_dumpObject

Marshal support



49
50
51
# File 'lib/rgeo/coord_sys/proj4.rb', line 49

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

#marshal_load(data_) ⇒ Object

:nodoc:



53
54
55
# File 'lib/rgeo/coord_sys/proj4.rb', line 53

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.



104
105
106
# File 'lib/rgeo/coord_sys/proj4.rb', line 104

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)


125
126
127
# File 'lib/rgeo/coord_sys/proj4.rb', line 125

def radians?
  _radians?
end

#to_sObject

:nodoc:



25
26
27
# File 'lib/rgeo/coord_sys/proj4.rb', line 25

def to_s  # :nodoc:
  canonical_str
end