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
data/lib/proj/datum.rb CHANGED
@@ -1,32 +1,32 @@
1
- module Proj
2
- class Datum < PjObject
3
- # Returns the frame reference epoch of a dynamic geodetic or vertical reference frame
4
- #
5
- # @see https://proj.org/development/reference/functions.html#c.proj_dynamic_datum_get_frame_reference_epoch
6
- #
7
- # @return [Float] The frame reference epoch as decimal year, or -1 in case of error.
8
- def frame_reference_epoch
9
- Api.proj_dynamic_datum_get_frame_reference_epoch(self.context, self)
10
- end
11
-
12
- # Return the ellipsoid
13
- #
14
- # @see https://proj.org/development/reference/functions.html#c.proj_get_ellipsoid
15
- #
16
- # @return [PjObject]
17
- def ellipsoid
18
- ptr = Api.proj_get_ellipsoid(self.context, self)
19
- self.class.create_object(ptr, self.context)
20
- end
21
-
22
- # Returns the prime meridian
23
- #
24
- # @see https://proj.org/development/reference/functions.html#c.proj_get_prime_meridian
25
- #
26
- # @return [PjObject]
27
- def prime_meridian
28
- ptr = Api.proj_get_prime_meridian(self.context, self)
29
- self.class.create_object(ptr, self.context)
30
- end
31
- end
32
- end
1
+ module Proj
2
+ class Datum < PjObject
3
+ # Returns the frame reference epoch of a dynamic geodetic or vertical reference frame
4
+ #
5
+ # @see https://proj.org/development/reference/functions.html#c.proj_dynamic_datum_get_frame_reference_epoch
6
+ #
7
+ # @return [Float] The frame reference epoch as decimal year, or -1 in case of error.
8
+ def frame_reference_epoch
9
+ Api.proj_dynamic_datum_get_frame_reference_epoch(self.context, self)
10
+ end
11
+
12
+ # Return the ellipsoid
13
+ #
14
+ # @see https://proj.org/development/reference/functions.html#c.proj_get_ellipsoid
15
+ #
16
+ # @return [PjObject]
17
+ def ellipsoid
18
+ ptr = Api.proj_get_ellipsoid(self.context, self)
19
+ self.class.create_object(ptr, self.context)
20
+ end
21
+
22
+ # Returns the prime meridian
23
+ #
24
+ # @see https://proj.org/development/reference/functions.html#c.proj_get_prime_meridian
25
+ #
26
+ # @return [PjObject]
27
+ def prime_meridian
28
+ ptr = Api.proj_get_prime_meridian(self.context, self)
29
+ self.class.create_object(ptr, self.context)
30
+ end
31
+ end
32
+ end
@@ -1,34 +1,34 @@
1
- module Proj
2
- class DatumEnsemble < PjObject
3
-
4
- # Returns the number of members of a datum ensemble
5
- #
6
- # @see https://proj.org/development/reference/functions.html#c.proj_datum_ensemble_get_member_count
7
- #
8
- # @return [Integer]
9
- def count
10
- Api.proj_datum_ensemble_get_member_count(self.context, self)
11
- end
12
-
13
- # Returns a member from a datum ensemble.
14
- #
15
- # @see https://proj.org/development/reference/functions.html#c.proj_datum_ensemble_get_member
16
- #
17
- # @param index [Integer] Index of the datum member to extract. Should be between 0 and DatumEnsembel#count - 1.
18
- #
19
- # @return [Integer]
20
- def [](index)
21
- ptr = Api.proj_datum_ensemble_get_member(self.context, self, index)
22
- self.class.create_object(ptr, self.context)
23
- end
24
-
25
- # Returns the positional accuracy of the datum ensemble
26
- #
27
- # @see https://proj.org/development/reference/functions.html#c.proj_datum_ensemble_get_accuracy
28
- #
29
- # @return [Float] The data ensemble accuracy or -1 in case of error
30
- def accuracy
31
- Api.proj_datum_ensemble_get_accuracy(self.context, self)
32
- end
33
- end
34
- end
1
+ module Proj
2
+ class DatumEnsemble < PjObject
3
+
4
+ # Returns the number of members of a datum ensemble
5
+ #
6
+ # @see https://proj.org/development/reference/functions.html#c.proj_datum_ensemble_get_member_count
7
+ #
8
+ # @return [Integer]
9
+ def count
10
+ Api.proj_datum_ensemble_get_member_count(self.context, self)
11
+ end
12
+
13
+ # Returns a member from a datum ensemble.
14
+ #
15
+ # @see https://proj.org/development/reference/functions.html#c.proj_datum_ensemble_get_member
16
+ #
17
+ # @param index [Integer] Index of the datum member to extract. Should be between 0 and DatumEnsembel#count - 1.
18
+ #
19
+ # @return [Integer]
20
+ def [](index)
21
+ ptr = Api.proj_datum_ensemble_get_member(self.context, self, index)
22
+ self.class.create_object(ptr, self.context)
23
+ end
24
+
25
+ # Returns the positional accuracy of the datum ensemble
26
+ #
27
+ # @see https://proj.org/development/reference/functions.html#c.proj_datum_ensemble_get_accuracy
28
+ #
29
+ # @return [Float] The data ensemble accuracy or -1 in case of error
30
+ def accuracy
31
+ Api.proj_datum_ensemble_get_accuracy(self.context, self)
32
+ end
33
+ end
34
+ end
@@ -0,0 +1,82 @@
1
+ # encoding: UTF-8
2
+
3
+ module Proj
4
+ # Represents a usage domain of a PjObject. Each domain has a scope
5
+ # describing the purpose and a geographic area of use.
6
+ # Objects can have multiple domains.
7
+ class Domain
8
+ # @return [String] The scope of this domain
9
+ attr_reader :scope
10
+
11
+ # @return [Area] The geographic area of use for this domain
12
+ attr_reader :area_of_use
13
+
14
+ # @param scope [String] The scope describing the purpose of this domain
15
+ # @param area_of_use [Area] The geographic area of use
16
+ def initialize(scope:, area_of_use:)
17
+ @scope = scope
18
+ @area_of_use = area_of_use
19
+ end
20
+
21
+ # Returns the number of usage domains for an object.
22
+ # Requires PROJ 9.2+. Returns 1 on older versions for backward compatibility.
23
+ #
24
+ # @see https://proj.org/development/reference/functions.html#c.proj_get_domain_count
25
+ #
26
+ # @param pj_object [PjObject] The object to query
27
+ #
28
+ # @return [Integer]
29
+ def self.count(pj_object)
30
+ if Api.method_defined?(:proj_get_domain_count)
31
+ Api.proj_get_domain_count(pj_object)
32
+ else
33
+ 1
34
+ end
35
+ end
36
+
37
+ # Returns all usage domains for an object.
38
+ # On PROJ < 9.2, falls back to {Api.proj_get_scope} and {Api.proj_get_area_of_use}
39
+ # and returns a single domain.
40
+ #
41
+ # @see https://proj.org/development/reference/functions.html#c.proj_get_scope_ex
42
+ # @see https://proj.org/development/reference/functions.html#c.proj_get_area_of_use_ex
43
+ #
44
+ # @param pj_object [PjObject] The object to query
45
+ #
46
+ # @return [Array<Domain>]
47
+ def self.domains(pj_object)
48
+ use_ex = Api.method_defined?(:proj_get_domain_count)
49
+ domain_count = use_ex ? Api.proj_get_domain_count(pj_object) : 1
50
+
51
+ domain_count.times.map do |index|
52
+ scope = use_ex ? Api.proj_get_scope_ex(pj_object, index) : Api.proj_get_scope(pj_object)
53
+
54
+ p_name = FFI::MemoryPointer.new(:pointer)
55
+ p_west = FFI::MemoryPointer.new(:double)
56
+ p_south = FFI::MemoryPointer.new(:double)
57
+ p_east = FFI::MemoryPointer.new(:double)
58
+ p_north = FFI::MemoryPointer.new(:double)
59
+
60
+ if use_ex
61
+ result = Api.proj_get_area_of_use_ex(pj_object.context, pj_object, index,
62
+ p_west, p_south, p_east, p_north, p_name)
63
+ else
64
+ result = Api.proj_get_area_of_use(pj_object.context, pj_object,
65
+ p_west, p_south, p_east, p_north, p_name)
66
+ end
67
+ unless result
68
+ Error.check_object(pj_object)
69
+ end
70
+
71
+ name = p_name.read_pointer.read_string_to_null.force_encoding('utf-8')
72
+ area = Area.new(west_lon_degree: p_west.read_double,
73
+ south_lat_degree: p_south.read_double,
74
+ east_lon_degree: p_east.read_double,
75
+ north_lat_degree: p_north.read_double,
76
+ name: name)
77
+
78
+ new(scope: scope, area_of_use: area)
79
+ end
80
+ end
81
+ end
82
+ end
@@ -1,78 +1,78 @@
1
- module Proj
2
- class Ellipsoid < PjObject
3
- # Returns a list of ellipsoids that are built into Proj. A more comprehensive
4
- # list is stored in the Proj database and can be queried via PjObject#create_from_database
5
- def self.built_in
6
- pointer_to_array = FFI::Pointer.new(Api::PJ_ELLPS, Api.proj_list_ellps)
7
-
8
- result = Array.new
9
- 0.step do |i|
10
- pj_ellps = Api::PJ_ELLPS.new(pointer_to_array[i])
11
- break result if pj_ellps[:id].nil?
12
- result << pj_ellps
13
- end
14
- result
15
- end
16
-
17
- # Returns ellipsoid parameters
18
- #
19
- # @see https://proj.org/development/reference/functions.html#c.proj_ellipsoid_get_parameters
20
- #
21
- # @return [Hash] Hash of ellipsoid parameters. Axes are in meters
22
- def parameters
23
- @parameters ||= begin
24
- out_semi_major_metre = FFI::MemoryPointer.new(:double)
25
- out_semi_minor_metre = FFI::MemoryPointer.new(:double)
26
- out_is_semi_minor_computed = FFI::MemoryPointer.new(:int)
27
- out_inv_flattening = FFI::MemoryPointer.new(:double)
28
-
29
- result = Api.proj_ellipsoid_get_parameters(self.context, self, out_semi_major_metre, out_semi_minor_metre, out_is_semi_minor_computed, out_inv_flattening)
30
-
31
- if result != 1
32
- Error.check_object(self)
33
- end
34
-
35
- {semi_major_axis: out_semi_major_metre.read_double,
36
- semi_minor_axis: out_semi_minor_metre.read_double,
37
- semi_minor_axis_computed: out_is_semi_minor_computed.read_int == 1 ? true : false,
38
- inverse_flattening: out_inv_flattening.null? ? nil : out_inv_flattening.read_double}
39
- end
40
- end
41
-
42
- # Returns the semi-major axis in meters
43
- #
44
- # @see https://proj.org/development/reference/functions.html#c.proj_ellipsoid_get_parameters
45
- #
46
- # @return [Float]
47
- def semi_major_axis
48
- self.parameters[:semi_major_axis]
49
- end
50
-
51
- # Returns the semi-minor axis in meters
52
- #
53
- # @see https://proj.org/development/reference/functions.html#c.proj_ellipsoid_get_parameters
54
- #
55
- # @return [Float]
56
- def semi_minor_axis
57
- self.parameters[:semi_minor_axis]
58
- end
59
-
60
- # Returns whether the semi-minor axis is computed
61
- #
62
- # @see https://proj.org/development/reference/functions.html#c.proj_ellipsoid_get_parameters
63
- #
64
- # @return [Boolean]
65
- def semi_minor_axis_computed
66
- self.parameters[:semi_minor_axis_computed]
67
- end
68
-
69
- # Returns the inverse flattening value
70
- #
71
- # @see https://proj.org/development/reference/functions.html#c.proj_ellipsoid_get_parameters
72
- #
73
- # @return [Float]
74
- def inverse_flattening
75
- self.parameters[:inverse_flattening]
76
- end
77
- end
1
+ module Proj
2
+ class Ellipsoid < PjObject
3
+ # Returns a list of ellipsoids that are built into Proj. A more comprehensive
4
+ # list is stored in the Proj database and can be queried via PjObject#create_from_database
5
+ def self.built_in
6
+ pointer_to_array = FFI::Pointer.new(Api::PjEllps, Api.proj_list_ellps)
7
+
8
+ result = Array.new
9
+ 0.step do |i|
10
+ pj_ellps = Api::PjEllps.new(pointer_to_array[i])
11
+ break result if pj_ellps[:id].nil?
12
+ result << pj_ellps
13
+ end
14
+ result
15
+ end
16
+
17
+ # Returns ellipsoid parameters
18
+ #
19
+ # @see https://proj.org/development/reference/functions.html#c.proj_ellipsoid_get_parameters
20
+ #
21
+ # @return [Hash] Hash of ellipsoid parameters. Axes are in meters
22
+ def parameters
23
+ @parameters ||= begin
24
+ out_semi_major_metre = FFI::MemoryPointer.new(:double)
25
+ out_semi_minor_metre = FFI::MemoryPointer.new(:double)
26
+ out_is_semi_minor_computed = FFI::MemoryPointer.new(:int)
27
+ out_inv_flattening = FFI::MemoryPointer.new(:double)
28
+
29
+ result = Api.proj_ellipsoid_get_parameters(self.context, self, out_semi_major_metre, out_semi_minor_metre, out_is_semi_minor_computed, out_inv_flattening)
30
+
31
+ if result != 1
32
+ Error.check_object(self)
33
+ end
34
+
35
+ {semi_major_axis: out_semi_major_metre.read_double,
36
+ semi_minor_axis: out_semi_minor_metre.read_double,
37
+ semi_minor_axis_computed: out_is_semi_minor_computed.read_int == 1 ? true : false,
38
+ inverse_flattening: out_inv_flattening.null? ? nil : out_inv_flattening.read_double}
39
+ end
40
+ end
41
+
42
+ # Returns the semi-major axis in meters
43
+ #
44
+ # @see https://proj.org/development/reference/functions.html#c.proj_ellipsoid_get_parameters
45
+ #
46
+ # @return [Float]
47
+ def semi_major_axis
48
+ self.parameters[:semi_major_axis]
49
+ end
50
+
51
+ # Returns the semi-minor axis in meters
52
+ #
53
+ # @see https://proj.org/development/reference/functions.html#c.proj_ellipsoid_get_parameters
54
+ #
55
+ # @return [Float]
56
+ def semi_minor_axis
57
+ self.parameters[:semi_minor_axis]
58
+ end
59
+
60
+ # Returns whether the semi-minor axis is computed
61
+ #
62
+ # @see https://proj.org/development/reference/functions.html#c.proj_ellipsoid_get_parameters
63
+ #
64
+ # @return [Boolean]
65
+ def semi_minor_axis_computed
66
+ self.parameters[:semi_minor_axis_computed]
67
+ end
68
+
69
+ # Returns the inverse flattening value
70
+ #
71
+ # @see https://proj.org/development/reference/functions.html#c.proj_ellipsoid_get_parameters
72
+ #
73
+ # @return [Float]
74
+ def inverse_flattening
75
+ self.parameters[:inverse_flattening]
76
+ end
77
+ end
78
78
  end
