proj4rb 3.0.0 → 4.0.0

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