proj4rb 3.0.0 → 4.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (82) hide show
  1. checksums.yaml +4 -4
  2. data/ChangeLog +26 -15
  3. data/README.rdoc +82 -44
  4. data/Rakefile +27 -27
  5. data/lib/api/api.rb +96 -118
  6. data/lib/api/api_5_0.rb +331 -300
  7. data/lib/api/api_5_1.rb +6 -6
  8. data/lib/api/api_5_2.rb +4 -4
  9. data/lib/api/api_6_0.rb +116 -14
  10. data/lib/api/api_6_1.rb +4 -4
  11. data/lib/api/api_6_2.rb +9 -6
  12. data/lib/api/api_6_3.rb +6 -0
  13. data/lib/api/api_7_0.rb +68 -0
  14. data/lib/api/api_7_1.rb +73 -0
  15. data/lib/api/api_7_2.rb +14 -0
  16. data/lib/api/api_8_0.rb +6 -0
  17. data/lib/api/api_8_1.rb +24 -0
  18. data/lib/api/api_8_2.rb +6 -0
  19. data/lib/api/api_9_1.rb +7 -0
  20. data/lib/api/api_9_2.rb +9 -0
  21. data/lib/api/api_experimental.rb +196 -0
  22. data/lib/proj/area.rb +73 -32
  23. data/lib/proj/axis_info.rb +44 -0
  24. data/lib/proj/bounds.rb +13 -0
  25. data/lib/proj/context.rb +174 -28
  26. data/lib/proj/conversion.rb +92 -0
  27. data/lib/proj/coordinate.rb +281 -197
  28. data/lib/proj/coordinate_operation_mixin.rb +381 -0
  29. data/lib/proj/coordinate_system.rb +137 -0
  30. data/lib/proj/crs.rb +672 -204
  31. data/lib/proj/crs_info.rb +47 -0
  32. data/lib/proj/database.rb +305 -0
  33. data/lib/proj/datum.rb +32 -0
  34. data/lib/proj/datum_ensemble.rb +34 -0
  35. data/lib/proj/ellipsoid.rb +77 -41
  36. data/lib/proj/error.rb +62 -9
  37. data/lib/proj/file_api.rb +166 -0
  38. data/lib/proj/grid.rb +121 -0
  39. data/lib/proj/grid_cache.rb +64 -0
  40. data/lib/proj/grid_info.rb +19 -0
  41. data/lib/proj/network_api.rb +92 -0
  42. data/lib/proj/operation.rb +42 -42
  43. data/lib/proj/operation_factory_context.rb +136 -0
  44. data/lib/proj/parameter.rb +38 -0
  45. data/lib/proj/parameters.rb +106 -0
  46. data/lib/proj/pj_object.rb +670 -80
  47. data/lib/proj/pj_objects.rb +44 -0
  48. data/lib/proj/prime_meridian.rb +65 -39
  49. data/lib/proj/projection.rb +698 -207
  50. data/lib/proj/session.rb +46 -0
  51. data/lib/proj/strings.rb +32 -0
  52. data/lib/proj/transformation.rb +101 -60
  53. data/lib/proj/unit.rb +108 -53
  54. data/lib/proj.rb +110 -9
  55. data/proj4rb.gemspec +5 -5
  56. data/test/abstract_test.rb +23 -1
  57. data/test/context_test.rb +172 -82
  58. data/test/conversion_test.rb +368 -0
  59. data/test/coordinate_system_test.rb +144 -0
  60. data/test/crs_test.rb +770 -71
  61. data/test/database_test.rb +360 -0
  62. data/test/datum_ensemble_test.rb +65 -0
  63. data/test/datum_test.rb +55 -0
  64. data/test/ellipsoid_test.rb +64 -18
  65. data/test/file_api_test.rb +66 -0
  66. data/test/grid_cache_test.rb +72 -0
  67. data/test/grid_test.rb +141 -0
  68. data/test/network_api_test.rb +45 -0
  69. data/test/operation_factory_context_test.rb +201 -0
  70. data/test/parameters_test.rb +40 -0
  71. data/test/pj_object_test.rb +179 -0
  72. data/test/prime_meridian_test.rb +76 -0
  73. data/test/proj_test.rb +46 -4
  74. data/test/projection_test.rb +646 -222
  75. data/test/session_test.rb +78 -0
  76. data/test/transformation_test.rb +149 -7
  77. data/test/unit_test.rb +57 -28
  78. metadata +51 -13
  79. data/lib/api/api_4_9.rb +0 -31
  80. data/lib/proj/config.rb +0 -70
  81. data/lib/proj/point.rb +0 -72
  82. data/test/prime_meridians_test.rb +0 -33