data/lib/proj/error.rb CHANGED
@@ -25,11 +25,16 @@ module Proj
25
25
  PROJ_ERR_OTHER_NO_INVERSE_OP = PROJ_ERR_OTHER + 2 # No inverse method available
26
26
  PROJ_ERR_OTHER_NETWORK_ERROR = PROJ_ERR_OTHER + 3 # Failure when accessing a network resource
27
27
 
28
+ def self.validate_context!(context)
29
+ return if context.is_a?(Context)
30
+
31
+ raise(TypeError, "expected Proj::Context, got #{context.class}")
32
+ end
33
+
28
34
  # Check the context to see if an error occurred. If an error has happened will
29
35
  # raise an exception.
30
36
  def self.check_context(context)
31
37
  unless context.errno == 0
32
- # raise(self, "#{self.category(context.errno)}: #{self.message(context)}")
33
38
  raise(self, self.message(context, context.errno))
34
39
  end
35
40
  end
@@ -61,11 +66,5 @@ module Proj
61
66
  end
62
67
  end
63
68
 
64
- # Converts an errno to a error category
65
- def self.category(errno)
66
- self.constants.find do |constant|
67
- self.const_get(constant) == errno
68
- end
69
- end
70
69
  end
71
- end
70
+ end
@@ -0,0 +1,165 @@
1
+ module Proj
2
+ # Include this module in a class to create a custom file API for PROJ.
3
+ # Install it via {Context#set_file_api}.
4
+ #
5
+ # The including class must call {#install_callbacks} in its initializer and
6
+ # implement the following methods:
7
+ #
8
+ # * +open(path, access_mode)+ - Open a file. +access_mode+ is one of
9
+ # +:PROJ_OPEN_ACCESS_READ_ONLY+, +:PROJ_OPEN_ACCESS_READ_UPDATE+, or
10
+ # +:PROJ_OPEN_ACCESS_CREATE+. Return a file object (any Ruby object) or nil on error.
11
+ # * +read(file, size_bytes)+ - Read up to +size_bytes+ from +file+, return a String.
12
+ # * +write(file, data)+ - Write +data+ to +file+, return the number of bytes written.
13
+ # * +seek(file, offset, whence)+ - Seek within +file+ using SEEK_SET/SEEK_CUR/SEEK_END.
14
+ # * +tell(file)+ - Return the current position in +file+.
15
+ # * +close(file)+ - Close +file+.
16
+ # * +exists(path)+ - Return true if the file at +path+ exists.
17
+ # * +mkdir(path)+ - Create directory at +path+, return true on success.
18
+ # * +unlink(path)+ - Remove file at +path+, return true on success.
19
+ # * +rename(original_path, new_path)+ - Rename a file, return true on success.
20
+ #
21
+ # The +file+ parameter passed to read/write/seek/tell/close is whatever object
22
+ # your +open+ method returned.
23
+ #
24
+ # @example
25
+ # class MyFileApi
26
+ # include Proj::FileApiCallbacks
27
+ #
28
+ # def initialize(context)
29
+ # install_callbacks(context)
30
+ # end
31
+ #
32
+ # def open(path, access_mode)
33
+ # # return a file object or nil
34
+ # end
35
+ # # ... implement remaining methods ...
36
+ # end
37
+ #
38
+ # context.set_file_api(MyFileApi)
39
+ module FileApiCallbacks
40
+ def install_callbacks(context)
41
+ # PROJ keeps using this structure after proj_context_set_fileapi returns,
42
+ # so it must be retained on the Ruby object to avoid GC invalidating it.
43
+ @proj_file_api = Api::ProjFileApi.new
44
+ @proj_file_api[:version] = 1
45
+
46
+ # Maps native address -> {proj_handle:, file:}. Retaining proj_handle
47
+ # prevents the MemoryPointer from being GCed while PROJ holds the address.
48
+ @file_api_handles = {}
49
+
50
+ @proj_file_api[:open_cbk] = self.method(:open_callback)
51
+ @proj_file_api[:read_cbk] = self.method(:read_callback)
52
+ @proj_file_api[:write_cbk] = self.method(:write_callback)
53
+ @proj_file_api[:seek_cbk] = self.method(:seek_callback)
54
+ @proj_file_api[:tell_cbk] = self.method(:tell_callback)
55
+ @proj_file_api[:close_cbk] = self.method(:close_callback)
56
+ @proj_file_api[:exists_cbk] = self.method(:exists_callback)
57
+ @proj_file_api[:mkdir_cbk] = self.method(:mkdir_callback)
58
+ @proj_file_api[:unlink_cbk] = self.method(:unlink_callback)
59
+ @proj_file_api[:rename_cbk] = self.method(:rename_callback)
60
+
61
+ result = Api.proj_context_set_fileapi(context, @proj_file_api, nil)
62
+
63
+ if result != 1
64
+ Error.check_object(self)
65
+ end
66
+ end
67
+
68
+ # Open file. Return NULL if error
69
+ def open_callback(context, path, access_mode, user_data)
70
+ file = self.open(path, access_mode)
71
+ return nil unless file
72
+ register_handle(file)
73
+ end
74
+
75
+ # Read sizeBytes into buffer from current position and return number of bytes read
76
+ def read_callback(context, handle, buffer, size_bytes, user_data)
77
+ file = handle_to_file(handle)
78
+ data = self.read(file, size_bytes)
79
+ return 0 if data.nil? || data.empty?
80
+
81
+ read_bytes = [size_bytes, data.bytesize].min
82
+ buffer.put_bytes(0, data, 0, read_bytes)
83
+ read_bytes
84
+ end
85
+
86
+ # Write sizeBytes into buffer from current position and return number of bytes written
87
+ def write_callback(context, handle, buffer, size_bytes, user_data)
88
+ file = handle_to_file(handle)
89
+ data = buffer.get_bytes(0, size_bytes)
90
+ self.write(file, data)
91
+ end
92
+
93
+ # Seek to offset using whence=SEEK_SET/SEEK_CUR/SEEK_END. Return TRUE in case of success
94
+ def seek_callback(context, handle, offset, whence, user_data)
95
+ file = handle_to_file(handle)
96
+ self.seek(file, offset, whence)
97
+ return 1 # True
98
+ end
99
+
100
+ # Return current file position
101
+ def tell_callback(context, handle, user_data)
102
+ file = handle_to_file(handle)
103
+ self.tell(file)
104
+ end
105
+
106
+ # Close file
107
+ def close_callback(context, handle, user_data)
108
+ file = handle_to_file(handle)
109
+ self.close(file)
110
+ unregister_handle(handle)
111
+ end
112
+
113
+ # Return TRUE if a file exists
114
+ def exists_callback(context, path, user_data)
115
+ if self.exists(path)
116
+ 1
117
+ else
118
+ 0
119
+ end
120
+ end
121
+
122
+ # Return TRUE if directory exists or could be created
123
+ def mkdir_callback(context, path, user_data)
124
+ if self.mkdir(path)
125
+ 1
126
+ else
127
+ 0
128
+ end
129
+ end
130
+
131
+ # Return TRUE if file could be removed
132
+ def unlink_callback(context, path, user_data)
133
+ if self.unlink(path)
134
+ 1
135
+ else
136
+ 0
137
+ end
138
+ end
139
+
140
+ # Return TRUE if file could be renamed
141
+ def rename_callback(context, original_path, new_path, user_data)
142
+ if self.rename(original_path, new_path)
143
+ 1
144
+ else
145
+ 0
146
+ end
147
+ end
148
+
149
+ # Create an opaque handle for PROJ and associate it with a file object.
150
+ # The MemoryPointer is retained to prevent GC while PROJ holds the address.
151
+ def register_handle(file)
152
+ proj_handle = FFI::MemoryPointer.new(:pointer)
153
+ @file_api_handles[proj_handle.address] = { proj_handle: proj_handle, file: file }
154
+ proj_handle
155
+ end
156
+
157
+ def handle_to_file(handle)
158
+ @file_api_handles[handle.address][:file]
159
+ end
160
+
161
+ def unregister_handle(handle)
162
+ @file_api_handles.delete(handle.address)
163
+ end
164
+ end
165
+ end