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/grid.rb CHANGED
@@ -1,121 +1,121 @@
1
- module Proj
2
- # Grids define models that are used to perform dimension shifts.
3
- #
4
- # Grid files can be quite large and may not be included with Proj depending on how
5
- # it was packaged and any grid licensing requirements. Therefore, Proj has the ability
6
- # to download grids on the fly if {Context#network_enabled? networking} is enabled.
7
- #
8
- # @see https://proj.org/community/rfc/rfc-4.html#rfc4
9
- class Grid
10
- # @!attribute [r] context
11
- # @return [Context] The grid context
12
- # @!attribute [r] name
13
- # @return [String] The grid's name
14
- # @!attribute [r] full_name
15
- # @return [String] The grid's full name
16
- # @!attribute [r] package_name
17
- # @return [String] The grid's package name
18
- # @!attribute [r] url
19
- # @return [URI] A url that can be used to download the grid
20
- attr_reader :context, :name, :full_name, :package_name, :url
21
-
22
- def initialize(name, context = Context.default, full_name: nil, package_name: nil, url: nil,
23
- downloadable: false, open_license: false, available: false)
24
- @name = name
25
- @context = context
26
- @full_name = full_name
27
- @package_name = package_name
28
- @url = url
29
- @downloadable = downloadable
30
- @open_license = open_license
31
- @available = available
32
- end
33
-
34
- # Returns whether the grid can be downloaded
35
- #
36
- # @return [Boolean]
37
- def downloadable?
38
- @downloadable
39
- end
40
-
41
- # Returns whether the grid is released with an open license
42
- #
43
- # @return [Boolean]
44
- def open_license?
45
- @open_license
46
- end
47
-
48
- # Returns whether the grid is available at runtime
49
- #
50
- # @return [Boolean]
51
- def available?
52
- @available
53
- end
54
-
55
- # Returns information about this grid
56
- #
57
- # See https://proj.org/development/reference/functions.html#c.proj_grid_info proj_grid_info
58
- #
59
- # @return [GridInfo]
60
- def info
61
- ptr = Api.proj_grid_info(self.name)
62
- GridInfo.new(ptr)
63
- end
64
-
65
- # Returns if a grid is available in the PROJ user-writable directory.
66
- # This method will only return true if Context#network_enabled? is true
67
- #
68
- # @see https://proj.org/development/reference/functions.html#c.proj_is_download_needed
69
- #
70
- # @param ignore_ttl [Boolean] If set to FALSE, PROJ will only check the recentness of an already downloaded file, if the delay between the last time it has been verified and the current time exceeds the TTL setting. This can save network accesses. If set to TRUE, PROJ will unconditionally check from the server the recentness of the file.
71
- #
72
- # @return [Boolean]
73
- def downloaded?(ignore_ttl = false)
74
- if self.context.network_enabled?
75
- result = Api.proj_is_download_needed(self.context, self.url&.to_s || self.name, ignore_ttl ? 1 : 0)
76
- result == 1 ? false : true
77
- else
78
- false
79
- end
80
- end
81
-
82
- # Download a file in the PROJ user-writable directory if has not already been downlaoded.
83
- # This function can only be used if networking is enabled
84
- #
85
- # @see https://proj.org/development/reference/functions.html#c.proj_download_file
86
- #
87
- # @param ignore_ttl [Boolean] If set to FALSE, PROJ will only check the recentness of an already downloaded file, if the delay between the last time it has been verified and the current time exceeds the TTL setting. This can save network accesses. If set to TRUE, PROJ will unconditionally check from the server the recentness of the file.
88
- # @yieldparam percent [Float] The progress downloading the file in the range of 0 to 1
89
- #
90
- # @return [Boolean] True if the download was successful or unneeded. Otherwise false
91
- def download(ignore_ttl = false)
92
- callback = if block_given?
93
- Proc.new do |context, percent, user_data|
94
- result = yield percent
95
- # Return 1 to tell Proj to keep downloading the file
96
- result ? 1 : 0
97
- end
98
- end
99
-
100
- result = Api.proj_download_file(self.context, self.url&.to_s || self.name, ignore_ttl ? 1 : 0, callback, nil)
101
- result == 1 ? true : false
102
- end
103
-
104
- # Deletes the grid if it has been downloaded
105
- def delete
106
- if self.downloaded?
107
- path = File.join(self.context.user_directory, self.name)
108
- File.delete(path) if File.exist?(path)
109
- end
110
- end
111
-
112
- # Returns the path to the grid if it has been downloaded
113
- #
114
- # @return [String]
115
- def path
116
- if self.downloaded?
117
- File.join(self.context.user_directory, self.name)
118
- end
119
- end
120
- end
121
- end
1
+ module Proj
2
+ # Grids define models that are used to perform dimension shifts.
3
+ #
4
+ # Grid files can be quite large and may not be included with Proj depending on how
5
+ # it was packaged and any grid licensing requirements. Therefore, Proj has the ability
6
+ # to download grids on the fly if {Context#network_enabled? networking} is enabled.
7
+ #
8
+ # @see https://proj.org/community/rfc/rfc-4.html#rfc4
9
+ class Grid
10
+ # @!attribute [r] context
11
+ # @return [Context] The grid context
12
+ # @!attribute [r] name
13
+ # @return [String] The grid's name
14
+ # @!attribute [r] full_name
15
+ # @return [String] The grid's full name
16
+ # @!attribute [r] package_name
17
+ # @return [String] The grid's package name
18
+ # @!attribute [r] url
19
+ # @return [URI] A url that can be used to download the grid
20
+ attr_reader :context, :name, :full_name, :package_name, :url
21
+
22
+ def initialize(name, context = Context.default, full_name: nil, package_name: nil, url: nil,
23
+ downloadable: false, open_license: false, available: false)
24
+ @name = name
25
+ @context = context
26
+ @full_name = full_name
27
+ @package_name = package_name
28
+ @url = url
29
+ @downloadable = downloadable
30
+ @open_license = open_license
31
+ @available = available
32
+ end
33
+
34
+ # Returns whether the grid can be downloaded
35
+ #
36
+ # @return [Boolean]
37
+ def downloadable?
38
+ @downloadable
39
+ end
40
+
41
+ # Returns whether the grid is released with an open license
42
+ #
43
+ # @return [Boolean]
44
+ def open_license?
45
+ @open_license
46
+ end
47
+
48
+ # Returns whether the grid is available at runtime
49
+ #
50
+ # @return [Boolean]
51
+ def available?
52
+ @available
53
+ end
54
+
55
+ # Returns information about this grid
56
+ #
57
+ # See https://proj.org/development/reference/functions.html#c.proj_grid_info proj_grid_info
58
+ #
59
+ # @return [GridInfo]
60
+ def info
61
+ ptr = Api.proj_grid_info(self.name)
62
+ GridInfo.new(ptr)
63
+ end
64
+
65
+ # Returns if a grid is available in the PROJ user-writable directory.
66
+ # This method will only return true if Context#network_enabled? is true
67
+ #
68
+ # @see https://proj.org/development/reference/functions.html#c.proj_is_download_needed
69
+ #
70
+ # @param ignore_ttl [Boolean] If set to FALSE, PROJ will only check the recentness of an already downloaded file, if the delay between the last time it has been verified and the current time exceeds the TTL setting. This can save network accesses. If set to TRUE, PROJ will unconditionally check from the server the recentness of the file.
71
+ #
72
+ # @return [Boolean]
73
+ def downloaded?(ignore_ttl = false)
74
+ if self.context.network_enabled?
75
+ result = Api.proj_is_download_needed(self.context, self.url&.to_s || self.name, ignore_ttl ? 1 : 0)
76
+ result == 1 ? false : true
77
+ else
78
+ false
79
+ end
80
+ end
81
+
82
+ # Download a file in the PROJ user-writable directory if has not already been downlaoded.
83
+ # This function can only be used if networking is enabled
84
+ #
85
+ # @see https://proj.org/development/reference/functions.html#c.proj_download_file
86
+ #
87
+ # @param ignore_ttl [Boolean] If set to FALSE, PROJ will only check the recentness of an already downloaded file, if the delay between the last time it has been verified and the current time exceeds the TTL setting. This can save network accesses. If set to TRUE, PROJ will unconditionally check from the server the recentness of the file.
88
+ # @yieldparam percent [Float] The progress downloading the file in the range of 0 to 1
89
+ #
90
+ # @return [Boolean] True if the download was successful or unneeded. Otherwise false
91
+ def download(ignore_ttl = false)
92
+ callback = if block_given?
93
+ Proc.new do |context, percent, user_data|
94
+ result = yield percent
95
+ # Return 1 to tell Proj to keep downloading the file
96
+ result ? 1 : 0
97
+ end
98
+ end
99
+
100
+ result = Api.proj_download_file(self.context, self.url&.to_s || self.name, ignore_ttl ? 1 : 0, callback, nil)
101
+ result == 1 ? true : false
102
+ end
103
+
104
+ # Deletes the grid if it has been downloaded
105
+ def delete
106
+ if self.downloaded?
107
+ path = File.join(self.context.user_directory, self.name)
108
+ File.delete(path) if File.exist?(path)
109
+ end
110
+ end
111
+
112
+ # Returns the path to the grid if it has been downloaded
113
+ #
114
+ # @return [String]
115
+ def path
116
+ if self.downloaded?
117
+ File.join(self.context.user_directory, self.name)
118
+ end
119
+ end
120
+ end
121
+ end
@@ -1,64 +1,65 @@
1
- module Proj
2
- # To avoid repeated network access, it is possible to enable a local cache of grids.
3
- # Grid data is stored in a SQLite3 database, cache.db, that is by default stored
4
- # stored in the PROJ user writable directory.
5
- #
6
- # The local cache is enabled by default with a size of 300MB. Cache settings can be overridden
7
- # by this class, env variables or the proj.ini file
8
- #
9
- # @see https://proj.org/usage/network.html#caching
10
- class GridCache
11
- attr_reader :context
12
-
13
- def initialize(context)
14
- @context = context
15
- end
16
-
17
- # Enables or disables the grid cache
18
- #
19
- # @param value [Boolean]
20
- #
21
- # @see https://proj.org/development/reference/functions.html#c.proj_grid_cache_set_enable
22
- def enabled=(value)
23
- Api.proj_grid_cache_set_enable(self.context, value ? 1 : 0)
24
- end
25
-
26
- # Set the path and file of the local cache file which is sqlite database. By default
27
- # it is stored in the user writable directory.
28
- #
29
- # @param value [String] - Full path to the cache. If set to nil then caching will be disabled.
30
- #
31
- # @see https://proj.org/development/reference/functions.html#c.proj_grid_cache_set_filename
32
- def path=(value)
33
- Api.proj_grid_cache_set_filename(self.context, value.encode('UTF-8'))
34
- value
35
- end
36
-
37
- # Sets the cache size
38
- #
39
- # @param value [Integer] Maximum size in Megabytes (1024*1024 bytes), or negative value to set unlimited size.
40
- #
41
- # @see https://proj.org/development/reference/functions.html#c.proj_grid_cache_set_max_size
42
- def max_size=(value)
43
- Api.proj_grid_cache_set_max_size(self.context, value)
44
- value
45
- end
46
-
47
- # Specifies the time-to-live delay for re-checking if the cached properties of files are still up-to-date.
48
- #
49
- # @param value [Integer] Delay in seconds. Use negative value for no expiration.
50
- #
51
- # @see https://proj.org/development/reference/functions.html#c.proj_grid_cache_set_ttl
52
- def ttl=(value)
53
- Api.proj_grid_cache_set_ttl(self.context, value)
54
- value
55
- end
56
-
57
- # Clears the cache
58
- #
59
- # @see https://proj.org/development/reference/functions.html#c.proj_grid_cache_clear
60
- def clear
61
- Api.proj_grid_cache_clear(self.context)
62
- end
63
- end
64
- end
1
+ module Proj
2
+ # To avoid repeated network access, it is possible to enable a local cache of grids.
3
+ # Grid data is stored in a SQLite3 database, cache.db, that is by default stored
4
+ # stored in the PROJ user writable directory.
5
+ #
6
+ # The local cache is enabled by default with a size of 300MB. Cache settings can be overridden
7
+ # by this class, env variables or the proj.ini file
8
+ #
9
+ # @see https://proj.org/usage/network.html#caching
10
+ class GridCache
11
+ attr_reader :context
12
+
13
+ def initialize(context)
14
+ Error.validate_context!(context)
15
+ @context = context
16
+ end
17
+
18
+ # Enables or disables the grid cache
19
+ #
20
+ # @param value [Boolean]
21
+ #
22
+ # @see https://proj.org/development/reference/functions.html#c.proj_grid_cache_set_enable
23
+ def enabled=(value)
24
+ Api.proj_grid_cache_set_enable(self.context, value ? 1 : 0)
25
+ end
26
+
27
+ # Set the path and file of the local cache file which is sqlite database. By default
28
+ # it is stored in the user writable directory.
29
+ #
30
+ # @param value [String] - Full path to the cache. If set to nil then caching will be disabled.
31
+ #
32
+ # @see https://proj.org/development/reference/functions.html#c.proj_grid_cache_set_filename
33
+ def path=(value)
34
+ Api.proj_grid_cache_set_filename(self.context, value.encode('UTF-8'))
35
+ value
36
+ end
37
+
38
+ # Sets the cache size
39
+ #
40
+ # @param value [Integer] Maximum size in Megabytes (1024*1024 bytes), or negative value to set unlimited size.
41
+ #
42
+ # @see https://proj.org/development/reference/functions.html#c.proj_grid_cache_set_max_size
43
+ def max_size=(value)
44
+ Api.proj_grid_cache_set_max_size(self.context, value)
45
+ value
46
+ end
47
+
48
+ # Specifies the time-to-live delay for re-checking if the cached properties of files are still up-to-date.
49
+ #
50
+ # @param value [Integer] Delay in seconds. Use negative value for no expiration.
51
+ #
52
+ # @see https://proj.org/development/reference/functions.html#c.proj_grid_cache_set_ttl
53
+ def ttl=(value)
54
+ Api.proj_grid_cache_set_ttl(self.context, value)
55
+ value
56
+ end
57
+
58
+ # Clears the cache
59
+ #
60
+ # @see https://proj.org/development/reference/functions.html#c.proj_grid_cache_clear
61
+ def clear
62
+ Api.proj_grid_cache_clear(self.context)
63
+ end
64
+ end
65
+ end
@@ -1,19 +1,19 @@
1
- module Proj
2
- class GridInfo
3
- attr_reader :gridname, :filename, :format,
4
- :lower_left, :upper_right,
5
- :size_lon, :size_lat, :cell_size_lon, :cell_size_lat
6
-
7
- def initialize(pj_grid_info)
8
- @filename = pj_grid_info[:filename].to_ptr.read_string
9
- @gridname = pj_grid_info[:gridname].to_ptr.read_string
10
- @format = pj_grid_info[:format].to_ptr.read_string
11
- @lower_left = pj_grid_info[:lowerleft]
12
- @upper_right = pj_grid_info[:upperright]
13
- @size_lon = pj_grid_info[:n_lon]
14
- @size_lat = pj_grid_info[:n_lat]
15
- @cell_size_lon = pj_grid_info[:cs_lon]
16
- @cell_size_lat = pj_grid_info[:cs_lat]
17
- end
18
- end
19
- end
1
+ module Proj
2
+ class GridInfo
3
+ attr_reader :gridname, :filename, :format,
4
+ :lower_left, :upper_right,
5
+ :size_lon, :size_lat, :cell_size_lon, :cell_size_lat
6
+
7
+ def initialize(pj_grid_info)
8
+ @filename = pj_grid_info[:filename].to_ptr.read_string
9
+ @gridname = pj_grid_info[:gridname].to_ptr.read_string
10
+ @format = pj_grid_info[:format].to_ptr.read_string
11
+ @lower_left = pj_grid_info[:lowerleft]
12
+ @upper_right = pj_grid_info[:upperright]
13
+ @size_lon = pj_grid_info[:n_lon]
14
+ @size_lat = pj_grid_info[:n_lat]
15
+ @cell_size_lon = pj_grid_info[:cs_lon]
16
+ @cell_size_lat = pj_grid_info[:cs_lat]
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,21 @@
1
+ module Proj
2
+ # Tracks whether a PROJ context is still alive. Shared between a Context and
3
+ # the finalizers of objects that depend on it (PjObject, Session). Because
4
+ # Ruby's GC finalizer ordering is non-deterministic during shutdown, a
5
+ # dependent's finalizer may run after its context has already been destroyed.
6
+ # Calling proj_destroy() in that situation segfaults (PROJ 9.8+). This object
7
+ # lets dependents check before calling into PROJ.
8
+ class LifeSpan
9
+ def initialize
10
+ @alive = true
11
+ end
12
+
13
+ def alive?
14
+ @alive
15
+ end
16
+
17
+ def ended!
18
+ @alive = false
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,86 @@
1
+ module Proj
2
+ # Include this module in a class to create a custom network API for PROJ.
3
+ # Install it via {Context#set_network_api}.
4
+ #
5
+ # The including class must call {#install_callbacks} in its initializer and
6
+ # implement the following methods:
7
+ #
8
+ # * +open(uri, offset, size_to_read)+ - Open a network connection and perform
9
+ # the initial read. Return a connection object (any Ruby object) that holds
10
+ # the connection state, along with the data read. The return value must be a
11
+ # Hash with at least a +:data+ key containing the bytes read.
12
+ # * +close(connection)+ - Close the network connection.
13
+ # * +header_value(connection, header_name)+ - Return the value of a response header.
14
+ # * +read_range(connection, offset, size_to_read)+ - Read a range of bytes, return a String.
15
+ #
16
+ # The +connection+ parameter passed to close/header_value/read_range is whatever
17
+ # object your +open+ method returned.
18
+ module NetworkApiCallbacks
19
+ def install_callbacks(context)
20
+ @open_cbk = self.method(:open_callback)
21
+ @close_cbk = self.method(:close_callback)
22
+ @header_value_cbk = self.method(:header_value_callback)
23
+ @read_range_cbk = self.method(:read_range_callback)
24
+
25
+ # Maps native address -> {proj_handle:, connection:, header_ptrs: []}
26
+ # Retaining proj_handle prevents the MemoryPointer from being GCed
27
+ # while PROJ holds the address. header_ptrs retains string pointers
28
+ # returned by header_value_callback.
29
+ @network_handles = {}
30
+
31
+ result = Api.proj_context_set_network_callbacks(context, @open_cbk, @close_cbk, @header_value_cbk, @read_range_cbk, nil)
32
+
33
+ if result != 1
34
+ Error.check_object(self)
35
+ end
36
+ end
37
+
38
+ def open_callback(context, url, offset, size_to_read, buffer, out_size_read, error_string_max_size, out_error_string, user_data)
39
+ uri = URI.parse(url)
40
+ connection = self.open(uri, offset, size_to_read)
41
+ out_size = [size_to_read, connection[:data].size].min
42
+ out_size_read.write(:size_t, out_size)
43
+ buffer.write_bytes(connection[:data], 0, out_size)
44
+
45
+ register_handle(connection)
46
+ end
47
+
48
+ def close_callback(context, handle, user_data)
49
+ connection = handle_to_connection(handle)
50
+ self.close(connection)
51
+ unregister_handle(handle)
52
+ end
53
+
54
+ def header_value_callback(context, handle, header_name, user_data)
55
+ connection = handle_to_connection(handle)
56
+ value = self.header_value(connection, header_name)
57
+ ptr = FFI::MemoryPointer.from_string(value)
58
+ @network_handles[handle.address][:header_ptrs] << ptr
59
+ ptr
60
+ end
61
+
62
+ def read_range_callback(context, handle, offset, size_to_read, buffer, error_string_max_size, out_error_string, user_data)
63
+ connection = handle_to_connection(handle)
64
+ data = self.read_range(connection, offset, size_to_read)
65
+ out_size = [size_to_read, data.size].min
66
+ buffer.write_bytes(data, 0, out_size)
67
+ out_size
68
+ end
69
+
70
+ private
71
+
72
+ def register_handle(connection)
73
+ proj_handle = FFI::MemoryPointer.new(:pointer)
74
+ @network_handles[proj_handle.address] = { proj_handle: proj_handle, connection: connection, header_ptrs: [] }
75
+ proj_handle
76
+ end
77
+
78
+ def handle_to_connection(handle)
79
+ @network_handles[handle.address][:connection]
80
+ end
81
+
82
+ def unregister_handle(handle)
83
+ @network_handles.delete(handle.address)
84
+ end
85
+ end
86
+ end
@@ -1,43 +1,67 @@
1
- module Proj
2
- class Operation
3
- attr_reader :id, :description
4
-
5
- def self.list
6
- pointer_to_array = FFI::Pointer.new(Api::PJ_LIST, Api.proj_list_operations)
7
- result = Array.new
8
- 0.step do |i|
9
- operation_info = Api::PJ_LIST.new(pointer_to_array[i])
10
- break result if operation_info[:id].nil?
11
- id = operation_info[:id]
12
- description = operation_info[:descr].read_pointer.read_string.force_encoding('UTF-8')
13
- result << self.new(id, description)
14
- end
15
- result
16
- end
17
-
18
- def self.get(id)
19
- self.list.find {|operation| operation.id == id}
20
- end
21
-
22
- def initialize(id, description)
23
- @id = id
24
- @description = description
25
- end
26
-
27
- def <=>(other)
28
- self.id <=> other.id
29
- end
30
-
31
- def ==(other)
32
- self.id == other.id
33
- end
34
-
35
- def to_s
36
- self.id
37
- end
38
-
39
- def inspect
40
- "#<#{self.class} id=\"#{id}\", major=\"#{major}\", ell=\"#{ell}\", name=\"#{name}\">"
41
- end
42
- end
1
+ module Proj
2
+ # Represents a PROJ operation (projection or conversion method).
3
+ # Each operation has an identifier and a human-readable description.
4
+ #
5
+ # @example List all operations
6
+ # operations = Proj::Operation.list
7
+ # operations.each { |op| puts "#{op.id}: #{op.description}" }
8
+ #
9
+ # @example Find a specific operation
10
+ # op = Proj::Operation.get("aea")
11
+ # puts op.description #=> "Albers Equal Area"
12
+ class Operation
13
+ # @!attribute [r] id
14
+ # @return [String] The operation identifier (e.g., "aea", "merc")
15
+ # @!attribute [r] description
16
+ # @return [String] Human-readable description of the operation
17
+ attr_reader :id, :description
18
+
19
+ # Returns a list of all available operations.
20
+ #
21
+ # @return [Array<Operation>]
22
+ def self.list
23
+ pointer_to_array = FFI::Pointer.new(Api::PjList, Api.proj_list_operations)
24
+ result = Array.new
25
+ 0.step do |i|
26
+ operation_info = Api::PjList.new(pointer_to_array[i])
27
+ break result if operation_info[:id].nil?
28
+ id = operation_info[:id]
29
+ description = operation_info[:descr].read_pointer.read_string.force_encoding('UTF-8')
30
+ result << self.new(id, description)
31
+ end
32
+ result
33
+ end
34
+
35
+ # Finds an operation by its identifier.
36
+ #
37
+ # @param id [String] The operation identifier to search for
38
+ #
39
+ # @return [Operation, nil] The matching operation or nil if not found
40
+ def self.get(id)
41
+ self.list.find {|operation| operation.id == id}
42
+ end
43
+
44
+ # Creates a new Operation.
45
+ #
46
+ # @param id [String] The operation identifier
47
+ # @param description [String] Human-readable description
48
+ #
49
+ # @return [Operation]
50
+ def initialize(id, description)
51
+ @id = id
52
+ @description = description
53
+ end
54
+
55
+ def ==(other)
56
+ self.id == other.id
57
+ end
58
+
59
+ def to_s
60
+ self.id
61
+ end
62
+
63
+ def inspect
64
+ "#<#{self.class} id=\"#{id}\", description=\"#{description}\">"
65
+ end
66
+ end
43
67
  end
@@ -21,7 +21,9 @@ module Proj
21
21
  # If authority is a non-empty string different of "any", then coordinate
22
22
  # operations will be searched only in that authority namespace.
23
23
  def initialize(context, authority: nil)
24
- @pointer = Api.proj_create_operation_factory_context(context, authority)
24
+ @context = context || Context.current
25
+ Error.validate_context!(@context)
26
+ @pointer = Api.proj_create_operation_factory_context(@context, authority)
25
27
  ObjectSpace.define_finalizer(self, self.class.finalize(@pointer))
26
28
  end
27
29
 
@@ -134,4 +136,4 @@ module Proj
134
136
  @pointer
135
137
  end
136
138
  end
137
- end
139
+ end