@@ -1,80 +1,670 @@
1
- # encoding: UTF-8
2
- module Proj
3
- class PjObject
4
- def self.finalize(pointer)
5
- proc do
6
- Api.proj_destroy(pointer)
7
- end
8
- end
9
-
10
- def initialize(pointer, context=nil)
11
- @pointer = pointer
12
- @context = context
13
- ObjectSpace.define_finalizer(self, self.class.finalize(@pointer))
14
- end
15
-
16
- def to_ptr
17
- @pointer
18
- end
19
-
20
- def context
21
- @context || Context.current
22
- end
23
-
24
- def proj_type
25
- Api.proj_get_type(self)
26
- end
27
-
28
- def info
29
- Api.proj_pj_info(self)
30
- end
31
-
32
- def id
33
- self.info[:id]
34
- end
35
-
36
- def name
37
- Api.proj_get_name(self).force_encoding('UTF-8')
38
- end
39
-
40
- def auth_name(index=0)
41
- Api.proj_get_id_auth_name(self, index).force_encoding('UTF-8')
42
- end
43
-
44
- def auth_code(index=0)
45
- Api.proj_get_id_code(self, index)
46
- end
47
-
48
- def auth(index=0)
49
- "#{self.auth_name(index)}:#{self.auth_code(index)}"
50
- end
51
-
52
- def description
53
- self.info[:description] ? self.info[:description].force_encoding('UTF-8') : nil
54
- end
55
-
56
- def definition
57
- self.info[:definition] ? self.info[:definition].force_encoding('UTF-8') : nil
58
- end
59
-
60
- def has_inverse?
61
- self.info[:has_inverse] == 1 ? true : false
62
- end
63
-
64
- def accuracy
65
- self.info[:accuracy]
66
- end
67
-
68
- def to_proj_string(string_type=:PJ_PROJ_4)
69
- Api.proj_as_proj_string(self.context, self, string_type, nil).force_encoding('UTF-8')
70
- end
71
-
72
- def to_json
73
- Api.proj_as_projjson(self.context, self, nil).force_encoding('UTF-8')
74
- end
75
-
76
- def to_wkt(wkt_type=:PJ_WKT2_2018)
77
- Api.proj_as_wkt(self.context, self, wkt_type, nil).force_encoding('UTF-8')
78
- end
79
- end
80
- end
1
+ # encoding: UTF-8
2
+ module Proj
3
+ class PjObject
4
+ # @!visibility private
5
+ def self.create_object(pointer, context)
6
+ unless pointer.null?
7
+ # Get the proj type
8
+ type = Api.proj_get_type(pointer)
9
+
10
+ # If we can create a derived classes, but we do not want to call their
11
+ # initializers since we already have a valid PROJ object that we
12
+ # just have to wrap
13
+ klass = case type
14
+ when :PJ_TYPE_CRS, :PJ_TYPE_GEODETIC_CRS, :PJ_TYPE_GEOCENTRIC_CRS,
15
+ :PJ_TYPE_GEOGRAPHIC_2D_CRS, :PJ_TYPE_GEOGRAPHIC_3D_CRS,
16
+ :PJ_TYPE_GEOGRAPHIC_CRS, :PJ_TYPE_VERTICAL_CRS,:PJ_TYPE_PROJECTED_CRS,
17
+ :PJ_TYPE_COMPOUND_CRS, :PJ_TYPE_TEMPORAL_CRS, :PJ_TYPE_ENGINEERING_CRS,
18
+ :PJ_TYPE_BOUND_CRS, :PJ_TYPE_OTHER_CRS
19
+ Crs
20
+ when :PJ_TYPE_CONVERSION, :PJ_TYPE_OTHER_COORDINATE_OPERATION, :PJ_TYPE_CONCATENATED_OPERATION
21
+ Conversion
22
+ when :PJ_TYPE_TRANSFORMATION
23
+ Transformation
24
+ when :PJ_TYPE_TEMPORAL_DATUM, :PJ_TYPE_ENGINEERING_DATUM, :PJ_TYPE_PARAMETRIC_DATUM,
25
+ :PJ_TYPE_GEODETIC_REFERENCE_FRAME, :PJ_TYPE_DYNAMIC_GEODETIC_REFERENCE_FRAME,
26
+ :PJ_TYPE_VERTICAL_REFERENCE_FRAME, :PJ_TYPE_DYNAMIC_VERTICAL_REFERENCE_FRAME
27
+ Datum
28
+ when :PJ_TYPE_DATUM_ENSEMBLE
29
+ DatumEnsemble
30
+ when :PJ_TYPE_ELLIPSOID
31
+ Ellipsoid
32
+ when :PJ_TYPE_PRIME_MERIDIAN
33
+ PrimeMeridian
34
+ else
35
+ # Return whatever the current class is
36
+ self
37
+ end
38
+
39
+ # Now setup the instance variables
40
+ result = klass.allocate
41
+ result.instance_variable_set(:@pointer, pointer)
42
+ result.instance_variable_set(:@context, context)
43
+
44
+ result
45
+ end
46
+ end
47
+
48
+ # Instantiates an object from a string
49
+ #
50
+ # @example
51
+ # conversion = Proj::Conversion.create("+proj=helmert")
52
+ #
53
+ # @see https://proj.org/development/reference/functions.html#c.proj_create
54
+ #
55
+ # @param value [String] Can be:
56
+ # * Proj string
57
+ # * WKT string
58
+ # * Object code (like "EPSG:4326", "urn:ogc:def:crs:EPSG::4326", "urn:ogc:def:coordinateOperation:EPSG::1671"),
59
+ # * Object name. e.g "WGS 84", "WGS 84 / UTM zone 31N". In that case as uniqueness is not guaranteed, heuristics are applied to determine the appropriate best match.
60
+ # * OGC URN combining references for compound coordinate reference systems (e.g "urn:ogc:def:crs,crs:EPSG::2393,crs:EPSG::5717" or custom abbreviated syntax "EPSG:2393+5717"),
61
+ # * OGC URN combining references for concatenated operations (e.g. "urn:ogc:def:coordinateOperation,coordinateOperation:EPSG::3895,coordinateOperation:EPSG::1618")
62
+ # * PROJJSON string. The jsonschema is at https://proj.org/schemas/v0.4/projjson.schema.json (added in 6.2)
63
+ # * compound CRS made from two object names separated with " + ". e.g. "WGS 84 + EGM96 height" (added in 7.1)
64
+ #
65
+ # @return [PjObject] Crs or Transformation
66
+ def self.create(value, context=nil)
67
+ ptr = Api.proj_create(context || Context.current, value)
68
+
69
+ if ptr.null?
70
+ Error.check_object(self)
71
+ end
72
+
73
+ create_object(ptr, context)
74
+ end
75
+
76
+ # Instantiates an object from a database lookup
77
+ #
78
+ # @example
79
+ # crs = Proj::Crs.create_from_database("EPSG", "32631", :PJ_CATEGORY_CRS)
80
+ #
81
+ # @see https://proj.org/development/reference/functions.html#c.proj_create_from_database
82
+ #
83
+ # @param auth_name [String] Authority name (must not be nil)
84
+ # @param code [String] Object code (must not be nil)
85
+ # @param category [PJ_CATEGORY] A PJ_CATEGORY enum value
86
+ # @param use_alternative_grid_names [Boolean] Whether PROJ alternative grid names should be substituted to the official grid names. Only used on transformations. Defaults to false
87
+ # @param context [Context] Context. If nil the current context is used
88
+ #
89
+ # @return [PjObject] Crs or Transformation
90
+ def self.create_from_database(auth_name, code, category, use_alternative_grid_names = false, context = nil)
91
+ context ||= Context.current
92
+ ptr = Api.proj_create_from_database(context, auth_name, code, category,
93
+ use_alternative_grid_names ? 1 : 0, nil)
94
+
95
+ if ptr.null?
96
+ Error.check_context(context)
97
+ end
98
+
99
+ create_object(ptr, context)
100
+ end
101
+
102
+ # Return a list of objects by their name
103
+ #
104
+ # @example
105
+ # objects = Proj::PjObject.create_from_name("WGS 84", Context.current)
106
+ #
107
+ # @see https://proj.org/development/reference/functions.html#c.proj_create_from_name
108
+ #
109
+ # @param name [String] Search value, must be at least two characters
110
+ # @param context [Context] Context. If nil the current context is used
111
+ # @param auth_name [String] Authority name or nil for all authorities. Default is nil
112
+ # @param types [Array<PJ_TYPE>] Types of objects to search for or nil for all types. Default is nil
113
+ # @param approximate_match [Boolean] Whether approximate name identification is allowed. Default is false
114
+ # @param limit [Integer] The maximum number of results to return, use 0 for all results. Default is 0
115
+ #
116
+ # @return [PjObjects] Found objects
117
+ def self.create_from_name(name, context, auth_name: nil, types: nil, approximate_match: false, limit: 0)
118
+ if types
119
+ types_ptr = FFI::MemoryPointer.new(Api::PJ_TYPE.native_type, types.size)
120
+ types_ptr.write_array_of_int(types.map { |symbol| Api::PJ_TYPE[symbol]})
121
+ types_count = types.size
122
+ else
123
+ types_ptr = nil
124
+ types_count = 0
125
+ end
126
+
127
+ ptr = Api.proj_create_from_name(context, auth_name, name, types_ptr, types_count, approximate_match ? 1 : 0, limit, nil)
128
+ PjObjects.new(ptr, context)
129
+ end
130
+
131
+ # Instantiates an object from a WKT string.
132
+ #
133
+ # @see https://proj.org/development/reference/functions.html#c.proj_create_from_wkt
134
+ #
135
+ # @param wkt [String] WKT string (must not be nil)
136
+ # @param context [Context] Context. If nil the current context is used
137
+ # @param strict [Boolean] Enables strict validation will be enabled. Default is false
138
+ # @param unset_identifiers [Boolean] When enabled object identifiers are unset when there is
139
+ # a contradiction between the definition from WKT and the one
140
+ # from the database. Defaults to nil because this option
141
+ # is only available in Proj 9+
142
+ #
143
+ # @return [PjObject] Crs or Transformation
144
+ def self.create_from_wkt(wkt, context = nil, strict: false, unset_identifiers: nil)
145
+ out_warnings = FFI::MemoryPointer.new(:pointer)
146
+ out_grammar_errors = FFI::MemoryPointer.new(:pointer)
147
+
148
+ # @param wkt_type [PJ_WKT_TYPE] WKT version to output. Defaults to PJ_WKT2_2018
149
+ # @param multiline [Boolean] Specifies if output span multiple lines. Defaults to true.
150
+ # @param indentation_width [Integer] Specifies the indentation level. Defaults to 4.
151
+ #
152
+ # @return [String] wkt
153
+
154
+ # Unset
155
+ options = {"STRICT": strict ? "YES" : "NO"}
156
+ case unset_identifiers
157
+ when TrueClass
158
+ options["UNSET_IDENTIFIERS_IF_INCOMPATIBLE_DEF"] = "YES"
159
+ when FalseClass
160
+ options["UNSET_IDENTIFIERS_IF_INCOMPATIBLE_DEF"] = "NO"
161
+ end
162
+ options_ptr = create_options_pointer(options)
163
+
164
+ ptr = Api.proj_create_from_wkt(context, wkt, options_ptr, out_warnings, out_grammar_errors)
165
+
166
+ warnings = Strings.new(out_warnings.read_pointer)
167
+ errors = Strings.new(out_grammar_errors.read_pointer)
168
+
169
+ unless errors.empty?
170
+ raise(RuntimeError, errors.join(". "))
171
+ end
172
+
173
+ unless warnings.empty?
174
+ warn(warnings.join(". "))
175
+ end
176
+
177
+ create_object(ptr, context)
178
+ end
179
+
180
+ # @!visibility private
181
+ def self.finalize(pointer)
182
+ proc do
183
+ Api.proj_destroy(pointer)
184
+ end
185
+ end
186
+
187
+ def initialize(pointer, context=nil)
188
+ if pointer.null?
189
+ raise(Error, "Cannot create a PjObject with a null pointer")
190
+ end
191
+ @pointer = pointer
192
+ @context = context
193
+ ObjectSpace.define_finalizer(self, self.class.finalize(@pointer))
194
+ end
195
+
196
+ def initialize_copy(original)
197
+ ObjectSpace.undefine_finalizer(self)
198
+
199
+ super
200
+
201
+ @pointer = Api.proj_clone(original.context, original)
202
+
203
+ ObjectSpace.define_finalizer(self, self.class.finalize(@pointer))
204
+ end
205
+
206
+ # Assign a new context to this object
207
+ #
208
+ # @param [Context] The context to assign to this object
209
+ def context=(value)
210
+ Api.proj_assign_context(self, value)
211
+ end
212
+
213
+ def to_ptr
214
+ @pointer
215
+ end
216
+
217
+ def context
218
+ @context || Context.current
219
+ end
220
+
221
+ def errno
222
+ Api.proj_errno(self)
223
+ end
224
+
225
+ # Returns whether an object is deprecated
226
+ #
227
+ # @see https://proj.org/development/reference/functions.html#c.proj_is_deprecated
228
+ #
229
+ # @return [Boolean] True if the object is deprecated, otherwise false
230
+ def deprecated?
231
+ result = Api.proj_is_deprecated(self)
232
+ result == 1 ? true : false
233
+ end
234
+
235
+ # Return whether two objects are equivalent. For versions 6.3.0 and higher
236
+ # the check may use using the proj database to check for name aliases
237
+ #
238
+ # @see https://proj.org/development/reference/functions.html#c.proj_is_equivalent_to
239
+ # @see https://proj.org/development/reference/functions.html#c.proj_is_equivalent_to_with_ctx
240
+ #
241
+ # @param other [PjObject] Object to compare to
242
+ # @param comparison [PJ_COMPARISON_CRITERION] Comparison criterion
243
+ #
244
+ # @return [Boolean] True if the objects are equivalent, otherwise false
245
+ def equivalent_to?(other, comparison)
246
+ result = if defined?(Api.proj_is_equivalent_to_with_ctx)
247
+ Api.proj_is_equivalent_to_with_ctx(self.context, self, other, comparison)
248
+ else
249
+ Api.proj_is_equivalent_to(self, other, comparison)
250
+ end
251
+ result == 1 ? true : false
252
+ end
253
+
254
+ # Returns the current error-state of this object
255
+ #
256
+ # @see https://proj.org/development/reference/functions.html#c.proj_errno
257
+ #
258
+ # @return [Integer] An non-zero error codes indicates an error either with the transformation setup or during a transformation
259
+ def errorno
260
+ Api.proj_errno(self)
261
+ end
262
+
263
+ # Get information about this object
264
+ #
265
+ # @see https://proj.org/development/reference/functions.html#c.proj_pj_info
266
+ #
267
+ # @return [PJ_PROJ_INFO]
268
+ def info
269
+ Api.proj_pj_info(self)
270
+ end
271
+
272
+ # Short ID of the operation the PJ object is based on, that is, what comes after the +proj=
273
+ # in a proj-string, e.g. "merc".
274
+ #
275
+ # @see https://proj.org/development/reference/functions.html#c.proj_pj_info
276
+ #
277
+ # @return [String]
278
+ def id
279
+ self.info[:id]
280
+ end
281
+
282
+ # Long description of the operation the PJ object is based on, e.g. "Mercator Cyl, Sph&Ell lat_ts="
283
+ #
284
+ # @see https://proj.org/development/reference/functions.html#c.proj_pj_info
285
+ #
286
+ # @return [String]
287
+ def description
288
+ self.info[:description] ? self.info[:description].force_encoding('UTF-8') : nil
289
+ end
290
+
291
+ # The proj-string that was used to create the PJ object with, e.g. "+proj=merc +lat_0=24 +lon_0=53 +ellps=WGS84"
292
+ #
293
+ # @see https://proj.org/development/reference/functions.html#c.proj_pj_info
294
+ #
295
+ # @return [String]
296
+ def definition
297
+ self.info[:definition] ? self.info[:definition].force_encoding('UTF-8') : nil
298
+ end
299
+
300
+ # Returns true if an an inverse mapping of the defined operation exists, otherwise false
301
+ #
302
+ # @see https://proj.org/development/reference/functions.html#c.proj_pj_info
303
+ #
304
+ # @return [Boolean]
305
+ def has_inverse?
306
+ self.info[:has_inverse] == 1 ? true : false
307
+ end
308
+
309
+ # Expected accuracy of the transformation. -1 if unknown
310
+ #
311
+ # @see https://proj.org/development/reference/functions.html#c.proj_pj_info
312
+ #
313
+ # @return [Double]
314
+ def accuracy
315
+ self.info[:accuracy]
316
+ end
317
+
318
+ # Return the type of an object
319
+ #
320
+ # @see https://proj.org/development/reference/functions.html#c.proj_get_type
321
+ #
322
+ # @return [PJ_TYPE]
323
+ def proj_type
324
+ Api.proj_get_type(self)
325
+ end
326
+
327
+ # Returns the name of an object
328
+ #
329
+ # @see https://proj.org/development/reference/functions.html#c.proj_get_name
330
+ #
331
+ # @return [String]
332
+ def name
333
+ Api.proj_get_name(self)&.force_encoding('UTF-8')
334
+ end
335
+
336
+ # Returns the authority name / codespace of an identifier of an object.
337
+ #
338
+ # @see https://proj.org/development/reference/functions.html#c.proj_get_id_auth_name
339
+ #
340
+ # @param index [Integer] Index of the identifier. 0 is for the first identifier. Default is 0.
341
+ #
342
+ # @return [String]
343
+ def auth_name(index=0)
344
+ Api.proj_get_id_auth_name(self, index)&.force_encoding('UTF-8')
345
+ end
346
+
347
+ # Get the code of an identifier of an object
348
+ #
349
+ # @see https://proj.org/development/reference/functions.html#c.proj_get_id_code
350
+ #
351
+ # @param index [Integer] Index of the identifier. 0 is the first identifier. Default is 0
352
+ #
353
+ # @return [String] The code or nil in case of error or missing name
354
+ def id_code(index=0)
355
+ Api.proj_get_id_code(self, index)
356
+ end
357
+
358
+ # Get the remarks of an object
359
+ #
360
+ # @see https://proj.org/development/reference/functions.html#c.proj_get_remarks
361
+ #
362
+ # @return [String] Remarks or nil in case of error
363
+ def remarks
364
+ Api.proj_get_remarks(self)
365
+ end
366
+
367
+ # Get the scope of an object
368
+ #
369
+ # @see https://proj.org/development/reference/functions.html#c.proj_get_scope
370
+ #
371
+ # @return [String] Scope or nil in case of error or a missing scope
372
+ def scope
373
+ Api.proj_get_scope(self)
374
+ end
375
+
376
+ def auth(index=0)
377
+ auth_name = self.auth_name(index)
378
+ code = self.id_code(index)
379
+
380
+ if auth_name and code
381
+ "#{self.auth_name(index)}:#{self.id_code(index)}"
382
+ elsif auth_name
383
+ auth_name
384
+ elsif code
385
+ code
386
+ end
387
+ end
388
+
389
+ # Return the area of use of an object
390
+ #
391
+ # @see https://proj.org/development/reference/functions.html#c.proj_get_area_of_use
392
+ #
393
+ # @return [Area] In case of multiple usages, this will be the one of first usage.
394
+ def area_of_use
395
+ p_name = FFI::MemoryPointer.new(:pointer)
396
+ p_west_lon_degree = FFI::MemoryPointer.new(:double)
397
+ p_south_lat_degree = FFI::MemoryPointer.new(:double)
398
+ p_east_lon_degree = FFI::MemoryPointer.new(:double)
399
+ p_north_lat_degree = FFI::MemoryPointer.new(:double)
400
+
401
+ result = Api.proj_get_area_of_use(self.context, self,
402
+ p_west_lon_degree, p_south_lat_degree, p_east_lon_degree, p_north_lat_degree,
403
+ p_name)
404
+ if result != 1
405
+ Error.check_object(self)
406
+ end
407
+
408
+ name = p_name.read_pointer.read_string_to_null.force_encoding('utf-8')
409
+ Area.new(west_lon_degree: p_west_lon_degree.read_double,
410
+ south_lat_degree: p_south_lat_degree.read_double,
411
+ east_lon_degree: p_east_lon_degree.read_double,
412
+ north_lat_degree: p_north_lat_degree.read_double,
413
+ name: name)
414
+ end
415
+
416
+ # Return the base CRS of a BoundCRS or a DerivedCRS/ProjectedCRS, or the source CRS of a
417
+ # CoordinateOperation, or the CRS of a CoordinateMetadata.
418
+ #
419
+ # @see https://proj.org/development/reference/functions.html#c.proj_get_source_crs
420
+ #
421
+ # @return [Crs]
422
+ def source_crs
423
+ ptr = Api.proj_get_source_crs(self.context, self)
424
+ self.class.create_object(ptr, self.context)
425
+ end
426
+
427
+ # Return the hub CRS of a BoundCRS or the target CRS of a CoordinateOperation
428
+ #
429
+ # @see https://proj.org/development/reference/functions.html#c.proj_get_target_crs
430
+ #
431
+ # @return [Crs]
432
+ def target_crs
433
+ ptr = Api.proj_get_target_crs(self.context, self)
434
+ self.class.create_object(ptr, self.context)
435
+ end
436
+
437
+ # Calculate various cartographic properties, such as scale factors, angular distortion and
438
+ # meridian convergence. Depending on the underlying projection values will be
439
+ # calculated either numerically (default) or analytically. The function also calculates
440
+ # the partial derivatives of the given coordinate.
441
+ #
442
+ # @see https://proj.org/development/reference/functions.html#c.proj_factors
443
+ #
444
+ # @param coordinate [Coordinate] Input geodetic coordinate in radians
445
+ #
446
+ # @return [PJ_FACTORS]
447
+ def factors(coordinate)
448
+ Api.proj_factors(self, coordinate)
449
+ end
450
+
451
+ # Return a list of non-deprecated objects related to the passed one
452
+ #
453
+ # @see https://proj.org/development/reference/functions.html#c.proj_get_non_deprecated
454
+ #
455
+ # @return [Array] Array of objects
456
+ def non_deprecated
457
+ ptr = Api.proj_get_non_deprecated(self.context, self)
458
+ PjObjects.new(ptr, self.context)
459
+ end
460
+
461
+ # Calculate geodesic distance between two points in geodetic coordinates. The calculated distance is between
462
+ # the two points located on the ellipsoid. Note that the axis order of the transformation object
463
+ # is not taken into account, so even though a CRS object comes with axis ordering
464
+ # latitude/longitude coordinates used in this function should be reordered as longitude/latitude.
465
+ #
466
+ # @see https://proj.org/development/reference/functions.html#c.proj_lp_dist
467
+ #
468
+ # @param coord1 [Coordinate] Coordinate of first point. Must be lat/long in radians
469
+ # @param coord2 [Coordinate] Coordinate of second point. Must be lat/long in radians
470
+ #
471
+ # @return [Double] Distance between the coordinates in meters
472
+ def lp_distance(coord1, coord2)
473
+ Api.proj_lp_dist(self, coord1, coord2)
474
+ end
475
+
476
+ # Calculate geodesic distance between two points in geodetic coordinates. Similar to
477
+ # PjObject#lp_distance but also takes the height above the ellipsoid into account.
478
+ #
479
+ # Note that the axis order of the transformation object is not taken into account, so even though
480
+ # a CRS object comes with axis ordering latitude/longitude coordinates used in this function
481
+ # should be reordered as longitude/latitude.
482
+ #
483
+ # @see https://proj.org/development/reference/functions.html#c.proj_lpz_dist
484
+ #
485
+ # @param coord1 [Coordinate] Coordinate of first point. Must be lat/long in radians
486
+ # @param coord2 [Coordinate] Coordinate of second point. Must be lat/long in radians
487
+ #
488
+ # @return [Double] Distance between the coordinates in meters
489
+ def lpz_distance(coord1, coord2)
490
+ Api.proj_lpz_dist(self, coord1, coord2)
491
+ end
492
+
493
+ # Calculate the 2-dimensional euclidean between two projected coordinates
494
+ #
495
+ # @see https://proj.org/development/reference/functions.html#c.proj_xy_dist
496
+ #
497
+ # @param coord1 [Coordinate] Coordinate of first point
498
+ # @param coord2 [Coordinate] Coordinate of second point
499
+ #
500
+ # @return [Double] Distance between the coordinates in meters
501
+ def xy_distance(coord1, coord2)
502
+ Api.proj_xy_dist(coord1, coord2)
503
+ end
504
+
505
+ # Calculate the 2-dimensional euclidean between two projected coordinates. Similar to
506
+ # PjObject#xy_distance but also takes height into account.
507
+ #
508
+ # @see https://proj.org/development/reference/functions.html#c.proj_xyz_dist
509
+ #
510
+ # @param coord1 [Coordinate] Coordinate of first point
511
+ # @param coord2 [Coordinate] Coordinate of second point
512
+ #
513
+ # @return [Double] Distance between the coordinates in meters
514
+ def xyz_distance(coord1, coord2)
515
+ Api.proj_xyz_dist(coord1, coord2)
516
+ end
517
+
518
+ # Calculate the geodesic distance as well as forward and reverse azimuth between two points on the ellipsoid.
519
+ #
520
+ # Note that the axis order of the transformation object is not taken into account, so even though
521
+ # a CRS object comes with axis ordering latitude/longitude coordinates used in this function
522
+ # should be reordered as longitude/latitude.
523
+ #
524
+ # @see https://proj.org/development/reference/functions.html#c.proj_geod
525
+ #
526
+ # @param coord1 [Coordinate] Coordinate of first point. Must be lat/long in radians
527
+ # @param coord2 [Coordinate] Coordinate of first point. Must be lat/long in radians
528
+ #
529
+ # @return [Coordinate] The first value is the distance between coord1 and coord2 in meters,
530
+ # the second is the forward azimuth, the third value the reverse azimuth and the fourth value is unused.
531
+ def geod_distance(coord1, coord2)
532
+ ptr = Api.proj_geod(self, coord1, coord2)
533
+ Coordinate.from_coord(ptr)
534
+ end
535
+
536
+ # Returns if an operation expects input in radians
537
+ #
538
+ # @see https://proj.org/development/reference/functions.html#c.proj_angular_input
539
+ #
540
+ # @param direction []PJ_DIRECTION] Direction of transformation
541
+ def angular_input?(direction)
542
+ result = Api.proj_angular_input(self, direction)
543
+ result == 1 ? true : false
544
+ end
545
+
546
+ # Check if an operation returns output in radians
547
+ #
548
+ # @see https://proj.org/development/reference/functions.html#c.proj_angular_output
549
+ #
550
+ # @param direction []PJ_DIRECTION] Direction of transformation
551
+ def angular_output?(direction)
552
+ result = Api.proj_angular_output(self, direction)
553
+ result == 1 ? true : false
554
+ end
555
+
556
+ # Returns if an operation expects input in degrees
557
+ #
558
+ # @see https://proj.org/development/reference/functions.html#c.proj_degree_input
559
+ #
560
+ # @param direction []PJ_DIRECTION] Direction of transformation
561
+ def degree_input?(direction)
562
+ result = Api.proj_degree_input(self, direction)
563
+ result == 1 ? true : false
564
+ end
565
+
566
+ # Check if an operation returns output in degrees
567
+ #
568
+ # @see https://proj.org/development/reference/functions.html#c.proj_degree_output
569
+ #
570
+ # @param direction []PJ_DIRECTION] Direction of transformation
571
+ def degree_output?(direction)
572
+ result = Api.proj_degree_output(self, direction)
573
+ result == 1 ? true : false
574
+ end
575
+
576
+ # Returns the proj representation string for this object
577
+ #
578
+ # @see https://proj.org/development/reference/functions.html#c.proj_as_proj_string
579
+ #
580
+ # @param proj_version [PJ_PROJ_STRING_TYPE] The proj version. Defaults to :PJ_PROJ_5
581
+ # @param use_approx_tmerc [Boolean] True adds the +approx flag to +proj=tmerc or +proj=utm. Defaults to false
582
+ # @param multiline [Boolean] Specifies if output span multiple lines. Defaults to false.
583
+ # @param indentation_width [Integer] Specifies the indentation level. Defaults to 2.
584
+ # @param max_line_length [Integer] Specifies the max line length level. Defaults to 80.
585
+ #
586
+ # @return [String]
587
+ def to_proj_string(proj_version=:PJ_PROJ_5, use_approx_tmerc: false, multiline: false,
588
+ indentation_width: 2, max_line_length: 80)
589
+
590
+ options = {"USE_APPROX_TMERC": use_approx_tmerc ? "YES" : "NO",
591
+ "MULTILINE": multiline ? "YES" : "NO",
592
+ "INDENTATION_WIDTH": indentation_width,
593
+ "MAX_LINE_LENGTH": max_line_length}
594
+
595
+ options_ptr = create_options_pointer(options)
596
+ Api.proj_as_proj_string(self.context, self, proj_version, options_ptr).force_encoding('UTF-8')
597
+ end
598
+
599
+ # Returns the json representation for this object
600
+ #
601
+ # @see https://proj.org/development/reference/functions.html#c.proj_as_projjson
602
+ #
603
+ # @param multiline [Boolean] Specifies if output span multiple lines. Defaults to true.
604
+ # @param indentation_width [Integer] Specifies the indentation level. Defaults to 2.
605
+ #
606
+ # @return [String]
607
+ def to_json(multiline: true, indentation_width: 2)
608
+ options = {"MULTILINE": multiline ? "YES" : "NO",
609
+ "INDENTATION_WIDTH": indentation_width}
610
+
611
+ options_ptr = create_options_pointer(options)
612
+ Api.proj_as_projjson(self.context, self, options_ptr).force_encoding('UTF-8')
613
+ end
614
+
615
+ # Returns the wkt representation for this object
616
+ #
617
+ # @see https://proj.org/development/reference/functions.html#c.proj_as_wkt
618
+ #
619
+ # @param wkt_type [PJ_WKT_TYPE] WKT version to output. Defaults to PJ_WKT2_2018
620
+ # @param multiline [Boolean] Specifies if output span multiple lines. Defaults to true.
621
+ # @param indentation_width [Integer] Specifies the indentation level. Defaults to 4.
622
+ #
623
+ # @return [String] wkt
624
+ def to_wkt(wkt_type=:PJ_WKT2_2019, multiline: true, indentation_width: 4)
625
+ options = {"MULTILINE": multiline ? "YES" : "NO",
626
+ "INDENTATION_WIDTH": indentation_width,
627
+ "OUTPUT_AXIS": "AUTO",
628
+ "STRICT": "YES",
629
+ "ALLOW_ELLIPSOIDAL_HEIGHT_AS_VERTICAL_CRS": "NO"}
630
+
631
+ options_ptr = create_options_pointer(options)
632
+ result = Api.proj_as_wkt(self.context, self, wkt_type, nil)
633
+
634
+ if result.nil?
635
+ Error.check_object(self)
636
+ end
637
+
638
+ result.force_encoding('UTF-8')
639
+ end
640
+
641
+ # Returns the string representation for this object
642
+ #
643
+ # @return [String] String
644
+ def to_s
645
+ "#<#{self.class.name} - #{name}, #{proj_type}>"
646
+ end
647
+
648
+ private
649
+
650
+ def self.create_options_pointer(options)
651
+ options = options.compact
652
+ options_ptr_array = options.map do |key, value|
653
+ FFI::MemoryPointer.from_string("#{key}=#{value}")
654
+ end
655
+
656
+ if options_ptr_array.empty?
657
+ nil
658
+ else
659
+ # Add extra item at end for null pointer
660
+ options_ptr = FFI::MemoryPointer.new(:pointer, options.size + 1)
661
+ options_ptr.write_array_of_pointer(options_ptr_array)
662
+ options_ptr
663
+ end
664
+ end
665
+
666
+ def create_options_pointer(options)
667
+ self.class.create_options_pointer(options)
668
+ end
669
+ end
670
+ end