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,368 +1,376 @@
1
- # encoding: UTF-8
2
-
3
- require_relative './abstract_test'
4
-
5
- class ConversionTest < AbstractTest
6
- def test_create_conversion
7
- context = Proj::Context.new
8
- param = Proj::Parameter.new(name: "param name", value: 0.99,
9
- unit_conv_factor: 1.0, unit_type: :PJ_UT_SCALE)
10
-
11
- conversion = Proj::Conversion.create_conversion(context, name: "conv",
12
- auth_name: "conv auth", code: "conv code",
13
- method_name: "method", method_auth_name: "method auth", method_code: "method code",
14
- params: [param])
15
- assert(conversion)
16
- assert_equal("conv", conversion.name)
17
- assert_equal("conv auth:conv code", conversion.auth)
18
- assert_equal(:PJ_TYPE_CONVERSION, conversion.proj_type)
19
- end
20
-
21
- def test_inverse_operation
22
- operation = Proj::Conversion.new(<<~EOS)
23
- +proj=pipeline +step +proj=axisswap +order=2,1 +step
24
- +proj=unitconvert +xy_in=deg +xy_out=rad +step +proj=push
25
- +v_3 +step +proj=cart +ellps=evrst30 +step +proj=helmert
26
- +x=293 +y=836 +z=318 +rx=0.5 +ry=1.6 +rz=-2.8 +s=2.1
27
- +convention=position_vector +step +inv +proj=cart
28
- +ellps=WGS84 +step +proj=pop +v_3 +step +proj=unitconvert
29
- +xy_in=rad +xy_out=deg +step +proj=axisswap +order=2,1
30
- EOS
31
-
32
- inverse = operation.create_inverse
33
- proj_string = inverse.to_proj_string(:PJ_PROJ_5, multiline: true, indentation_width: 4, max_line_length: 40)
34
-
35
- expected = if proj7?
36
- <<~EOS
37
- +proj=pipeline
38
- +step +proj=axisswap +order=2,1
39
- +step +proj=unitconvert +xy_in=deg
40
- +xy_out=rad
41
- +step +proj=push +v_3
42
- +step +proj=cart +ellps=WGS84
43
- +step +inv +proj=helmert +x=293
44
- +y=836 +z=318 +rx=0.5 +ry=1.6
45
- +rz=-2.8 +s=2.1
46
- +convention=position_vector
47
- +step +inv +proj=cart +ellps=evrst30
48
- +step +proj=pop +v_3
49
- +step +proj=unitconvert +xy_in=rad
50
- +xy_out=deg
51
- +step +proj=axisswap +order=2,1
52
- EOS
53
- else
54
- "+proj=pipeline +step +proj=axisswap +order=2,1 +step +proj=unitconvert +xy_in=deg +xy_out=rad +step +proj=push +v_3 +step +proj=cart +ellps=WGS84 +step +inv +proj=helmert +x=293 +y=836 +z=318 +rx=0.5 +ry=1.6 +rz=-2.8 +s=2.1 +convention=position_vector +step +inv +proj=cart +ellps=evrst30 +step +proj=pop +v_3 +step +proj=unitconvert +xy_in=rad +xy_out=deg +step +proj=axisswap +order=2,1"
55
- end
56
-
57
- assert_equal(expected.strip, proj_string)
58
- end
59
-
60
- def test_accuracy_coordinate_operation
61
- object = Proj::Conversion.create_from_database("EPSG", "1170", :PJ_CATEGORY_COORDINATE_OPERATION)
62
- assert_equal(16.0, object.accuracy)
63
- end
64
-
65
- def test_roundrip
66
- conversion = Proj::Conversion.new("+proj=cart +ellps=GRS80")
67
- coord1 = Proj::Coordinate.new(lon: Proj.degrees_to_radians(12), lat: Proj.degrees_to_radians(55), z: 100)
68
- coord2 = conversion.forward(coord1)
69
-
70
- dist = conversion.roundtrip(:PJ_FWD, 10000, coord1)
71
- dist += conversion.roundtrip(:PJ_INV, 10000, coord2)
72
- assert(dist < 4e-9)
73
- end
74
-
75
- def test_accuracy_projection
76
- conversion = Proj::Conversion.create("+proj=helmert")
77
- assert_equal(-1.0, conversion.accuracy)
78
- end
79
-
80
- def test_method_info
81
- crs = Proj::Crs.create_from_database("EPSG", "32631", :PJ_CATEGORY_CRS)
82
- operation = crs.coordinate_operation
83
- assert_equal("Transverse Mercator", operation.method_name)
84
- assert_equal("EPSG", operation.method_auth_name)
85
- assert_equal("9807", operation.method_code)
86
- end
87
-
88
- def test_ballpark_transformation
89
- crs = Proj::Crs.create_from_database("EPSG", "32631", :PJ_CATEGORY_CRS)
90
- operation = crs.coordinate_operation
91
- refute(operation.ballpark_transformation?)
92
- end
93
-
94
- def test_param_count
95
- crs = Proj::Crs.create_from_database("EPSG", "32631", :PJ_CATEGORY_CRS)
96
- operation = crs.coordinate_operation
97
- assert_equal(5, operation.param_count)
98
- end
99
-
100
- def test_param
101
- crs = Proj::Crs.create_from_database("EPSG", "32631", :PJ_CATEGORY_CRS)
102
- operation = crs.coordinate_operation
103
-
104
- param = operation.param(3)
105
- assert_equal("False easting", param.name)
106
- assert_equal("EPSG", param.auth_name)
107
- assert_equal("8806", param.code)
108
- assert_equal(500000.0, param.value)
109
- refute(param.value_string)
110
- assert_equal(1.0, param.unit_conv_factor)
111
- assert_equal("metre", param.unit_name)
112
- assert_equal("EPSG", param.unit_auth_name)
113
- assert_equal("9001", param.unit_code)
114
- assert_equal("linear", param.unit_category)
115
- end
116
-
117
- def test_grid_count
118
- operation = Proj::Conversion.create_from_database("EPSG", "1312", :PJ_CATEGORY_COORDINATE_OPERATION)
119
- assert_equal(1, operation.grid_count)
120
- end
121
-
122
- def test_grid_url_invalid_index
123
- context = Proj::Context.new
124
- conversion = Proj::Conversion.create_from_database("EPSG", "1312", :PJ_CATEGORY_COORDINATE_OPERATION, false, context)
125
-
126
- error = assert_raises(Proj::Error) do
127
- conversion.grid(-1)
128
- end
129
-
130
- if proj9?
131
- assert_equal("File not found or invalid", error.to_s)
132
- else
133
- assert_equal("Unknown error (code 4096)", error.to_s)
134
- end
135
- end
136
-
137
- def test_grid_url
138
- context = Proj::Context.new
139
-
140
- conversion = Proj::Conversion.create_from_database("EPSG", "1312", :PJ_CATEGORY_COORDINATE_OPERATION, true, context)
141
- grid = conversion.grid(0)
142
-
143
- assert_equal("ca_nrc_ntv1_can.tif", grid.name)
144
- assert_match(/ntv1_can/, grid.full_name)
145
- assert(grid.package_name.empty?)
146
- assert_equal("https://cdn.proj.org/ca_nrc_ntv1_can.tif", grid.url)
147
- assert(grid.downloadable?)
148
- assert(grid.open_license?)
149
- assert(grid.available?)
150
- end
151
-
152
- def test_xy_dist
153
- conversion = Proj::Conversion.new("+proj=utm; +zone=32; +ellps=GRS80")
154
- coord1 = Proj::Coordinate.new(lam: Proj.degrees_to_radians(12),
155
- phi: Proj.degrees_to_radians(55))
156
-
157
- coord2 = conversion.forward(coord1)
158
- coord1 = conversion.forward(coord1)
159
-
160
- dist = conversion.xy_distance(coord1, coord2)
161
- assert(dist < 2e-9)
162
- end
163
-
164
- def test_angular_input
165
- conversion = Proj::Conversion.new("+proj=cart +ellps=GRS80")
166
- assert(conversion.angular_input?(:PJ_FWD))
167
- refute(conversion.angular_input?(:PJ_INV))
168
- end
169
-
170
- def test_angular_output
171
- conversion = Proj::Conversion.new("+proj=cart +ellps=GRS80")
172
- refute(conversion.angular_output?(:PJ_FWD))
173
- assert(conversion.angular_output?(:PJ_INV))
174
- end
175
-
176
- def test_degree_input
177
- skip "Unsure why these test fail"
178
- conversion = Proj::Conversion.new(<<~EOS)
179
- +proj=pipeline
180
- +step +inv +proj=utm +zone=32 +ellps=GRS80
181
- "+step +proj=unitconvert +xy_in=rad +xy_out=deg
182
- EOS
183
-
184
- refute(conversion.degree_input?(:PJ_FWD))
185
- assert(conversion.degree_input?(:PJ_INV))
186
- end
187
-
188
- def test_degree_output
189
- skip "Unsure why these test fail"
190
- conversion = Proj::Conversion.new(<<~EOS)
191
- +proj=pipeline
192
- +step +inv +proj=utm +zone=32 +ellps=GRS80
193
- "+step +proj=unitconvert +xy_in=rad +xy_out=deg
194
- EOS
195
-
196
- assert(conversion.degree_output?(:PJ_FWD))
197
- refute(conversion.degree_output?(:PJ_INV))
198
- end
199
-
200
- def test_steps_concatenated
201
- context = Proj::Context.new
202
- source_crs = Proj::Conversion.create_from_database("EPSG", "28356", :PJ_CATEGORY_CRS)
203
- target_crs = Proj::Conversion.create_from_database("EPSG", "7856", :PJ_CATEGORY_CRS)
204
-
205
- factory_context = Proj::OperationFactoryContext.new(context)
206
- factory_context.spatial_criterion = :PROJ_SPATIAL_CRITERION_PARTIAL_INTERSECTION
207
- factory_context.grid_availability = :PROJ_GRID_AVAILABILITY_IGNORED
208
-
209
- operations = factory_context.create_operations(source_crs, target_crs)
210
- assert_equal(3, operations.count)
211
-
212
- operation = operations[0]
213
- assert_instance_of(Proj::Conversion, operation)
214
- assert_equal(:PJ_TYPE_CONCATENATED_OPERATION, operation.proj_type)
215
-
216
- assert_equal(3, operation.step_count)
217
- refute(operation.step(-1))
218
-
219
- step = operation.step(1)
220
- assert_equal("Transformation of GDA94 coordinates that have been derived through GNSS CORS.", step.scope)
221
- assert_equal("Scale difference in ppb where 1/billion = 1E-9. See CT codes 8444-46 for NTv2 method giving equivalent results for Christmas Island, Cocos Islands and Australia respectively. See CT code 8447 for alternative including distortion model for Australia only.", step.remarks)
222
- end
223
-
224
- def test_transform_array
225
- crs = Proj::Conversion.new("+proj=utm +zone=32 +ellps=GRS80")
226
-
227
- coord1 = Proj::Coordinate.new(lon: Proj.degrees_to_radians(12), lat: Proj.degrees_to_radians(55), z: 45)
228
- coord2 = Proj::Coordinate.new(lon: Proj.degrees_to_radians(12), lat: Proj.degrees_to_radians(56), z: 50)
229
- new_coords = crs.transform_array([coord1, coord2], :PJ_FWD)
230
-
231
- coord = new_coords[0]
232
- assert_equal(691875.6321396607, coord.x)
233
- assert_equal(6098907.825005012, coord.y)
234
- assert_equal(45, coord.z)
235
- assert_equal(0, coord.t)
236
-
237
- coord = new_coords[1]
238
- assert_equal(687071.439109443, coord.x)
239
- assert_equal(6210141.326748009, coord.y)
240
- assert_equal(50, coord.z)
241
- assert_equal(0, coord.t)
242
- end
243
-
244
- def test_transform_array_invalid
245
- context = Proj::Context.new
246
- crs = Proj::Conversion.new("+proj=utm +zone=32 +ellps=GRS80", context)
247
-
248
- coord1 = Proj::Coordinate.new(lon: Proj.degrees_to_radians(12), lat: Proj.degrees_to_radians(95), z: 45)
249
- coord2 = Proj::Coordinate.new(lon: Proj.degrees_to_radians(12), lat: Proj.degrees_to_radians(56), z: 50)
250
-
251
- error = assert_raises(Proj::Error) do
252
- crs.transform_array([coord1, coord2], :PJ_FWD)
253
- end
254
-
255
- assert_equal("Invalid coordinate", error.to_s)
256
- end
257
-
258
- def test_pipeline
259
- conversion = Proj::Conversion.new(<<~EOS)
260
- +proj=pipeline
261
- +step +inv +proj=lcc +lat_1=33.88333333333333
262
- +lat_2=32.78333333333333 +lat_0=32.16666666666666
263
- +lon_0=-116.25 +x_0=2000000.0001016 +y_0=500000.0001016001 +ellps=GRS80
264
- +towgs84=0,0,0,0,0,0,0 +units=us-ft +no_defs
265
- +step +proj=lcc +lat_1=33.88333333333333 +lat_2=32.78333333333333 +lat_0=32.16666666666666
266
- +lon_0=-116.25 +x_0=2000000 +y_0=500000
267
- +ellps=GRS80 +towgs84=0,0,0,0,0,0,0 +units=m +no_defs
268
- EOS
269
-
270
- # The Presidio, approximately
271
- coordinate_1 = Proj::Coordinate.new(x: 4760096.421921, y: 3744293.729449)
272
- coordinate_2 = conversion.forward(coordinate_1)
273
-
274
- assert_in_delta(1450880.2910605003, coordinate_2.x)
275
- assert_in_delta(1141263.01116045, coordinate_2.y)
276
- end
277
-
278
- if proj9?
279
- def test_last_used_operation
280
- wkt = <<~EOS
281
- CONVERSION["UTM zone 31N",
282
- METHOD["Transverse Mercator",
283
- ID["EPSG",9807]],
284
- PARAMETER["Latitude of natural origin",0,
285
- ANGLEUNIT["degree",0.0174532925199433],
286
- ID["EPSG",8801]],
287
- PARAMETER["Longitude of natural origin",3,
288
- ANGLEUNIT["degree",0.0174532925199433],
289
- ID["EPSG",8802]],
290
- PARAMETER["Scale factor at natural origin",0.9996,
291
- SCALEUNIT["unity",1],
292
- ID["EPSG",8805]],
293
- PARAMETER["False easting",500000,
294
- LENGTHUNIT["metre",1],
295
- ID["EPSG",8806]],
296
- PARAMETER["False northing",0,
297
- LENGTHUNIT["metre",1],
298
- ID["EPSG",8807]],
299
- ID["EPSG",16031]]
300
- EOS
301
-
302
- operation = Proj::Conversion.create_from_wkt(wkt)
303
- puts operation.to_wkt
304
-
305
- operation = Proj::Conversion.create_from_database("EPSG", "16031", :PJ_CATEGORY_COORDINATE_OPERATION)
306
- puts operation.to_wkt
307
-
308
- last = operation.last_used_operation
309
- refute(last)
310
-
311
- coord = Proj::Coordinate.new(x: Proj::Api.proj_torad(3.0), y: 0, z: 0, t: 0)
312
- new_coord = operation.forward(coord)
313
-
314
- assert_in_delta(500000, new_coord.x, 1.0)
315
- assert_in_delta(0.0, new_coord.y)
316
- assert_in_delta(0.0, new_coord.z)
317
- assert_in_delta(0.0, new_coord.t)
318
-
319
- last = operation.last_used_operation
320
- assert(last)
321
- assert(last.equivalent_to?(operation, :PJ_COMP_STRICT))
322
- end
323
- end
324
-
325
- def test_convert_conversion_to_other_method
326
- context = Proj::Context.new
327
- coordinate_system = Proj::CoordinateSystem.create_ellipsoidal_2d(:PJ_ELLPS2D_LONGITUDE_LATITUDE, context)
328
-
329
- crs = Proj::Crs.create_geographic(context, name: "WGS 84", datum_name: "World Geodetic System 1984", ellipsoid_name: "WGS 84",
330
- semi_major_meter: 6378137, inv_flattening: 298.257223563,
331
- prime_meridian_name: "Greenwich", prime_meridian_offset: 0.0, pm_angular_units: "Degree", pm_angular_units_conv: 0.0174532925199433,
332
- coordinate_system: coordinate_system)
333
-
334
- mercator = Proj::Projection.mercator_variant_a(context, center_lat: 0, center_long: 1,
335
- scale: 0.99,
336
- false_easting: 2, false_northing: 3,
337
- ang_unit_name: "Degree", ang_unit_conv_factor: 0.0174532925199433,
338
- linear_unit_name: "Metre", linear_unit_conv_factor: 1.0)
339
-
340
- cartesian = Proj::CoordinateSystem.create_cartesian_2d(context, :PJ_CART2D_EASTING_NORTHING)
341
-
342
- projected = Proj::Crs.create_projected(context, name: "My CRS", geodetic_crs: crs,
343
- conversion: mercator, coordinate_system: cartesian)
344
-
345
- conversion = projected.coordinate_operation
346
- assert_equal(:PJ_TYPE_CONVERSION, conversion.proj_type)
347
-
348
- # Error - Don't set code or method name
349
- new_conversion = conversion.convert_to_other_method
350
- refute(new_conversion)
351
-
352
- # 9805 is epsg code mercator variant b
353
- new_conversion = conversion.convert_to_other_method(new_method_epsg_code: 9805)
354
- assert(new_conversion)
355
- refute(new_conversion.equivalent_to?(conversion, :PJ_COMP_STRICT))
356
- assert(new_conversion.equivalent_to?(conversion, :PJ_COMP_EQUIVALENT))
357
-
358
- new_conversion = conversion.convert_to_other_method(new_method_name: "Mercator (variant B)")
359
- assert(new_conversion)
360
- refute(new_conversion.equivalent_to?(conversion, :PJ_COMP_STRICT))
361
- assert(new_conversion.equivalent_to?(conversion, :PJ_COMP_EQUIVALENT))
362
-
363
- # Convert back
364
- new_conversion = conversion.convert_to_other_method(new_method_name: "Mercator (variant A)")
365
- assert(new_conversion)
366
- assert(new_conversion.equivalent_to?(conversion, :PJ_COMP_STRICT))
367
- end
368
- end
1
+ # encoding: UTF-8
2
+
3
+ require_relative './abstract_test'
4
+
5
+ class ConversionTest < AbstractTest
6
+ def test_create_conversion
7
+ context = Proj::Context.new
8
+ param = Proj::Parameter.new(name: "param name", value: 0.99,
9
+ unit_conv_factor: 1.0, unit_type: :PJ_UT_SCALE)
10
+
11
+ conversion = Proj::Conversion.create_conversion(context, name: "conv",
12
+ auth_name: "conv auth", code: "conv code",
13
+ method_name: "method", method_auth_name: "method auth", method_code: "method code",
14
+ params: [param])
15
+ assert(conversion)
16
+ assert_equal("conv", conversion.name)
17
+ assert_equal("conv auth:conv code", conversion.auth)
18
+ assert_equal(:PJ_TYPE_CONVERSION, conversion.proj_type)
19
+ end
20
+
21
+ def test_inverse_operation
22
+ operation = Proj::Conversion.new(<<~EOS)
23
+ +proj=pipeline +step +proj=axisswap +order=2,1 +step
24
+ +proj=unitconvert +xy_in=deg +xy_out=rad +step +proj=push
25
+ +v_3 +step +proj=cart +ellps=evrst30 +step +proj=helmert
26
+ +x=293 +y=836 +z=318 +rx=0.5 +ry=1.6 +rz=-2.8 +s=2.1
27
+ +convention=position_vector +step +inv +proj=cart
28
+ +ellps=WGS84 +step +proj=pop +v_3 +step +proj=unitconvert
29
+ +xy_in=rad +xy_out=deg +step +proj=axisswap +order=2,1
30
+ EOS
31
+
32
+ inverse = operation.create_inverse
33
+ proj_string = inverse.to_proj_string(:PJ_PROJ_5, multiline: true, indentation_width: 4, max_line_length: 40)
34
+
35
+ expected = if Proj::Api::PROJ_VERSION >= Gem::Version.new('7.0.0')
36
+ <<~EOS
37
+ +proj=pipeline
38
+ +step +proj=axisswap +order=2,1
39
+ +step +proj=unitconvert +xy_in=deg
40
+ +xy_out=rad
41
+ +step +proj=push +v_3
42
+ +step +proj=cart +ellps=WGS84
43
+ +step +inv +proj=helmert +x=293
44
+ +y=836 +z=318 +rx=0.5 +ry=1.6
45
+ +rz=-2.8 +s=2.1
46
+ +convention=position_vector
47
+ +step +inv +proj=cart +ellps=evrst30
48
+ +step +proj=pop +v_3
49
+ +step +proj=unitconvert +xy_in=rad
50
+ +xy_out=deg
51
+ +step +proj=axisswap +order=2,1
52
+ EOS
53
+ else
54
+ "+proj=pipeline +step +proj=axisswap +order=2,1 +step +proj=unitconvert +xy_in=deg +xy_out=rad +step +proj=push +v_3 +step +proj=cart +ellps=WGS84 +step +inv +proj=helmert +x=293 +y=836 +z=318 +rx=0.5 +ry=1.6 +rz=-2.8 +s=2.1 +convention=position_vector +step +inv +proj=cart +ellps=evrst30 +step +proj=pop +v_3 +step +proj=unitconvert +xy_in=rad +xy_out=deg +step +proj=axisswap +order=2,1"
55
+ end
56
+
57
+ assert_equal(expected.strip, proj_string)
58
+ end
59
+
60
+ def test_create_from_crs_string
61
+ error = assert_raises(Proj::Error) do
62
+ Proj::Conversion.new("EPSG:4326")
63
+ end
64
+ assert_match(/Invalid conversion/, error.message)
65
+ end
66
+
67
+ def test_accuracy_coordinate_operation
68
+ object = Proj::Conversion.create_from_database("EPSG", "1170", :PJ_CATEGORY_COORDINATE_OPERATION)
69
+ assert_equal(16.0, object.accuracy)
70
+ end
71
+
72
+ def test_roundrip
73
+ conversion = Proj::Conversion.new("+proj=cart +ellps=GRS80")
74
+ coord1 = Proj::Coordinate.new(lon: Proj.degrees_to_radians(12), lat: Proj.degrees_to_radians(55), z: 100)
75
+ coord2 = conversion.forward(coord1)
76
+
77
+ dist = conversion.roundtrip(:PJ_FWD, 10000, coord1)
78
+ dist += conversion.roundtrip(:PJ_INV, 10000, coord2)
79
+ assert(dist < 4e-9)
80
+ end
81
+
82
+ def test_accuracy_projection
83
+ conversion = Proj::Conversion.create("+proj=helmert")
84
+ assert_equal(-1.0, conversion.accuracy)
85
+ end
86
+
87
+ def test_method_info
88
+ crs = Proj::Crs.create_from_database("EPSG", "32631", :PJ_CATEGORY_CRS)
89
+ operation = crs.coordinate_operation
90
+ assert_equal("Transverse Mercator", operation.method_name)
91
+ assert_equal("EPSG", operation.method_auth_name)
92
+ assert_equal("9807", operation.method_code)
93
+ end
94
+
95
+ def test_ballpark_transformation
96
+ crs = Proj::Crs.create_from_database("EPSG", "32631", :PJ_CATEGORY_CRS)
97
+ operation = crs.coordinate_operation
98
+ refute(operation.ballpark_transformation?)
99
+ end
100
+
101
+ if Proj::Api::PROJ_VERSION >= Gem::Version.new('9.5.0')
102
+ def test_requires_per_coordinate_input_time
103
+ crs = Proj::Crs.create_from_database("EPSG", "32631", :PJ_CATEGORY_CRS)
104
+ operation = crs.coordinate_operation
105
+ refute(operation.requires_per_coordinate_input_time?)
106
+ end
107
+ end
108
+
109
+ def test_param_count
110
+ crs = Proj::Crs.create_from_database("EPSG", "32631", :PJ_CATEGORY_CRS)
111
+ operation = crs.coordinate_operation
112
+ assert_equal(5, operation.param_count)
113
+ end
114
+
115
+ def test_param
116
+ crs = Proj::Crs.create_from_database("EPSG", "32631", :PJ_CATEGORY_CRS)
117
+ operation = crs.coordinate_operation
118
+
119
+ param = operation.param(3)
120
+ assert_equal("False easting", param.name)
121
+ assert_equal("EPSG", param.auth_name)
122
+ assert_equal("8806", param.code)
123
+ assert_equal(500000.0, param.value)
124
+ refute(param.value_string)
125
+ assert_equal(1.0, param.unit_conv_factor)
126
+ assert_equal("metre", param.unit_name)
127
+ assert_equal("EPSG", param.unit_auth_name)
128
+ assert_equal("9001", param.unit_code)
129
+ assert_equal("linear", param.unit_category)
130
+ end
131
+
132
+ def test_grid_count
133
+ operation = Proj::Conversion.create_from_database("EPSG", "1312", :PJ_CATEGORY_COORDINATE_OPERATION)
134
+ assert_equal(1, operation.grid_count)
135
+ end
136
+
137
+ def test_grid_url_invalid_index
138
+ context = Proj::Context.new
139
+ conversion = Proj::Conversion.create_from_database("EPSG", "1312", :PJ_CATEGORY_COORDINATE_OPERATION, false, context)
140
+
141
+ error = assert_raises(Proj::Error) do
142
+ conversion.grid(-1)
143
+ end
144
+
145
+ assert_includes(["Unknown error (code 4096)", "File not found or invalid"], error.to_s)
146
+ end
147
+
148
+ def test_grid_url
149
+ context = Proj::Context.new
150
+
151
+ conversion = Proj::Conversion.create_from_database("EPSG", "1312", :PJ_CATEGORY_COORDINATE_OPERATION, true, context)
152
+ grid = conversion.grid(0)
153
+
154
+ assert_equal("ca_nrc_ntv1_can.tif", grid.name)
155
+ assert(grid.package_name.empty?)
156
+ assert_equal("https://cdn.proj.org/ca_nrc_ntv1_can.tif", grid.url)
157
+ assert(grid.downloadable?)
158
+ assert(grid.open_license?)
159
+ #assert_match(/ntv1_can/, grid.full_name)
160
+ #assert(grid.available?)
161
+ end
162
+
163
+ def test_xy_dist
164
+ conversion = Proj::Conversion.new("+proj=utm; +zone=32; +ellps=GRS80")
165
+ coord1 = Proj::Coordinate.new(lam: Proj.degrees_to_radians(12),
166
+ phi: Proj.degrees_to_radians(55))
167
+
168
+ coord2 = conversion.forward(coord1)
169
+ coord1 = conversion.forward(coord1)
170
+
171
+ dist = Proj::Coordinate.xy_distance(coord1, coord2)
172
+ assert(dist < 2e-9)
173
+ end
174
+
175
+ def test_angular_input
176
+ conversion = Proj::Conversion.new("+proj=cart +ellps=GRS80")
177
+ assert(conversion.angular_input?(:PJ_FWD))
178
+ refute(conversion.angular_input?(:PJ_INV))
179
+ end
180
+
181
+ def test_angular_output
182
+ conversion = Proj::Conversion.new("+proj=cart +ellps=GRS80")
183
+ refute(conversion.angular_output?(:PJ_FWD))
184
+ assert(conversion.angular_output?(:PJ_INV))
185
+ end
186
+
187
+ def test_degree_input
188
+ conversion = Proj::Conversion.new(<<~EOS)
189
+ +proj=pipeline
190
+ +step +inv +proj=utm +zone=32 +ellps=GRS80
191
+ +step +proj=unitconvert +xy_in=rad +xy_out=deg
192
+ EOS
193
+
194
+ refute(conversion.degree_input?(:PJ_FWD))
195
+ assert(conversion.degree_input?(:PJ_INV))
196
+ end
197
+
198
+ def test_degree_output
199
+ conversion = Proj::Conversion.new(<<~EOS)
200
+ +proj=pipeline
201
+ +step +inv +proj=utm +zone=32 +ellps=GRS80
202
+ +step +proj=unitconvert +xy_in=rad +xy_out=deg
203
+ EOS
204
+
205
+ assert(conversion.degree_output?(:PJ_FWD))
206
+ refute(conversion.degree_output?(:PJ_INV))
207
+ end
208
+
209
+ def test_steps_concatenated
210
+ context = Proj::Context.new
211
+ source_crs = Proj::Conversion.create_from_database("EPSG", "28356", :PJ_CATEGORY_CRS)
212
+ target_crs = Proj::Conversion.create_from_database("EPSG", "7856", :PJ_CATEGORY_CRS)
213
+
214
+ factory_context = Proj::OperationFactoryContext.new(context)
215
+ factory_context.spatial_criterion = :PROJ_SPATIAL_CRITERION_PARTIAL_INTERSECTION
216
+ factory_context.grid_availability = :PROJ_GRID_AVAILABILITY_IGNORED
217
+
218
+ operations = factory_context.create_operations(source_crs, target_crs)
219
+ assert_equal(3, operations.count)
220
+
221
+ operation = operations[0]
222
+ assert_instance_of(Proj::Conversion, operation)
223
+ assert_equal(:PJ_TYPE_CONCATENATED_OPERATION, operation.proj_type)
224
+
225
+ assert_equal(3, operation.step_count)
226
+ refute(operation.step(-1))
227
+
228
+ step = operation.step(1)
229
+ assert_equal("Transformation of GDA94 coordinates that have been derived through GNSS CORS.", step.scope)
230
+ assert_equal("Scale difference in ppb where 1/billion = 1E-9. See CT codes 8444-46 for NTv2 method giving equivalent results for Christmas Island, Cocos Islands and Australia respectively. See CT code 8447 for alternative including distortion model for Australia only.", step.remarks)
231
+ end
232
+
233
+ def test_transform_array
234
+ crs = Proj::Conversion.new("+proj=utm +zone=32 +ellps=GRS80")
235
+
236
+ coord1 = Proj::Coordinate.new(lon: Proj.degrees_to_radians(12), lat: Proj.degrees_to_radians(55), z: 45)
237
+ coord2 = Proj::Coordinate.new(lon: Proj.degrees_to_radians(12), lat: Proj.degrees_to_radians(56), z: 50)
238
+ new_coords = crs.transform_array([coord1, coord2], :PJ_FWD)
239
+
240
+ coord = new_coords[0]
241
+ assert_equal(691875.6321396607, coord.x)
242
+ assert_equal(6098907.825005012, coord.y)
243
+ assert_equal(45, coord.z)
244
+ assert_equal(0, coord.t)
245
+
246
+ coord = new_coords[1]
247
+ assert_equal(687071.439109443, coord.x)
248
+ assert_equal(6210141.326748009, coord.y)
249
+ assert_equal(50, coord.z)
250
+ assert_equal(0, coord.t)
251
+
252
+ assert_equal(2, new_coords.size)
253
+ end
254
+
255
+ def test_transform_array_invalid
256
+ context = Proj::Context.new
257
+ crs = Proj::Conversion.new("+proj=utm +zone=32 +ellps=GRS80", context)
258
+
259
+ coord1 = Proj::Coordinate.new(lon: Proj.degrees_to_radians(12), lat: Proj.degrees_to_radians(95), z: 45)
260
+ coord2 = Proj::Coordinate.new(lon: Proj.degrees_to_radians(12), lat: Proj.degrees_to_radians(56), z: 50)
261
+
262
+ error = assert_raises(Proj::Error) do
263
+ crs.transform_array([coord1, coord2], :PJ_FWD)
264
+ end
265
+
266
+ assert_equal("Invalid coordinate", error.to_s)
267
+ end
268
+
269
+ def test_pipeline
270
+ conversion = Proj::Conversion.new(<<~EOS)
271
+ +proj=pipeline
272
+ +step +inv +proj=lcc +lat_1=33.88333333333333
273
+ +lat_2=32.78333333333333 +lat_0=32.16666666666666
274
+ +lon_0=-116.25 +x_0=2000000.0001016 +y_0=500000.0001016001 +ellps=GRS80
275
+ +towgs84=0,0,0,0,0,0,0 +units=us-ft +no_defs
276
+ +step +proj=lcc +lat_1=33.88333333333333 +lat_2=32.78333333333333 +lat_0=32.16666666666666
277
+ +lon_0=-116.25 +x_0=2000000 +y_0=500000
278
+ +ellps=GRS80 +towgs84=0,0,0,0,0,0,0 +units=m +no_defs
279
+ EOS
280
+
281
+ # The Presidio, approximately
282
+ coordinate_1 = Proj::Coordinate.new(x: 4760096.421921, y: 3744293.729449)
283
+ coordinate_2 = conversion.forward(coordinate_1)
284
+
285
+ assert_in_delta(1450880.2910605003, coordinate_2.x)
286
+ assert_in_delta(1141263.01116045, coordinate_2.y)
287
+ end
288
+
289
+ if Proj::Api::PROJ_VERSION >= '9.0.0'
290
+ def test_last_used_operation
291
+ wkt = <<~EOS
292
+ CONVERSION["UTM zone 31N",
293
+ METHOD["Transverse Mercator",
294
+ ID["EPSG",9807]],
295
+ PARAMETER["Latitude of natural origin",0,
296
+ ANGLEUNIT["degree",0.0174532925199433],
297
+ ID["EPSG",8801]],
298
+ PARAMETER["Longitude of natural origin",3,
299
+ ANGLEUNIT["degree",0.0174532925199433],
300
+ ID["EPSG",8802]],
301
+ PARAMETER["Scale factor at natural origin",0.9996,
302
+ SCALEUNIT["unity",1],
303
+ ID["EPSG",8805]],
304
+ PARAMETER["False easting",500000,
305
+ LENGTHUNIT["metre",1],
306
+ ID["EPSG",8806]],
307
+ PARAMETER["False northing",0,
308
+ LENGTHUNIT["metre",1],
309
+ ID["EPSG",8807]],
310
+ ID["EPSG",16031]]
311
+ EOS
312
+
313
+ operation = Proj::Conversion.create_from_wkt(wkt)
314
+ operation = Proj::Conversion.create_from_database("EPSG", "16031", :PJ_CATEGORY_COORDINATE_OPERATION)
315
+
316
+ last = operation.last_used_operation
317
+ refute(last)
318
+
319
+ coord = Proj::Coordinate.new(x: Proj::Api.proj_torad(3.0), y: 0, z: 0, t: 0)
320
+ new_coord = operation.forward(coord)
321
+
322
+ assert_in_delta(500000, new_coord.x, 1.0)
323
+ assert_in_delta(0.0, new_coord.y)
324
+ assert_in_delta(0.0, new_coord.z)
325
+ assert_in_delta(0.0, new_coord.t)
326
+
327
+ last = operation.last_used_operation
328
+ assert(last)
329
+ assert(last.equivalent_to?(operation, :PJ_COMP_STRICT))
330
+ end
331
+ end
332
+
333
+ def test_convert_conversion_to_other_method
334
+ context = Proj::Context.new
335
+ coordinate_system = Proj::CoordinateSystem.create_ellipsoidal_2d(:PJ_ELLPS2D_LONGITUDE_LATITUDE, context)
336
+
337
+ crs = Proj::Crs.create_geographic(context, name: "WGS 84", datum_name: "World Geodetic System 1984", ellipsoid_name: "WGS 84",
338
+ semi_major_meter: 6378137, inv_flattening: 298.257223563,
339
+ prime_meridian_name: "Greenwich", prime_meridian_offset: 0.0, pm_angular_units: "Degree", pm_angular_units_conv: 0.0174532925199433,
340
+ coordinate_system: coordinate_system)
341
+
342
+ mercator = Proj::Projection.mercator_variant_a(context, center_latitude: 0, center_longitude: 1,
343
+ scale: 0.99,
344
+ false_easting: 2, false_northing: 3,
345
+ angular_unit_name: "Degree", angular_unit_conversion_factor: 0.0174532925199433,
346
+ linear_unit_name: "Metre", linear_unit_conversion_factor: 1.0)
347
+
348
+ cartesian = Proj::CoordinateSystem.create_cartesian_2d(context, :PJ_CART2D_EASTING_NORTHING)
349
+
350
+ projected = Proj::Crs.create_projected(context, name: "My CRS", geodetic_crs: crs,
351
+ conversion: mercator, coordinate_system: cartesian)
352
+
353
+ conversion = projected.coordinate_operation
354
+ assert_equal(:PJ_TYPE_CONVERSION, conversion.proj_type)
355
+
356
+ # Error - Don't set code or method name
357
+ new_conversion = conversion.convert_to_other_method
358
+ refute(new_conversion)
359
+
360
+ # 9805 is epsg code mercator variant b
361
+ new_conversion = conversion.convert_to_other_method(new_method_epsg_code: 9805)
362
+ assert(new_conversion)
363
+ refute(new_conversion.equivalent_to?(conversion, :PJ_COMP_STRICT))
364
+ assert(new_conversion.equivalent_to?(conversion, :PJ_COMP_EQUIVALENT))
365
+
366
+ new_conversion = conversion.convert_to_other_method(new_method_name: "Mercator (variant B)")
367
+ assert(new_conversion)
368
+ refute(new_conversion.equivalent_to?(conversion, :PJ_COMP_STRICT))
369
+ assert(new_conversion.equivalent_to?(conversion, :PJ_COMP_EQUIVALENT))
370
+
371
+ # Convert back
372
+ new_conversion = conversion.convert_to_other_method(new_method_name: "Mercator (variant A)")
373
+ assert(new_conversion)
374
+ assert(new_conversion.equivalent_to?(conversion, :PJ_COMP_STRICT))
375
+ end
376
+ end