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