rgeo-proj4 1.0.0.rc1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/LICENSE.txt +21 -0
- data/ext/proj4_c_impl/extconf.rb +62 -0
- data/ext/proj4_c_impl/main.c +315 -0
- data/lib/rgeo/coord_sys/proj4.rb +293 -0
- data/lib/rgeo/coord_sys/srs_database/proj4_data.rb +140 -0
- data/lib/rgeo/proj4.rb +5 -0
- data/lib/rgeo/proj4/version.rb +5 -0
- metadata +122 -0
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
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: []
|