rgeo-proj4 1.0.0.rc1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: b9c7651625154609a01979271df45bd5229be4c1c78b10834540981408bf8240
4
+ data.tar.gz: 53a6f93a200983b08020f8bea256c575a27e061b78f172a03d51caba53852442
5
+ SHA512:
6
+ metadata.gz: 24b00ed0eb17c8e77ea5fa1efd0eedc91468c6c79ee89d06ee0940cd613a3c382942e2f881c42d66a00281c3653f566d94524a05af6de388021631e06e04a376
7
+ data.tar.gz: 16e019ec27581cc9bca711beca05558339c43a2e919847861be79e96fd4a8c88cf592a3ddf91490dc47ab06099c1508cb26f695d3a535759371ae325fdb0788b
data/LICENSE.txt ADDED
@@ -0,0 +1,21 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2017 Tee Parham, Daniel Azuma
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in
13
+ all copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21
+ THE SOFTWARE.
@@ -0,0 +1,62 @@
1
+ # -----------------------------------------------------------------------------
2
+ #
3
+ # Makefile builder for Proj4 wrapper
4
+ #
5
+ # -----------------------------------------------------------------------------
6
+
7
+ if ::RUBY_DESCRIPTION =~ /^jruby\s/
8
+
9
+ ::File.open("Makefile", "w") { |f_| f_.write(".PHONY: install\ninstall:\n") }
10
+
11
+ else
12
+
13
+ require "mkmf"
14
+
15
+ header_dirs_ =
16
+ [
17
+ ::RbConfig::CONFIG["includedir"],
18
+ "/usr/local/include",
19
+ "/usr/local/proj/include",
20
+ "/usr/local/proj4/include",
21
+ "/opt/local/include",
22
+ "/opt/proj/include",
23
+ "/opt/proj4/include",
24
+ "/opt/include",
25
+ "/Library/Frameworks/PROJ.framework/unix/include",
26
+ "/usr/include"
27
+ ]
28
+ lib_dirs_ =
29
+ [
30
+ ::RbConfig::CONFIG["libdir"],
31
+ "/usr/local/lib",
32
+ "/usr/local/lib64",
33
+ "/usr/local/proj/lib",
34
+ "/usr/local/proj4/lib",
35
+ "/opt/local/lib",
36
+ "/opt/proj/lib",
37
+ "/opt/proj4/lib",
38
+ "/opt/lib",
39
+ "/Library/Frameworks/PROJ.framework/unix/lib",
40
+ "/usr/lib",
41
+ "/usr/lib64"
42
+ ]
43
+ header_dirs_.delete_if { |path_| !::File.directory?(path_) }
44
+ lib_dirs_.delete_if { |path_| !::File.directory?(path_) }
45
+
46
+ found_proj_ = false
47
+ header_dirs_, lib_dirs_ = dir_config("proj", header_dirs_, lib_dirs_)
48
+ if have_header("proj_api.h")
49
+ $libs << " -lproj"
50
+ if have_func("pj_init_plus", "proj_api.h")
51
+ found_proj_ = true
52
+ else
53
+ $libs.gsub!(" -lproj", "")
54
+ end
55
+ end
56
+ unless found_proj_
57
+ puts "**** WARNING: Unable to find Proj headers or Proj version is too old."
58
+ puts "**** Compiling without Proj support."
59
+ end
60
+ create_makefile("rgeo/coord_sys/proj4_c_impl")
61
+
62
+ end
@@ -0,0 +1,315 @@
1
+ /*
2
+ Main initializer for Proj4 wrapper
3
+ */
4
+
5
+ #ifdef HAVE_PROJ_API_H
6
+ #ifdef HAVE_PJ_INIT_PLUS
7
+ #define RGEO_PROJ4_SUPPORTED
8
+ #endif
9
+ #endif
10
+
11
+ #ifdef __cplusplus
12
+ #define RGEO_BEGIN_C extern "C" {
13
+ #define RGEO_END_C }
14
+ #else
15
+ #define RGEO_BEGIN_C
16
+ #define RGEO_END_C
17
+ #endif
18
+
19
+
20
+ #ifdef RGEO_PROJ4_SUPPORTED
21
+
22
+ #include <ruby.h>
23
+ #include <proj_api.h>
24
+
25
+ #endif
26
+
27
+
28
+ RGEO_BEGIN_C
29
+
30
+
31
+ #ifdef RGEO_PROJ4_SUPPORTED
32
+
33
+
34
+ typedef struct {
35
+ projPJ pj;
36
+ VALUE original_str;
37
+ char uses_radians;
38
+ } RGeo_Proj4Data;
39
+
40
+
41
+ #define RGEO_PROJ4_DATA_PTR(obj) ((RGeo_Proj4Data*)DATA_PTR(obj))
42
+
43
+
44
+ // Destroy function for proj data.
45
+
46
+ static void destroy_proj4_func(RGeo_Proj4Data* data)
47
+ {
48
+ if (data->pj) {
49
+ pj_free(data->pj);
50
+ }
51
+ free(data);
52
+ }
53
+
54
+
55
+ static void mark_proj4_func(RGeo_Proj4Data* data)
56
+ {
57
+ if (!NIL_P(data->original_str)) {
58
+ rb_gc_mark(data->original_str);
59
+ }
60
+ }
61
+
62
+
63
+ static VALUE alloc_proj4(VALUE klass)
64
+ {
65
+ VALUE result;
66
+ RGeo_Proj4Data* data;
67
+
68
+ result = Qnil;
69
+ data = ALLOC(RGeo_Proj4Data);
70
+ if (data) {
71
+ data->pj = NULL;
72
+ data->original_str = Qnil;
73
+ data->uses_radians = 0;
74
+ result = Data_Wrap_Struct(klass, mark_proj4_func, destroy_proj4_func, data);
75
+ }
76
+ return result;
77
+ }
78
+
79
+
80
+ static VALUE method_proj4_initialize_copy(VALUE self, VALUE orig)
81
+ {
82
+ RGeo_Proj4Data* self_data;
83
+ projPJ pj;
84
+ RGeo_Proj4Data* orig_data;
85
+ char* str;
86
+
87
+ // Clear out any existing value
88
+ self_data = RGEO_PROJ4_DATA_PTR(self);
89
+ pj = self_data->pj;
90
+ if (pj) {
91
+ pj_free(pj);
92
+ self_data->pj = NULL;
93
+ self_data->original_str = Qnil;
94
+ }
95
+
96
+ // Copy value from orig
97
+ orig_data = RGEO_PROJ4_DATA_PTR(orig);
98
+ if (!NIL_P(orig_data->original_str)) {
99
+ self_data->pj = pj_init_plus(RSTRING_PTR(orig_data->original_str));
100
+ }
101
+ else {
102
+ str = pj_get_def(orig_data->pj, 0);
103
+ self_data->pj = pj_init_plus(str);
104
+ pj_dalloc(str);
105
+ }
106
+ self_data->original_str = orig_data->original_str;
107
+ self_data->uses_radians = orig_data->uses_radians;
108
+
109
+ return self;
110
+ }
111
+
112
+
113
+ static VALUE method_proj4_set_value(VALUE self, VALUE str, VALUE uses_radians)
114
+ {
115
+ RGeo_Proj4Data* self_data;
116
+ projPJ pj;
117
+
118
+ Check_Type(str, T_STRING);
119
+
120
+ // Clear out any existing value
121
+ self_data = RGEO_PROJ4_DATA_PTR(self);
122
+ pj = self_data->pj;
123
+ if (pj) {
124
+ pj_free(pj);
125
+ self_data->pj = NULL;
126
+ self_data->original_str = Qnil;
127
+ }
128
+
129
+ // Set new data
130
+ self_data->pj = pj_init_plus(RSTRING_PTR(str));
131
+ self_data->original_str = str;
132
+ self_data->uses_radians = RTEST(uses_radians) ? 1 : 0;
133
+
134
+ return self;
135
+ }
136
+
137
+
138
+ static VALUE method_proj4_get_geographic(VALUE self)
139
+ {
140
+ VALUE result;
141
+ RGeo_Proj4Data* new_data;
142
+ RGeo_Proj4Data* self_data;
143
+
144
+ result = Qnil;
145
+ new_data = ALLOC(RGeo_Proj4Data);
146
+ if (new_data) {
147
+ self_data = RGEO_PROJ4_DATA_PTR(self);
148
+ new_data->pj = pj_latlong_from_proj(self_data->pj);
149
+ new_data->original_str = Qnil;
150
+ new_data->uses_radians = self_data->uses_radians;
151
+ result = Data_Wrap_Struct(CLASS_OF(self), mark_proj4_func, destroy_proj4_func, new_data);
152
+ }
153
+ return result;
154
+ }
155
+
156
+
157
+ static VALUE method_proj4_original_str(VALUE self)
158
+ {
159
+ return RGEO_PROJ4_DATA_PTR(self)->original_str;
160
+ }
161
+
162
+
163
+ static VALUE method_proj4_uses_radians(VALUE self)
164
+ {
165
+ return RGEO_PROJ4_DATA_PTR(self)->uses_radians ? Qtrue : Qfalse;
166
+ }
167
+
168
+
169
+ static VALUE method_proj4_canonical_str(VALUE self)
170
+ {
171
+ VALUE result;
172
+ projPJ pj;
173
+ char* str;
174
+
175
+ result = Qnil;
176
+ pj = RGEO_PROJ4_DATA_PTR(self)->pj;
177
+ if (pj) {
178
+ str = pj_get_def(pj, 0);
179
+ if (str) {
180
+ result = rb_str_new2(str);
181
+ pj_dalloc(str);
182
+ }
183
+ }
184
+ return result;
185
+ }
186
+
187
+
188
+ static VALUE method_proj4_is_geographic(VALUE self)
189
+ {
190
+ VALUE result;
191
+ projPJ pj;
192
+
193
+ result = Qnil;
194
+ pj = RGEO_PROJ4_DATA_PTR(self)->pj;
195
+ if (pj) {
196
+ result = pj_is_latlong(pj) ? Qtrue : Qfalse;
197
+ }
198
+ return result;
199
+ }
200
+
201
+
202
+ static VALUE method_proj4_is_geocentric(VALUE self)
203
+ {
204
+ VALUE result;
205
+ projPJ pj;
206
+
207
+ result = Qnil;
208
+ pj = RGEO_PROJ4_DATA_PTR(self)->pj;
209
+ if (pj) {
210
+ result = pj_is_geocent(pj) ? Qtrue : Qfalse;
211
+ }
212
+ return result;
213
+ }
214
+
215
+
216
+ static VALUE method_proj4_is_valid(VALUE self)
217
+ {
218
+ return RGEO_PROJ4_DATA_PTR(self)->pj ? Qtrue : Qfalse;
219
+ }
220
+
221
+
222
+ static VALUE cmethod_proj4_version(VALUE module)
223
+ {
224
+ return INT2NUM(PJ_VERSION);
225
+ }
226
+
227
+
228
+ static VALUE cmethod_proj4_transform(VALUE module, VALUE from, VALUE to, VALUE x, VALUE y, VALUE z)
229
+ {
230
+ VALUE result;
231
+ projPJ from_pj;
232
+ projPJ to_pj;
233
+ double xval, yval, zval;
234
+ int err;
235
+
236
+ result = Qnil;
237
+ from_pj = RGEO_PROJ4_DATA_PTR(from)->pj;
238
+ to_pj = RGEO_PROJ4_DATA_PTR(to)->pj;
239
+ if (from_pj && to_pj) {
240
+ xval = rb_num2dbl(x);
241
+ yval = rb_num2dbl(y);
242
+ zval = 0.0;
243
+ if (!NIL_P(z)) {
244
+ zval = rb_num2dbl(z);
245
+ }
246
+ err = pj_transform(from_pj, to_pj, 1, 1, &xval, &yval, NIL_P(z) ? NULL : &zval);
247
+ if (!err && xval != HUGE_VAL && yval != HUGE_VAL && (NIL_P(z) || zval != HUGE_VAL)) {
248
+ result = rb_ary_new2(NIL_P(z) ? 2 : 3);
249
+ rb_ary_push(result, rb_float_new(xval));
250
+ rb_ary_push(result, rb_float_new(yval));
251
+ if (!NIL_P(z)) {
252
+ rb_ary_push(result, rb_float_new(zval));
253
+ }
254
+ }
255
+ }
256
+ return result;
257
+ }
258
+
259
+
260
+ static VALUE cmethod_proj4_create(VALUE klass, VALUE str, VALUE uses_radians)
261
+ {
262
+ VALUE result;
263
+ RGeo_Proj4Data* data;
264
+
265
+ result = Qnil;
266
+ Check_Type(str, T_STRING);
267
+ data = ALLOC(RGeo_Proj4Data);
268
+ if (data) {
269
+ data->pj = pj_init_plus(RSTRING_PTR(str));
270
+ data->original_str = str;
271
+ data->uses_radians = RTEST(uses_radians) ? 1 : 0;
272
+ result = Data_Wrap_Struct(klass, mark_proj4_func, destroy_proj4_func, data);
273
+ }
274
+ return result;
275
+ }
276
+
277
+
278
+ static void rgeo_init_proj4()
279
+ {
280
+ VALUE rgeo_module;
281
+ VALUE coordsys_module;
282
+ VALUE proj4_class;
283
+
284
+ rgeo_module = rb_define_module("RGeo");
285
+ coordsys_module = rb_define_module_under(rgeo_module, "CoordSys");
286
+ proj4_class = rb_define_class_under(coordsys_module, "Proj4", rb_cObject);
287
+
288
+ rb_define_alloc_func(proj4_class, alloc_proj4);
289
+ rb_define_module_function(proj4_class, "_create", cmethod_proj4_create, 2);
290
+ rb_define_method(proj4_class, "initialize_copy", method_proj4_initialize_copy, 1);
291
+ rb_define_method(proj4_class, "_set_value", method_proj4_set_value, 2);
292
+ rb_define_method(proj4_class, "_original_str", method_proj4_original_str, 0);
293
+ rb_define_method(proj4_class, "_canonical_str", method_proj4_canonical_str, 0);
294
+ rb_define_method(proj4_class, "_valid?", method_proj4_is_valid, 0);
295
+ rb_define_method(proj4_class, "_geographic?", method_proj4_is_geographic, 0);
296
+ rb_define_method(proj4_class, "_geocentric?", method_proj4_is_geocentric, 0);
297
+ rb_define_method(proj4_class, "_radians?", method_proj4_uses_radians, 0);
298
+ rb_define_method(proj4_class, "_get_geographic", method_proj4_get_geographic, 0);
299
+ rb_define_module_function(proj4_class, "_transform_coords", cmethod_proj4_transform, 5);
300
+ rb_define_module_function(proj4_class, "_proj_version", cmethod_proj4_version, 0);
301
+ }
302
+
303
+
304
+ #endif
305
+
306
+
307
+ void Init_proj4_c_impl()
308
+ {
309
+ #ifdef RGEO_PROJ4_SUPPORTED
310
+ rgeo_init_proj4();
311
+ #endif
312
+ }
313
+
314
+
315
+ RGEO_END_C
@@ -0,0 +1,293 @@
1
+ # -----------------------------------------------------------------------------
2
+ #
3
+ # Proj4 wrapper for RGeo
4
+ #
5
+ # -----------------------------------------------------------------------------
6
+
7
+ module RGeo
8
+ module CoordSys
9
+ # This is a Ruby wrapper around a Proj4 coordinate system.
10
+ # It represents a single geographic coordinate system, which may be
11
+ # a flat projection, a geocentric (3-dimensional) coordinate system,
12
+ # or a geographic (latitude-longitude) coordinate system.
13
+ #
14
+ # Generally, these are used to define the projection for a
15
+ # Feature::Factory. You can then convert between coordinate systems
16
+ # by casting geometries between such factories using the :project
17
+ # option. You may also use this object directly to perform low-level
18
+ # coordinate transformations.
19
+
20
+ class Proj4
21
+ def inspect # :nodoc:
22
+ "#<#{self.class}:0x#{object_id.to_s(16)} #{canonical_str.inspect}>"
23
+ end
24
+
25
+ def to_s # :nodoc:
26
+ canonical_str
27
+ end
28
+
29
+ def hash # :nodoc:
30
+ @hash ||= canonical_hash.hash
31
+ end
32
+
33
+ # Returns true if this Proj4 is equivalent to the given Proj4.
34
+ #
35
+ # Note: this tests for equivalence by comparing only the hash
36
+ # definitions of the Proj4 objects, and returning true if those
37
+ # definitions are equivalent. In some cases, this may still return
38
+ # false even if the actual coordinate systems are identical, since
39
+ # there are sometimes multiple ways to express a given coordinate
40
+ # system.
41
+
42
+ def eql?(rhs_)
43
+ rhs_.class == self.class && rhs_.canonical_hash == canonical_hash && rhs_._radians? == _radians?
44
+ end
45
+ alias_method :==, :eql?
46
+
47
+ # Marshal support
48
+
49
+ def marshal_dump # :nodoc:
50
+ { "rad" => radians?, "str" => original_str || canonical_str }
51
+ end
52
+
53
+ def marshal_load(data_) # :nodoc:
54
+ _set_value(data_["str"], data_["rad"])
55
+ end
56
+
57
+ # Psych support
58
+
59
+ def encode_with(coder_) # :nodoc:
60
+ coder_["proj4"] = original_str || canonical_str
61
+ coder_["radians"] = radians?
62
+ end
63
+
64
+ def init_with(coder_) # :nodoc:
65
+ if coder_.type == :scalar
66
+ _set_value(coder_.scalar, false)
67
+ else
68
+ _set_value(coder_["proj4"], coder_["radians"])
69
+ end
70
+ end
71
+
72
+ # Returns the "canonical" string definition for this coordinate
73
+ # system, as reported by Proj4. This may be slightly different
74
+ # from the definition used to construct this object.
75
+
76
+ def canonical_str
77
+ unless defined?(@canonical_str)
78
+ @canonical_str = _canonical_str
79
+ if @canonical_str.respond_to?(:force_encoding)
80
+ @canonical_str.force_encoding("US-ASCII")
81
+ end
82
+ end
83
+ @canonical_str
84
+ end
85
+
86
+ # Returns the "canonical" hash definition for this coordinate
87
+ # system, as reported by Proj4. This may be slightly different
88
+ # from the definition used to construct this object.
89
+
90
+ def canonical_hash
91
+ unless defined?(@canonical_hash)
92
+ @canonical_hash = {}
93
+ canonical_str.strip.split(/\s+/).each do |elem_|
94
+ @canonical_hash[Regexp.last_match(1)] = Regexp.last_match(3) if elem_ =~ /^\+(\w+)(=(\S+))?$/
95
+ end
96
+ end
97
+ @canonical_hash
98
+ end
99
+
100
+ # Returns the string definition originally used to construct this
101
+ # object. Returns nil if this object wasn't created by a string
102
+ # definition; i.e. if it was created using get_geographic.
103
+
104
+ def original_str
105
+ _original_str
106
+ end
107
+
108
+ # Returns true if this Proj4 object is a geographic (lat-long)
109
+ # coordinate system.
110
+
111
+ def geographic?
112
+ _geographic?
113
+ end
114
+
115
+ # Returns true if this Proj4 object is a geocentric (3dz)
116
+ # coordinate system.
117
+
118
+ def geocentric?
119
+ _geocentric?
120
+ end
121
+
122
+ # Returns true if this Proj4 object uses radians rather than degrees
123
+ # if it is a geographic coordinate system.
124
+
125
+ def radians?
126
+ _radians?
127
+ end
128
+
129
+ # Get the geographic (unprojected lat-long) coordinate system
130
+ # corresponding to this coordinate system; i.e. the one that uses
131
+ # the same ellipsoid and datum.
132
+
133
+ def get_geographic
134
+ _get_geographic
135
+ end
136
+
137
+ class << self
138
+ # Returns true if Proj4 is supported in this installation.
139
+ # If this returns false, the other methods such as create
140
+ # will not work.
141
+
142
+ def supported?
143
+ respond_to?(:_create)
144
+ end
145
+
146
+ # Returns the Proj library version as an integer (example: 493).
147
+ # TODO: return as string of the format "x.y.z".
148
+ def version
149
+ _proj_version
150
+ end
151
+
152
+ # Create a new Proj4 object, given a definition, which may be
153
+ # either a string or a hash. Returns nil if the given definition
154
+ # is invalid or Proj4 is not supported.
155
+ #
156
+ # Recognized options include:
157
+ #
158
+ # [<tt>:radians</tt>]
159
+ # If set to true, then this proj4 will represent geographic
160
+ # (latitude/longitude) coordinates in radians rather than
161
+ # degrees. If this is a geographic coordinate system, then its
162
+ # units will be in radians. If this is a projected coordinate
163
+ # system, then its units will be unchanged, but any geographic
164
+ # coordinate system obtained using get_geographic will use
165
+ # radians as its units. If this is a geocentric or other type of
166
+ # coordinate system, this has no effect. Default is false.
167
+ # (That is all coordinates are in degrees by default.)
168
+
169
+ def create(defn_, opts_ = {})
170
+ result_ = nil
171
+ if supported?
172
+ if defn_.is_a?(::Hash)
173
+ defn_ = defn_.map { |k_, v_| v_ ? "+#{k_}=#{v_}" : "+#{k_}" }.join(" ")
174
+ end
175
+ unless defn_ =~ /^\s*\+/
176
+ defn_ = defn_.sub(/^(\s*)/, '\1+').gsub(/(\s+)([^+\s])/, '\1+\2')
177
+ end
178
+ result_ = _create(defn_, opts_[:radians])
179
+ result_ = nil unless result_._valid?
180
+ end
181
+ result_
182
+ end
183
+
184
+ # Create a new Proj4 object, given a definition, which may be
185
+ # either a string or a hash. Raises Error::UnsupportedOperation
186
+ # if the given definition is invalid or Proj4 is not supported.
187
+ #
188
+ # Recognized options include:
189
+ #
190
+ # [<tt>:radians</tt>]
191
+ # If set to true, then this proj4 will represent geographic
192
+ # (latitude/longitude) coordinates in radians rather than
193
+ # degrees. If this is a geographic coordinate system, then its
194
+ # units will be in radians. If this is a projected coordinate
195
+ # system, then its units will be unchanged, but any geographic
196
+ # coordinate system obtained using get_geographic will use
197
+ # radians as its units. If this is a geocentric or other type of
198
+ # coordinate system, this has no effect. Default is false.
199
+ # (That is all coordinates are in degrees by default.)
200
+
201
+ def new(defn_, opts_ = {})
202
+ result_ = create(defn_, opts_)
203
+ unless result_
204
+ raise Error::UnsupportedOperation, "Proj4 not supported in this installation"
205
+ end
206
+ result_
207
+ end
208
+
209
+ # Low-level coordinate transform method.
210
+ # Transforms the given coordinate (x, y, [z]) from one proj4
211
+ # coordinate system to another. Returns an array with either two
212
+ # or three elements.
213
+
214
+ def transform_coords(from_proj_, to_proj_, x_, y_, z_ = nil)
215
+ if !from_proj_._radians? && from_proj_._geographic?
216
+ x_ *= ImplHelper::Math::RADIANS_PER_DEGREE
217
+ y_ *= ImplHelper::Math::RADIANS_PER_DEGREE
218
+ end
219
+ result_ = _transform_coords(from_proj_, to_proj_, x_, y_, z_)
220
+ if result_ && !to_proj_._radians? && to_proj_._geographic?
221
+ result_[0] *= ImplHelper::Math::DEGREES_PER_RADIAN
222
+ result_[1] *= ImplHelper::Math::DEGREES_PER_RADIAN
223
+ end
224
+ result_
225
+ end
226
+
227
+ # Low-level geometry transform method.
228
+ # Transforms the given geometry between the given two projections.
229
+ # The resulting geometry is constructed using the to_factory.
230
+ # Any projections associated with the factories themselves are
231
+ # ignored.
232
+
233
+ def transform(from_proj_, from_geometry_, to_proj_, to_factory_)
234
+ case from_geometry_
235
+ when Feature::Point
236
+ _transform_point(from_proj_, from_geometry_, to_proj_, to_factory_)
237
+ when Feature::Line
238
+ to_factory_.line(from_geometry_.points.map { |p_| _transform_point(from_proj_, p_, to_proj_, to_factory_) })
239
+ when Feature::LinearRing
240
+ _transform_linear_ring(from_proj_, from_geometry_, to_proj_, to_factory_)
241
+ when Feature::LineString
242
+ to_factory_.line_string(from_geometry_.points.map { |p_| _transform_point(from_proj_, p_, to_proj_, to_factory_) })
243
+ when Feature::Polygon
244
+ _transform_polygon(from_proj_, from_geometry_, to_proj_, to_factory_)
245
+ when Feature::MultiPoint
246
+ to_factory_.multi_point(from_geometry_.map { |p_| _transform_point(from_proj_, p_, to_proj_, to_factory_) })
247
+ when Feature::MultiLineString
248
+ to_factory_.multi_line_string(from_geometry_.map { |g_| transform(from_proj_, g_, to_proj_, to_factory_) })
249
+ when Feature::MultiPolygon
250
+ to_factory_.multi_polygon(from_geometry_.map { |p_| _transform_polygon(from_proj_, p_, to_proj_, to_factory_) })
251
+ when Feature::GeometryCollection
252
+ to_factory_.collection(from_geometry_.map { |g_| transform(from_proj_, g_, to_proj_, to_factory_) })
253
+ end
254
+ end
255
+
256
+ def _transform_point(from_proj_, from_point_, to_proj_, to_factory_) # :nodoc:
257
+ from_factory_ = from_point_.factory
258
+ from_has_z_ = from_factory_.property(:has_z_coordinate)
259
+ from_has_m_ = from_factory_.property(:has_m_coordinate)
260
+ to_has_z_ = to_factory_.property(:has_z_coordinate)
261
+ to_has_m_ = to_factory_.property(:has_m_coordinate)
262
+ x_ = from_point_.x
263
+ y_ = from_point_.y
264
+ if !from_proj_._radians? && from_proj_._geographic?
265
+ x_ *= ImplHelper::Math::RADIANS_PER_DEGREE
266
+ y_ *= ImplHelper::Math::RADIANS_PER_DEGREE
267
+ end
268
+ coords_ = _transform_coords(from_proj_, to_proj_, x_, y_, from_has_z_ ? from_point_.z : nil)
269
+ if coords_
270
+ if !to_proj_._radians? && to_proj_._geographic?
271
+ coords_[0] *= ImplHelper::Math::DEGREES_PER_RADIAN
272
+ coords_[1] *= ImplHelper::Math::DEGREES_PER_RADIAN
273
+ end
274
+ extras_ = []
275
+ extras_ << coords_[2].to_f if to_has_z_
276
+ extras_ << from_has_m_ ? from_point_.m : 0.0 if to_has_m_
277
+ to_factory_.point(coords_[0], coords_[1], *extras_)
278
+ end
279
+ end
280
+
281
+ def _transform_linear_ring(from_proj_, from_ring_, to_proj_, to_factory_) # :nodoc:
282
+ to_factory_.linear_ring(from_ring_.points[0..-2].map { |p_| _transform_point(from_proj_, p_, to_proj_, to_factory_) })
283
+ end
284
+
285
+ def _transform_polygon(from_proj_, from_polygon_, to_proj_, to_factory_) # :nodoc:
286
+ ext_ = _transform_linear_ring(from_proj_, from_polygon_.exterior_ring, to_proj_, to_factory_)
287
+ int_ = from_polygon_.interior_rings.map { |r_| _transform_linear_ring(from_proj_, r_, to_proj_, to_factory_) }
288
+ to_factory_.polygon(ext_, int_)
289
+ end
290
+ end
291
+ end
292
+ end
293
+ end
@@ -0,0 +1,140 @@
1
+ # -----------------------------------------------------------------------------
2
+ #
3
+ # SRS database interface
4
+ #
5
+ # -----------------------------------------------------------------------------
6
+
7
+ module RGeo
8
+ module CoordSys
9
+ module SRSDatabase
10
+ # A spatial reference database implementation backed by coordinate
11
+ # system files installed as part of the proj4 library. For a given
12
+ # Proj4Data object, you specify a single file (e.g. the epsg data
13
+ # file), and you can retrieve records by ID number.
14
+
15
+ class Proj4Data
16
+ # Connect to one of the proj4 data files. You should provide the
17
+ # file name, optionally the installation directory if it is not
18
+ # in a typical location, and several additional options.
19
+ #
20
+ # These options are recognized:
21
+ #
22
+ # [<tt>:dir</tt>]
23
+ # The path for the share/proj directory that contains the
24
+ # requested data file. By default, the Proj4Data class will
25
+ # try a number of directories for you, including
26
+ # /usr/local/share/proj, /opt/local/share/proj, /usr/share/proj,
27
+ # and a few other variants. However, if you have proj4 installed
28
+ # elsewhere, you can provide an explicit directory using this
29
+ # option. You may also pass nil as the value, in which case all
30
+ # the normal lookup paths will be disabled, and you will have to
31
+ # provide the full path as the file name.
32
+ # [<tt>:cache</tt>]
33
+ # If set to true, this class caches previously looked up entries
34
+ # so subsequent lookups do not have to reread the file. If set
35
+ # to <tt>:read_all</tt>, then ALL values in the file are read in
36
+ # and cached the first time a lookup is done. If set to
37
+ # <tt>:preload</tt>, then ALL values in the file are read in
38
+ # immediately when the database is created. Default is false,
39
+ # indicating that the file will be reread on every lookup.
40
+ # [<tt>:authority</tt>]
41
+ # If set, its value is taken as the authority name for all
42
+ # entries. The authority code will be set to the identifier. If
43
+ # not set, then the authority fields of entries will be blank.
44
+
45
+ def initialize(filename_, opts_ = {})
46
+ dir_ = nil
47
+ if opts_.include?(:dir)
48
+ dir_ = opts_[:dir]
49
+ else
50
+ ["/usr/local/share/proj", "/usr/local/proj/share/proj", "/usr/local/proj4/share/proj", "/opt/local/share/proj", "/opt/proj/share/proj", "/opt/proj4/share/proj", "/opt/share/proj", "/usr/share/proj"].each do |d_|
51
+ if ::File.directory?(d_) && ::File.readable?(d_)
52
+ dir_ = d_
53
+ break
54
+ end
55
+ end
56
+ end
57
+ @path = dir_ ? "#{dir_}/#{filename_}" : filename_
58
+ @authority = opts_[:authority]
59
+ if opts_[:cache]
60
+ @cache = {}
61
+ case opts_[:cache]
62
+ when :read_all
63
+ @populate_state = 1
64
+ when :preload
65
+ _search_file(nil)
66
+ @populate_state = 2
67
+ else
68
+ @populate_state = 0
69
+ end
70
+ else
71
+ @cache = nil
72
+ @populate_state = 0
73
+ end
74
+ end
75
+
76
+ # Retrieve the Entry for the given ID number.
77
+
78
+ def get(ident_)
79
+ ident_ = ident_.to_s
80
+ return @cache[ident_] if @cache && @cache.include?(ident_)
81
+ result_ = nil
82
+ if @populate_state == 0
83
+ data_ = _search_file(ident_)
84
+ result_ = Entry.new(ident_, authority: @authority, authority_code: @authority ? ident_ : nil, name: data_[1], proj4: data_[2]) if data_
85
+ @cache[ident_] = result_ if @cache
86
+ elsif @populate_state == 1
87
+ _search_file(nil)
88
+ result_ = @cache[ident_]
89
+ @populate_state = 2
90
+ end
91
+ result_
92
+ end
93
+
94
+ # Clear the cache if one exists.
95
+
96
+ def clear_cache
97
+ @cache.clear if @cache
98
+ @populate_state = 1 if @populate_state == 2
99
+ end
100
+
101
+ def _search_file(ident_) # :nodoc:
102
+ ::File.open(@path) do |file_|
103
+ cur_name_ = nil
104
+ cur_ident_ = nil
105
+ cur_text_ = nil
106
+ file_.each do |line_|
107
+ line_.strip!
108
+ if (comment_delim_ = line_.index('#'))
109
+ cur_name_ = line_[comment_delim_ + 1..-1].strip
110
+ line_ = line_[0..comment_delim_ - 1].strip
111
+ end
112
+ unless cur_ident_
113
+ if line_ =~ /^<(\w+)>(.*)/
114
+ cur_ident_ = Regexp.last_match(1)
115
+ cur_text_ = []
116
+ line_ = Regexp.last_match(2).strip
117
+ end
118
+ end
119
+ next unless cur_ident_
120
+ if line_[-2..-1] == "<>"
121
+ cur_text_ << line_[0..-3].strip
122
+ cur_text_ = cur_text_.join(" ")
123
+ if ident_.nil?
124
+ @cache[ident_] = Entry.new(ident_, authority: @authority, authority_code: @authority ? id_ : nil, name: cur_name_, proj4: cur_text_)
125
+ end
126
+ return [ident_, cur_name_, cur_text_] if cur_ident_ == ident_
127
+ cur_ident_ = nil
128
+ cur_name_ = nil
129
+ cur_text_ = nil
130
+ else
131
+ cur_text_ << line_
132
+ end
133
+ end
134
+ end
135
+ nil
136
+ end
137
+ end
138
+ end
139
+ end
140
+ end
data/lib/rgeo/proj4.rb ADDED
@@ -0,0 +1,5 @@
1
+ require "rgeo"
2
+ require "rgeo/proj4/version"
3
+ require "rgeo/coord_sys/proj4"
4
+ require "rgeo/coord_sys/srs_database/proj4_data"
5
+ require "rgeo/coord_sys/proj4_c_impl"
@@ -0,0 +1,5 @@
1
+ module RGeo
2
+ module Proj4
3
+ VERSION = "1.0.0.rc1"
4
+ end
5
+ end
metadata ADDED
@@ -0,0 +1,122 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: rgeo-proj4
3
+ version: !ruby/object:Gem::Version
4
+ version: 1.0.0.rc1
5
+ platform: ruby
6
+ authors:
7
+ - Tee Parham, Daniel Azuma
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2017-11-27 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: rgeo
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: 1.0.0.rc1
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: 1.0.0.rc1
27
+ - !ruby/object:Gem::Dependency
28
+ name: bundler
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: '1.16'
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: '1.16'
41
+ - !ruby/object:Gem::Dependency
42
+ name: rake
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - "~>"
46
+ - !ruby/object:Gem::Version
47
+ version: '12.0'
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - "~>"
53
+ - !ruby/object:Gem::Version
54
+ version: '12.0'
55
+ - !ruby/object:Gem::Dependency
56
+ name: rake-compiler
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - "~>"
60
+ - !ruby/object:Gem::Version
61
+ version: '1.0'
62
+ type: :development
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - "~>"
67
+ - !ruby/object:Gem::Version
68
+ version: '1.0'
69
+ - !ruby/object:Gem::Dependency
70
+ name: test-unit
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - "~>"
74
+ - !ruby/object:Gem::Version
75
+ version: '3.0'
76
+ type: :development
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - "~>"
81
+ - !ruby/object:Gem::Version
82
+ version: '3.0'
83
+ description: Proj4 extension for rgeo.
84
+ email:
85
+ - parhameter@gmail.com, dazuma@gmail.com
86
+ executables: []
87
+ extensions:
88
+ - ext/proj4_c_impl/extconf.rb
89
+ extra_rdoc_files: []
90
+ files:
91
+ - LICENSE.txt
92
+ - ext/proj4_c_impl/extconf.rb
93
+ - ext/proj4_c_impl/main.c
94
+ - lib/rgeo/coord_sys/proj4.rb
95
+ - lib/rgeo/coord_sys/srs_database/proj4_data.rb
96
+ - lib/rgeo/proj4.rb
97
+ - lib/rgeo/proj4/version.rb
98
+ homepage: https://github.com/rgeo/rgeo-proj4
99
+ licenses:
100
+ - MIT
101
+ metadata: {}
102
+ post_install_message:
103
+ rdoc_options: []
104
+ require_paths:
105
+ - lib
106
+ required_ruby_version: !ruby/object:Gem::Requirement
107
+ requirements:
108
+ - - ">="
109
+ - !ruby/object:Gem::Version
110
+ version: 2.1.0
111
+ required_rubygems_version: !ruby/object:Gem::Requirement
112
+ requirements:
113
+ - - ">"
114
+ - !ruby/object:Gem::Version
115
+ version: 1.3.1
116
+ requirements: []
117
+ rubyforge_project:
118
+ rubygems_version: 2.7.0
119
+ signing_key:
120
+ specification_version: 4
121
+ summary: Proj4 extension for rgeo.
122
+ test_files: []