diff --git a/CMakeLists.txt b/CMakeLists.txt index cffc95a7..b6f48f9f 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -921,7 +921,9 @@ if(BUILD_TESTING) --ignore-errors negative --rc branch_coverage=1 COMMAND ${LCOV_EXECUTABLE} - --extract "${AST_COVERAGE_FULL_INFO}" "${CMAKE_SOURCE_DIR}/src/*" + --extract "${AST_COVERAGE_FULL_INFO}" + "${CMAKE_SOURCE_DIR}/src/*" + "${CMAKE_SOURCE_DIR}/ast_tester/*" --output-file "${AST_COVERAGE_INFO}" --ignore-errors unused,negative --rc branch_coverage=1 diff --git a/ast_tester/CMakeLists.txt b/ast_tester/CMakeLists.txt index 02c65e87..49d6c381 100644 --- a/ast_tester/CMakeLists.txt +++ b/ast_tester/CMakeLists.txt @@ -156,6 +156,7 @@ ast_add_test(testresimp INTERNAL_HEADERS) ast_add_test(testaxis) ast_add_test(testframe) ast_add_test(testunitnorm) +ast_add_test(testobjectkeymap) # --- Tests needing data files --- configure_file(2dspline_c.dat "${CMAKE_CURRENT_BINARY_DIR}/2dspline_c.dat" COPYONLY) @@ -439,6 +440,10 @@ if(HAVE_YAML) configure_file(tanSipWcs.txt "${CMAKE_CURRENT_BINARY_DIR}/tanSipWcs.txt" COPYONLY) configure_file(lsst_wcs.txt "${CMAKE_CURRENT_BINARY_DIR}/lsst_wcs.txt" COPYONLY) configure_file(rotate_seq3d_cartesian.asdf "${CMAKE_CURRENT_BINARY_DIR}/rotate_seq3d_cartesian.asdf" COPYONLY) + configure_file(time_equinox.asdf "${CMAKE_CURRENT_BINARY_DIR}/time_equinox.asdf" COPYONLY) + configure_file(quantity.asdf "${CMAKE_CURRENT_BINARY_DIR}/quantity.asdf" COPYONLY) + configure_file(transforms_1d.asdf "${CMAKE_CURRENT_BINARY_DIR}/transforms_1d.asdf" COPYONLY) + configure_file(transforms_2d.asdf "${CMAKE_CURRENT_BINARY_DIR}/transforms_2d.asdf" COPYONLY) ast_add_test(testyamlchan) endif() diff --git a/ast_tester/quantity.asdf b/ast_tester/quantity.asdf new file mode 100644 index 00000000..42f6253a --- /dev/null +++ b/ast_tester/quantity.asdf @@ -0,0 +1,79 @@ +#ASDF 1.0.0 +#ASDF_STANDARD 1.6.0 +%YAML 1.1 +%TAG ! tag:stsci.edu:asdf/ +--- !core/asdf-1.1.0 +asdf_library: !core/software-1.0.0 {author: The ASDF Developers, homepage: 'http://github.com/asdf-format/asdf', + name: asdf, version: 5.1.0} +history: + extensions: + - !core/extension_metadata-1.0.0 + extension_class: asdf.extension._manifest.ManifestExtension + extension_uri: asdf://asdf-format.org/core/extensions/core-1.6.0 + manifest_software: !core/software-1.0.0 {name: asdf_standard, version: 1.5.0} + software: !core/software-1.0.0 {name: asdf, version: 5.1.0} + - !core/extension_metadata-1.0.0 + extension_class: asdf.extension._manifest.ManifestExtension + extension_uri: asdf://asdf-format.org/transform/extensions/transform-1.6.0 + manifest_software: !core/software-1.0.0 {name: asdf_transform_schemas, version: 0.6.0} + software: !core/software-1.0.0 {name: asdf-astropy, version: 0.10.0} + - !core/extension_metadata-1.0.0 + extension_class: asdf.extension._manifest.ManifestExtension + extension_uri: asdf://asdf-format.org/astronomy/gwcs/extensions/gwcs-1.4.0 + manifest_software: !core/software-1.0.0 {name: asdf_wcs_schemas, version: 0.5.0} + software: !core/software-1.0.0 {name: gwcs, version: 1.0.3} + - !core/extension_metadata-1.0.0 + extension_class: asdf.extension._manifest.ManifestExtension + extension_uri: asdf://asdf-format.org/astronomy/extensions/astronomy-1.2.0 + manifest_software: !core/software-1.0.0 {name: asdf_standard, version: 1.5.0} + software: !core/software-1.0.0 {name: asdf-astropy, version: 0.10.0} + - !core/extension_metadata-1.0.0 + extension_class: asdf.extension._manifest.ManifestExtension + extension_uri: asdf://astropy.org/astropy/extensions/units-1.3.0 + software: !core/software-1.0.0 {name: asdf-astropy, version: 0.10.0} +wcs: ! + name: '' + pixel_shape: null + steps: + - ! + frame: ! + axes_names: [x, y] + axes_order: [0, 1] + axis_physical_types: ['custom:x', 'custom:y'] + name: detector + unit: [!unit/unit-1.0.0 pixel, !unit/unit-1.0.0 pixel] + transform: !transform/compose-1.3.0 + forward: + - !transform/affine-1.4.0 + inputs: [x, y] + matrix: !unit/quantity-1.1.0 + unit: !unit/unit-1.0.0 + value: !core/ndarray-1.1.0 + data: + - [0.6, -0.8] + - [0.8, 0.6] + datatype: float64 + shape: [2, 2] + outputs: [x, y] + translation: !unit/quantity-1.1.0 + unit: !unit/unit-1.0.0 + value: !core/ndarray-1.1.0 + data: [100.0, -50.0] + datatype: float64 + shape: [2] + - !transform/rotate2d-1.3.0 + angle: !unit/quantity-1.1.0 {datatype: float64, unit: !unit/unit-1.0.0 deg, + value: 90.0} + inputs: [x, y] + outputs: [x, y] + inputs: [x, y] + outputs: [x, y] + - ! + frame: ! + axes_names: [x, y] + axes_order: [0, 1] + axis_physical_types: ['custom:x', 'custom:y'] + name: world + unit: [!unit/unit-1.0.0 pixel, !unit/unit-1.0.0 pixel] + transform: null +... diff --git a/ast_tester/testobject.c b/ast_tester/testobject.c index 88b5de87..5f9c6058 100644 --- a/ast_tester/testobject.c +++ b/ast_tester/testobject.c @@ -1,3 +1,5 @@ +/* Test program for the AstObject base class. */ + #include "ast.h" #include #include @@ -12,122 +14,184 @@ static const char *baseName( const char *path ) { return basename( buffer ); } -int main( void ){ - const char *routine; - const char *file; - int i; - int line; - char *pickle1; - char *pickle2; +/* Verify that an Object survives a checkdump (astToString followed by + astFromString) round trip, and that the reloaded Object compares equal + to the original. */ +static void TestCheckToString( void ) { AstSkyFrame *sf = astSkyFrame( " " ); - int bf_line = __LINE__ + 1; AstFrame *bf = astFrame( 2, "Domain=SKY" ); AstFrameSet *fs = astConvert( bf, sf, " " ); - AstKeyMap *km; - void *p; + AstFrameSet *fs2 = NULL; + char *pickle1 = NULL; + char *pickle2 = NULL; - if( fs ) { + if( !fs ) { + if( astOK ) + astError( AST__INTER, "TestCheckToString: astConvert failed.\n" ); /* LCOV_EXCL_LINE */ + } else { pickle1 = astToString( fs ); - int fs2_line = __LINE__ + 1; - AstFrameSet *fs2 = astFromString( pickle1 ); + fs2 = astFromString( pickle1 ); pickle2 = astToString( fs2 ); + if( pickle1 && pickle2 ) { - if( strcmp( pickle1, pickle2 ) && astOK ) { - astError( AST__INTER, "Error 1\n" ); - } + if( strcmp( pickle1, pickle2 ) && astOK ) + astError( AST__INTER, "TestCheckToString: round-tripped strings differ.\n" ); /* LCOV_EXCL_LINE */ } else if( astOK ) { - astError( AST__INTER, "Error 2\n" ); + astError( AST__INTER, "TestCheckToString: astToString returned NULL.\n" ); /* LCOV_EXCL_LINE */ } + if( fs2 && !astEqual( fs, fs2 ) && astOK ) + astError( AST__INTER, "TestCheckToString: reloaded Object is not equal to the original.\n" ); /* LCOV_EXCL_LINE */ + } - pickle1 = astFree( pickle1 ); - pickle2 = astFree( pickle2 ); + pickle1 = astFree( pickle1 ); + pickle2 = astFree( pickle2 ); + sf = astAnnul( sf ); + bf = astAnnul( bf ); - if( fs2 && !astEqual( fs, fs2 ) && astOK ) { - astError( AST__INTER, "Error 3\n" ); - } + if( fs ) + fs = astAnnul( fs ); - astCreatedAt( bf, &routine, &file, &line ); - if( ( !routine || strcmp( routine, "main" ) ) && astOK ) { - astError( AST__INTER, "Error 31\n" ); - } - if( ( !file || strcmp( baseName(file), "testobject.c" ) ) && astOK ) { - astError( AST__INTER, "Error 32\n" ); - } - if( line != bf_line && astOK ) { - astError( AST__INTER, "Error 33 (line is %d, expected %d)\n", line, bf_line ); - } + if( fs2 ) + fs2 = astAnnul( fs2 ); +} +/* Verify that astCreatedAt reports the routine, file and line at which an + Object was created. */ +static void TestCreatedAt( void ) { + const char *routine; + const char *file; + int line; + int bf_line = __LINE__ + 1; + AstFrame *bf = astFrame( 2, "Domain=SKY" ); - km = astActiveObjects( NULL, 0, 0 ); - if( !km && astOK ) { - astError( AST__INTER, "Error 34\n" ); - } else { - int nkey = astMapSize( km ); - if( nkey != 3 && astOK ) { - astError( AST__INTER, "Error 35 (nkey is %d)\n", nkey ); - } + astCreatedAt( bf, &routine, &file, &line ); + if( ( !routine || strcmp( routine, "TestCreatedAt" ) ) && astOK ) + astError( AST__INTER, "TestCreatedAt: routine is '%s'.\n", routine ? routine : "" ); /* LCOV_EXCL_LINE */ + if( ( !file || strcmp( baseName( file ), "testobject.c" ) ) && astOK ) + astError( AST__INTER, "TestCreatedAt: file is '%s'.\n", file ? file : "" ); /* LCOV_EXCL_LINE */ + if( line != bf_line && astOK ) + astError( AST__INTER, "TestCreatedAt: line is %d, expected %d.\n", line, bf_line ); /* LCOV_EXCL_LINE */ - astSetC( km, "SortBy", "KeyUp" ); - for( i=0; i < 3; i++ ){ - const char *key = astMapKey( km, i ); - if( i == 0 ) { - if( strcmp( key, "Frame" ) && astOK ) { - astError( AST__INTER, "Error 36 (key 0 is '%s')\n", key ); - } else if( astMapLength(km,key) != 1 && astOK ) { - astError( AST__INTER, "Error 361 (%d)\n", astMapLength(km,key) ); - } else if( ( !astMapGetElemP( km, key, 0, &p ) || ( p != bf ) ) && astOK ) { - astError( AST__INTER, "Error 362\n" ); - } else { - astCreatedAt( p, &routine, &file, &line ); - if( ( !routine || strcmp( routine, "main" ) ) && astOK ) { - astError( AST__INTER, "Error 363\n" ); - } - if( ( !file || strcmp( baseName(file), "testobject.c" ) ) && astOK ) { - astError( AST__INTER, "Error 364\n" ); - } - if( line != bf_line && astOK ) { - astError( AST__INTER, "Error 365 (line is %d)\n", line ); - } - } - } else if( i == 1 ) { - if( strcmp( key, "FrameSet" ) && astOK ) { - astError( AST__INTER, "Error 37 (key 1 is '%s')\n", key ); - } else if( astMapLength(km,key) != 2 && astOK ) { - astError( AST__INTER, "Error 371 (%d)\n", astMapLength(km,key) ); - } else if( ( !astMapGetElemP( km, key, 1, &p ) || ( p != fs2 ) ) && astOK ) { - astError( AST__INTER, "Error 372\n" ); - } else { - astCreatedAt( p, &routine, &file, &line ); - if( ( !routine || strcmp( routine, "main" ) ) && astOK ) { - astError( AST__INTER, "Error 373\n" ); - } - if( ( !file || strcmp( baseName(file), "testobject.c" ) ) && astOK ) { - astError( AST__INTER, "Error 374\n" ); - } - if( line != fs2_line && astOK ) { - astError( AST__INTER, "Error 375 (line is %d, expected %d)\n", line, fs2_line ); - } - } + bf = astAnnul( bf ); +} + +/* Verify that astActiveObjects reports the Objects active within the + current context, keyed by class, including the creation information for + each one. The objects are created within their own AST context so the + "current" filter yields a deterministic set regardless of any objects + left alive by other tests. */ +static void TestActiveObjects( void ) { + AstSkyFrame *sf; + AstFrame *bf; + AstFrameSet *fs; + AstFrameSet *fs2; + AstKeyMap *km; + const char *routine; + const char *file; + const char *key; + int bf_line; + int fs2_line; + int idx; + int jdx; + int found; + int line; + int nkey; + void *p; + + astBegin; + + sf = astSkyFrame( " " ); + bf_line = __LINE__ + 1; + bf = astFrame( 2, "Domain=SKY" ); + fs = astConvert( bf, sf, " " ); + fs2_line = __LINE__ + 1; + fs2 = astCopy( fs ); + km = astActiveObjects( NULL, 0, 1 ); + + if( !km && astOK ) { + astError( AST__INTER, "TestActiveObjects: astActiveObjects returned NULL.\n" ); /* LCOV_EXCL_LINE */ + } else { + nkey = astMapSize( km ); + if( nkey != 3 && astOK ) + astError( AST__INTER, "TestActiveObjects: nkey is %d, expected 3.\n", nkey ); /* LCOV_EXCL_LINE */ + + astSetC( km, "SortBy", "KeyUp" ); + for( idx = 0; idx < nkey; idx++ ){ + key = astMapKey( km, idx ); + if( idx == 0 ) { + if( strcmp( key, "Frame" ) && astOK ) { + astError( AST__INTER, "TestActiveObjects: key 0 is '%s'.\n", key ); /* LCOV_EXCL_LINE */ + } else if( astMapLength( km, key ) != 1 && astOK ) { + astError( AST__INTER, "TestActiveObjects: Frame count is %d.\n", astMapLength( km, key ) ); /* LCOV_EXCL_LINE */ + } else if( ( !astMapGetElemP( km, key, 0, &p ) || ( p != bf ) ) && astOK ) { + astError( AST__INTER, "TestActiveObjects: Frame pointer mismatch.\n" ); /* LCOV_EXCL_LINE */ + } else { + astCreatedAt( p, &routine, &file, &line ); + if( ( !routine || strcmp( routine, "TestActiveObjects" ) ) && astOK ) + astError( AST__INTER, "TestActiveObjects: Frame routine is '%s'.\n", routine ? routine : "" ); /* LCOV_EXCL_LINE */ + if( ( !file || strcmp( baseName( file ), "testobject.c" ) ) && astOK ) + astError( AST__INTER, "TestActiveObjects: Frame file is '%s'.\n", file ? file : "" ); /* LCOV_EXCL_LINE */ + if( line != bf_line && astOK ) + astError( AST__INTER, "TestActiveObjects: Frame line is %d, expected %d.\n", line, bf_line ); /* LCOV_EXCL_LINE */ + } + } else if( idx == 1 ) { + if( strcmp( key, "FrameSet" ) && astOK ) { + astError( AST__INTER, "TestActiveObjects: key 1 is '%s'.\n", key ); /* LCOV_EXCL_LINE */ + } else if( astMapLength( km, key ) != 2 && astOK ) { + astError( AST__INTER, "TestActiveObjects: FrameSet count is %d.\n", astMapLength( km, key ) ); /* LCOV_EXCL_LINE */ } else { - if( strcmp( key, "SkyFrame" ) && astOK ) { - astError( AST__INTER, "Error 38 (key 2 is '%s')\n", key ); - } else if( astMapLength(km,key) != 1 && astOK ) { - astError( AST__INTER, "Error 381 (%d)\n", astMapLength(km,key) ); + +/* The two FrameSets are both active, but their order within the list is + determined by the order in which the underlying object handles happen + to be allocated, which is not the order of creation. Locate the entry + that corresponds to "fs2" (the FrameSet created in this routine by + astCopy) and verify that its recorded creation information is correct. */ + found = 0; + for( jdx = 0; jdx < 2; jdx++ ) { + if( astMapGetElemP( km, key, jdx, &p ) && p == fs2 ) { + found = 1; + astCreatedAt( p, &routine, &file, &line ); + if( ( !routine || strcmp( routine, "TestActiveObjects" ) ) && astOK ) + astError( AST__INTER, "TestActiveObjects: FrameSet routine is '%s'.\n", routine ? routine : "" ); /* LCOV_EXCL_LINE */ + if( ( !file || strcmp( baseName( file ), "testobject.c" ) ) && astOK ) + astError( AST__INTER, "TestActiveObjects: FrameSet file is '%s'.\n", file ? file : "" ); /* LCOV_EXCL_LINE */ + if( line != fs2_line && astOK ) + astError( AST__INTER, "TestActiveObjects: FrameSet line is %d, expected %d.\n", line, fs2_line ); /* LCOV_EXCL_LINE */ + } } + if( !found && astOK ) + astError( AST__INTER, "TestActiveObjects: fs2 not found among the active FrameSets.\n" ); /* LCOV_EXCL_LINE */ + } + } else { + if( strcmp( key, "SkyFrame" ) && astOK ) { + astError( AST__INTER, "TestActiveObjects: key 2 is '%s'.\n", key ); /* LCOV_EXCL_LINE */ + } else if( astMapLength( km, key ) != 1 && astOK ) { + astError( AST__INTER, "TestActiveObjects: SkyFrame count is %d.\n", astMapLength( km, key ) ); /* LCOV_EXCL_LINE */ } } } km = astAnnul( km ); - - } else if( astOK ){ - astError( AST__INTER, "Error 4\n" ); } + astEnd; +} + +int main( void ){ + astBegin; + + TestCheckToString(); + TestCreatedAt(); + TestActiveObjects(); + + astEnd; + if( astOK ) { printf(" All Object tests passed\n"); + return 0; } else { printf("Object tests failed\n"); + return 1; } return astOK ? 0 : 1; } diff --git a/ast_tester/testobjectkeymap.c b/ast_tester/testobjectkeymap.c new file mode 100644 index 00000000..45189854 --- /dev/null +++ b/ast_tester/testobjectkeymap.c @@ -0,0 +1,190 @@ +/* Test program for the protected astGetKeyMap method of the Object class. + * + * astGetKeyMap is a protected method, so (like testaxis.c and + * testunitnorm.c) this test is compiled with astCLASS defined and uses the + * protected interface directly. + * + * The behaviour covered here includes: + * - Lazy creation of an empty KeyMap on first access + * - Repeated requests returning references to the same KeyMap + * - A deep-copy of the KeyMap taken by astCopy + * - Serialisation that preserves a non-empty KeyMap but omits an empty + * one (ensuring that existing Object dumps are unmodified) + * - Inclusion of the KeyMap in the reported ObjSize. + */ + +#include +#include +#include "sae_par.h" +#include "ast_err.h" + +/* Just ensure astCLASS is defined; what it's defined as is arbitrary for this + purpose */ +#define astCLASS testobjectkeymap +#include "memory.h" +#include "object.h" +#include "frame.h" +#include "keymap.h" + +void astBegin_( void ); +void astEnd_( int * ); + +static void TestCreate( int *status ) { + AstFrame *frm = astFrame( 2, " ", status ); + AstKeyMap *km1; + AstKeyMap *km2; + +/* A new Object has no associated KeyMap, and astHasKeyMap does not create + one. */ + if( astHasKeyMap( frm ) && astOK ) + astError( AST__INTER, "TestCreate: New Object already has a KeyMap.\n", status ); /* LCOV_EXCL_LINE */ + +/* A new Object lazily creates an empty KeyMap on the first request. */ + km1 = astGetKeyMap( frm ); + if( !km1 && astOK ) { + astError( AST__INTER, "TestCreate: astGetKeyMap returned NULL.\n", status ); /* LCOV_EXCL_LINE */ + } else if( astMapSize( km1 ) != 0 && astOK ) { + astError( AST__INTER, "TestCreate: New KeyMap not empty (size %d).\n", status, astMapSize( km1 ) ); /* LCOV_EXCL_LINE */ + } + +/* After astGetKeyMap the Object reports that it has a KeyMap. */ + if( !astHasKeyMap( frm ) && astOK ) + astError( AST__INTER, "TestCreate: astHasKeyMap false after astGetKeyMap.\n", status ); /* LCOV_EXCL_LINE */ + +/* Repeated requests return references to the same KeyMap. Compare with + astSame rather than the pointer values directly--since this is just testing + the internal interface comparing pointers directly happens to work too, but + that's an implementation detal. Better stick to the API contract, also in + case we make this a public API later. */ + km2 = astGetKeyMap( frm ); + if( !astSame( km1, km2 ) && astOK ) + astError( AST__INTER, "TestCreate: astGetKeyMap returned different KeyMaps.\n", status ); /* LCOV_EXCL_LINE */ + + km1 = astAnnul( km1 ); + km2 = astAnnul( km2 ); + frm = astAnnul( frm ); +} + +static void TestCopy( int *status ) { + AstFrame *frm = astFrame( 2, " ", status ); + AstFrame *copy; + AstKeyMap *km1; + AstKeyMap *km2; + int v; + +/* Store some data in the KeyMap. */ + km1 = astGetKeyMap( frm ); + astMapPut0I( km1, "answer", 42, NULL ); + +/* astCopy takes a deep-copy of the KeyMap. */ + copy = astCopy( frm ); + km2 = astGetKeyMap( copy ); + if( astSame( km1, km2 ) && astOK ) + astError( AST__INTER, "TestCopy: Copy shares its KeyMap with the original.\n", status ); /* LCOV_EXCL_LINE */ + v = 0; + if( ( !astMapGet0I( km2, "answer", &v ) || v != 42 ) && astOK ) + astError( AST__INTER, "TestCopy: Copied KeyMap is missing data (got %d).\n", status, v ); /* LCOV_EXCL_LINE */ + +/* Modifying the copy's KeyMap does not affect the original. */ + astMapPut0I( km2, "answer", 7, NULL ); + v = 0; + astMapGet0I( km1, "answer", &v ); + if( v != 42 && astOK ) + astError( AST__INTER, "TestCopy: Modifying the copy affected the original (got %d).\n", status, v ); /* LCOV_EXCL_LINE */ + + km1 = astAnnul( km1 ); + km2 = astAnnul( km2 ); + frm = astAnnul( frm ); + copy = astAnnul( copy ); +} + +static void TestToString( int *status ) { + AstFrame *frm1 = astFrame( 2, " ", status ); + AstFrame *frm2; + AstFrame *empty; + AstKeyMap *km1; + AstKeyMap *km2; + AstKeyMap *km3; + char *str; + int v; + +/* Store some data in the KeyMap. */ + km1 = astGetKeyMap( frm1 ); + astMapPut0I( km1, "answer", 42, NULL ); + +/* A non-empty KeyMap survives a round trip. */ + str = astToString( frm1 ); + if( str && !strstr( str, "KeyMap" ) && astOK ) + astError( AST__INTER, "TestToString: Non-empty KeyMap was not serialised.\n", status ); /* LCOV_EXCL_LINE */ + frm2 = astFromString( str ); + km2 = astGetKeyMap( frm2 ); + v = 0; + if( ( !astMapGet0I( km2, "answer", &v ) || v != 42 ) && astOK ) + astError( AST__INTER, "TestToString: Reloaded KeyMap is missing data (got %d).\n", status, v ); /* LCOV_EXCL_LINE */ + str = astFree( str ); + +/* An empty KeyMap is not serialised at all, so the dump of an Object whose + KeyMap exists but is empty must be free of any "KeyMap" item. */ + empty = astFrame( 2, " ", status ); + km3 = astGetKeyMap( empty ); + str = astToString( empty ); + if( str && strstr( str, "KeyMap" ) && astOK ) + astError( AST__INTER, "TestToString: Empty KeyMap was serialised.\n", status ); /* LCOV_EXCL_LINE */ + str = astFree( str ); + + km1 = astAnnul( km1 ); + km2 = astAnnul( km2 ); + km3 = astAnnul( km3 ); + frm1 = astAnnul( frm1 ); + frm2 = astAnnul( frm2 ); + empty = astAnnul( empty ); +} + +static void TestObjSize( int *status ) { + AstFrame *frm = astFrame( 2, " ", status ); + AstKeyMap *km; + size_t kmsize; + size_t size0; + size_t size1; + +/* The reported size of the Object before it has an associated KeyMap. */ + size0 = astGetObjSize( frm ); + +/* Creating the KeyMap and re-measuring should increase the reported size + by exactly the size of the KeyMap, confirming that astGetObjSize takes + the associated KeyMap into account. */ + km = astGetKeyMap( frm ); + kmsize = astGetObjSize( km ); + + if( kmsize == 0 && astOK ) + astError( AST__INTER, "TestObjSize: KeyMap size should be non-zero\n", status ); /* LCOV_EXCL_LINE */ + + size1 = astGetObjSize( frm ); + if( size1 != size0 + kmsize && astOK ) + astError( AST__INTER, "TestObjSize: ObjSize does not include the KeyMap (%d != %d + %d).\n", status, (int) size1, (int) size0, (int) kmsize ); /* LCOV_EXCL_LINE */ + + km = astAnnul( km ); + frm = astAnnul( frm ); +} + +int main( void ) { + int _status = SAI__OK; + int *status = &_status; + astWatch( status ); + astBegin_(); + + TestCreate( status ); + TestCopy( status ); + TestToString( status ); + TestObjSize( status ); + + astEnd_( status ); + + if( astOK ) { + printf(" All Object KeyMap tests passed\n"); + return 0; + } else { + printf("Object KeyMap tests failed\n"); /* LCOV_EXCL_LINE */ + return 1; + } +} diff --git a/ast_tester/testyamlchan.c b/ast_tester/testyamlchan.c index 1c6575d2..4902b3e2 100644 --- a/ast_tester/testyamlchan.c +++ b/ast_tester/testyamlchan.c @@ -2,6 +2,7 @@ #include "mers.h" #include "sae_par.h" #include +#include #include @@ -20,6 +21,13 @@ static void test_native_encoding_roundtrip( int *status ); static void test_sphmap_roundtrip( int *status ); static void test_divide_roundtrip( int *status ); static void test_rotate_sequence_3d_roundtrip( int *status ); +static void test_affine_roundtrip( int *status ); +static void test_time_equinox( int *status ); +static void test_quantity( int *status ); +static void test_transforms_1d( int *status ); +static void test_transforms_2d( int *status ); +static void test_earthlocation( int *status ); +static void test_yamlchan_dump( int *status ); static int chrMatch( const char *a, const char *b ){ int result = 0; @@ -44,6 +52,13 @@ int main(){ test_sphmap_roundtrip( status ); test_divide_roundtrip( status ); test_rotate_sequence_3d_roundtrip( status ); + test_affine_roundtrip( status ); + test_time_equinox( status ); + test_quantity( status ); + test_transforms_1d( status ); + test_transforms_2d( status ); + test_earthlocation( status ); + test_yamlchan_dump( status ); astEnd; @@ -247,8 +262,10 @@ void test_sphmap_roundtrip( int *status ){ AstFrame *frm2d; AstFrameSet *sphfs; AstObject *sphfs2; + AstObject *sphfs3; AstMapping *sphmap; AstMapping *sphmap2; + AstMapping *sphmap3; if( *status != SAI__OK ) return; @@ -275,10 +292,28 @@ void test_sphmap_roundtrip( int *status ){ check_sphmap_mappings( sphmap, sphmap2, "SphMap round-trip test failed", status ); +/* Write the object recovered from ASDF back out and read it again. This + exercises the path where a spherical_cartesian read from ASDF (a SphMap + flanked by unit ZoomMaps) is re-serialised. */ + astClear( ch, "SourceFile" ); + astSet( ch, "SinkFile=sphmap_roundtrip2.asdf" ); + if( astWrite( ch, sphfs2 ) != 1 ) stopit( 33, status ); + + astClear( ch, "SinkFile" ); + astSet( ch, "SourceFile=sphmap_roundtrip2.asdf" ); + sphfs3 = astRead( ch ); + if( !sphfs3 ) stopit( 34, status ); + + sphmap3 = astGetMapping( (AstFrameSet *) sphfs3, AST__BASE, AST__CURRENT ); + check_sphmap_mappings( sphmap, sphmap3, "SphMap re-serialisation test failed", + status ); + astAnnul( sphmap ); astAnnul( sphmap2 ); + astAnnul( sphmap3 ); astAnnul( sphfs ); astAnnul( sphfs2 ); + astAnnul( sphfs3 ); astAnnul( ch ); } @@ -431,7 +466,9 @@ void test_divide_roundtrip( int *status ){ AstMapping *divide_mapping; AstMapping *divmap1; AstMapping *divmap2; + AstMapping *divmap3; AstObject *divfs2; + AstObject *divfs3; AstPermMap *forkmap; AstPermMap *intrlvmap; AstPermMap *mapa; @@ -536,10 +573,30 @@ void test_divide_roundtrip( int *status ){ check_divide_outputs( divmap1, divmap2, "Divide round-trip test failed", status ); +/* Write the object recovered from ASDF back out and read it again. The first + write above exercised the structural FindDivide path (the hand-built divide + had no summary); this second write exercises the KeyMap fast-path, since + ReadDivide attaches a "divide" summary to the Mapping it returns and + WriteMapping writes it directly via WriteProxyDivide. */ + astClear( ch, "SourceFile" ); + astSet( ch, "SinkFile=divide_roundtrip2.asdf" ); + if( astWrite( ch, divfs2 ) != 1 ) stopit( 58, status ); + + astClear( ch, "SinkFile" ); + astSet( ch, "SourceFile=divide_roundtrip2.asdf" ); + divfs3 = astRead( ch ); + if( !divfs3 ) stopit( 59, status ); + + divmap3 = astGetMapping( (AstFrameSet *) divfs3, AST__BASE, AST__CURRENT ); + check_divide_outputs( divmap1, divmap3, + "Divide re-serialisation test failed", status ); + astAnnul( divmap1 ); astAnnul( divmap2 ); + astAnnul( divmap3 ); astAnnul( divfs ); astAnnul( divfs2 ); + astAnnul( divfs3 ); astAnnul( ch ); } @@ -694,3 +751,302 @@ void test_rotate_sequence_3d_roundtrip( int *status ){ if( *status != SAI__OK ) printf( "rotate_sequence_3d and null-transform regression test failed\n" ); } + + + +/* Test affine round-trip: build a FrameSet whose mapping is a 2-D MatrixMap + followed by a 2-D ShiftMap (the pattern FindAffine recognises), write it to + ASDF as an affine transform, read it back and verify the recovered mapping + gives the same numerical results. Exercises FindAffine, WriteProxyAffine, + WriteAsdfAffine and ReadAffine. */ +void test_affine_roundtrip( int *status ){ + AstYamlChan *ch; + AstFrame *frm_in; + AstFrame *frm_out; + AstFrameSet *affs; + AstObject *affs2; + AstMatrixMap *mm; + AstShiftMap *sm; + AstMapping *affine; + double matrix[4] = { 0.6, -0.8, 0.8, 0.6 }; + double shift[2] = { 100.0, -50.0 }; + + if( *status != SAI__OK ) return; /* LCOV_EXCL_LINE */ + +/* MatrixMap (a rotation, so it will not collapse to a diagonal WinMap) + followed in series by a ShiftMap. */ + mm = astMatrixMap( 2, 2, 0, matrix, " " ); + sm = astShiftMap( 2, shift, " " ); + affine = (AstMapping *) astCmpMap( mm, sm, 1, " " ); + astAnnul( mm ); + astAnnul( sm ); + + frm_in = astFrame( 2, "Domain=PIXEL" ); + frm_out = astFrame( 2, "Domain=WORLD" ); + affs = astFrameSet( frm_in, " " ); + astAddFrame( affs, AST__BASE, affine, frm_out ); + astAnnul( frm_in ); + astAnnul( frm_out ); + astAnnul( affine ); + + ch = astYamlChan( NULL, NULL, " " ); + astSet( ch, "SinkFile=affine_roundtrip.asdf" ); + + if( astWrite( ch, affs ) != 1 ) + stopit( 70, status ); /* LCOV_EXCL_LINE */ + + astClear( ch, "SinkFile" ); + astSet( ch, "SourceFile=affine_roundtrip.asdf" ); + affs2 = astRead( ch ); + + if( !affs2 ) + stopit( 71, status ); /* LCOV_EXCL_LINE */ + + check_equal_transforms( (AstObject *) affs, affs2, + "Affine round-trip test failed", status ); + + astAnnul( affs ); + astAnnul( affs2 ); + astAnnul( ch ); +} + + + +/* Read an ASDF WCS whose current frame is an FK5 celestial frame carrying an + equinox stored as an ASDF time object. Exercises GetTime and the FK5 + celestial-frame reading path. */ +void test_time_equinox( int *status ){ + AstYamlChan *ch; + AstFrameSet *fs; + AstFrame *sf; + const char *eqn; + + if( *status != SAI__OK ) return; /* LCOV_EXCL_LINE */ + + ch = astYamlChan( NULL, NULL, " " ); + astSet( ch, "SourceFile=time_equinox.asdf" ); + fs = (AstFrameSet *) astRead( ch ); + astAnnul( ch ); + + if( !fs ) { + stopit( 80, status ); return; /* LCOV_EXCL_LINE */ + } + + sf = astGetFrame( fs, AST__CURRENT ); + + if( !chrMatch( astGetC( sf, "System" ), "FK5" ) ) + stopit( 81, status ); /* LCOV_EXCL_LINE */ + +/* The equinox came from a time object with value J2010, which AST formats as + a Julian epoch (2009.999...; after the timescale conversion). + TODO: See if this can be fixed. */ + eqn = astGetC( sf, "Equinox" ); + if( ( !eqn || fabs( atof( eqn ) - 2010.0 ) > 0.01 ) && *status == SAI__OK ) { + printf( "Equinox = %s\n", eqn ? eqn : "(null)" ); /* LCOV_EXCL_LINE */ + stopit( 82, status ); /* LCOV_EXCL_LINE */ + } + + astAnnul( sf ); + astAnnul( fs ); +} + + + +/* Read an ASDF WCS whose transform is an affine with matrix and translation + stored as array unit/quantity objects, followed by a rotate2d whose angle + is a scalar unit/quantity. Exercises GetQuantityV (the affine arrays), the + scalar GetQuantity path (the rotation angle), ReadAffine and ReadRotate2d, + and verifies the values were read correctly. */ +void test_quantity( int *status ){ + AstYamlChan *ch; + AstFrameSet *fs; + double xin[1] = { 1.0 }; + double yin[1] = { 0.0 }; + double xout[1]; + double yout[1]; + + if( *status != SAI__OK ) return; /* LCOV_EXCL_LINE */ + + ch = astYamlChan( NULL, NULL, " " ); + astSet( ch, "SourceFile=quantity.asdf" ); + fs = (AstFrameSet *) astRead( ch ); + astAnnul( ch ); + + if( !fs ) { + stopit( 85, status ); return; /* LCOV_EXCL_LINE */ + } + +/* matrix = [[0.6, -0.8],[0.8, 0.6]], translation = [100, -50], so the affine + maps (1, 0) -> (100.6, -49.2); the following 90-degree rotation maps + (x,y) -> (-y,x). */ + astTran2( fs, 1, xin, yin, 1, xout, yout ); + + if( ( fabs( xout[0] - 49.2 ) > 1.0E-9 || fabs( yout[0] - 100.6 ) > 1.0E-9 ) + && *status == SAI__OK ) { + printf( "quantity out = (%g,%g), expected (49.2,100.6)\n", xout[0], yout[0] ); /* LCOV_EXCL_LINE */ + stopit( 86, status ); /* LCOV_EXCL_LINE */ + } + + astAnnul( fs ); +} + + + +/* Read an ASDF WCS whose transform is a 1-D compose Const1D | Linear1D | + Multiply. Exercises ReadConstant, ReadLinear1d and ReadMultiplyScale. + Const1D outputs 2.5 for any input, Linear1D gives 2*2.5+1 = 6, and Multiply + gives 6*3 = 18. */ +void test_transforms_1d( int *status ){ + AstYamlChan *ch; + AstFrameSet *fs; + double xin[1] = { 7.0 }; + double xout[1]; + + if( *status != SAI__OK ) return; /* LCOV_EXCL_LINE */ + + ch = astYamlChan( NULL, NULL, " " ); + astSet( ch, "SourceFile=transforms_1d.asdf" ); + fs = (AstFrameSet *) astRead( ch ); + astAnnul( ch ); + + if( !fs ) { + stopit( 90, status ); return; /* LCOV_EXCL_LINE */ + } + + astTran1( fs, 1, xin, 1, xout ); + if( fabs( xout[0] - 18.0 ) > 1.0E-9 && *status == SAI__OK ) { + printf( "transforms_1d out = %g, expected 18\n", xout[0] ); /* LCOV_EXCL_LINE */ + stopit( 91, status ); /* LCOV_EXCL_LINE */ + } + + astAnnul( fs ); +} + + + +/* Read an ASDF WCS whose transform is fix_inputs(Rotation2D(30), {0:1.5}) + followed by Planar2D. Exercises ReadFixInputs, ReadRotate2d and ReadPlanar2d. + For input x, the rotation acts on (1.5, x); Planar2D then forms + a + 2*b + 3 from the rotated (a,b). */ +void test_transforms_2d( int *status ){ + AstYamlChan *ch; + AstFrameSet *fs; + double c = cos( 30.0*AST__DD2R ); + double s = sin( 30.0*AST__DD2R ); + double a = 1.5*c - 1.0*s; + double b = 1.5*s + 1.0*c; + double expected = a + 2.0*b + 3.0; + double xin[1] = { 1.0 }; + double xout[1]; + + if( *status != SAI__OK ) return; /* LCOV_EXCL_LINE */ + + ch = astYamlChan( NULL, NULL, " " ); + astSet( ch, "SourceFile=transforms_2d.asdf" ); + fs = (AstFrameSet *) astRead( ch ); + astAnnul( ch ); + + if( !fs ) { + stopit( 92, status ); return; /* LCOV_EXCL_LINE */ + } + + astTran1( fs, 1, xin, 1, xout ); + if( fabs( xout[0] - expected ) > 1.0E-8 && *status == SAI__OK ) { + printf( "transforms_2d out = %g, expected %g\n", xout[0], expected ); /* LCOV_EXCL_LINE */ + stopit( 93, status ); /* LCOV_EXCL_LINE */ + } + + astAnnul( fs ); +} + + + +/* Build an AZEL (azimuth-elevation) / AltAz SkyFrame with an observer + location and epoch, write it t. ASDF and read it back. Exercises + WriteAsdfEarthLocation, WriteAsdfQuantity and WriteAsdfTime; reading in + exercises ReadEarthLocation and the scalar GetQuantity/GetTime paths. + The geodetic latitude should survive the geocentric round trip. */ +void test_earthlocation( int *status ){ + AstYamlChan *ch; + AstFrame *pixfrm; + AstSkyFrame *sky; + AstFrameSet *fs; + AstObject *fs2; + AstMapping *um; + AstFrame *sky2; + + if( *status != SAI__OK ) return; /* LCOV_EXCL_LINE */ + + sky = astSkyFrame( "System=AZEL,ObsLat=19.8,ObsLon=-155.5," + "ObsAlt=4200,Epoch=2020.5" ); + + pixfrm = astFrame( 2, "Domain=PIXEL" ); + um = (AstMapping *) astUnitMap( 2, " " ); + fs = astFrameSet( pixfrm, " " ); + astAddFrame( fs, AST__BASE, um, (AstFrame *) sky ); + astAnnul( pixfrm ); + astAnnul( um ); + astAnnul( sky ); + + ch = astYamlChan( NULL, NULL, " " ); + astSet( ch, "SinkFile=earthloc.asdf" ); + + if( astWrite( ch, fs ) != 1 ) + stopit( 94, status ); /* LCOV_EXCL_LINE */ + + astClear( ch, "SinkFile" ); + astSet( ch, "SourceFile=earthloc.asdf" ); + fs2 = astRead( ch ); + astAnnul( ch ); + + if( !fs2 ) + stopit( 95, status ); astAnnul( fs ); return; /* LCOV_EXCL_LINE */ + + +/* The recovered frame should be an AZEL SkyFrame that still carries the + observer location (ObsLat/ObsLon), confirming the earthlocation was + written and read back. */ + sky2 = astGetFrame( (AstFrameSet *) fs2, AST__CURRENT ); + + if( !chrMatch( astGetC( sky2, "System" ), "AZEL" ) ) + stopit( 96, status ); /* LCOV_EXC_LINE */ + + if( ( !astTest( sky2, "ObsLat" ) || !astTest( sky2, "ObsLon" ) ) + && *status == SAI__OK ) stopit( 97, status ); /* LCOV_EXCL_LINE */ + + astAnnul( sky2 ); + astAnnul( fs ); + astAnnul( fs2 ); +} + + + +/* Dump a YamlChan (with its attributes set to non-default values) to a string + using the native AST serialisation and read it back, checking the + attributes survive. Exercises the YamlChan Dump and astLoadYamlChan + routines. */ +void test_yamlchan_dump( int *status ){ + AstYamlChan *ch; + AstObject *ch2; + char *dump; + + if( *status != SAI__OK ) return; /* LCOV_EXCL_LINE */ + + ch = astYamlChan( NULL, NULL, "YamlEncoding=NATIVE" ); + + dump = astToString( ch ); + ch2 = astFromString( dump ); + dump = astFree( dump ); + + if( !ch2 ) + stopit( 100, status ); astAnnul( ch ); return; /* LCOV_EXCL_LINE */ + + if( !astIsAYamlChan( ch2 ) ) + stopit( 101, status ); /* LCOV_EXCL_LINE */ + + if( !chrMatch( astGetC( ch2, "YamlEncoding" ), "NATIVE" ) ) + stopit( 102, status ); /* LCOV_EXCL_LINE */ + + astAnnul( ch ); + astAnnul( ch2 ); +} diff --git a/ast_tester/time_equinox.asdf b/ast_tester/time_equinox.asdf new file mode 100644 index 00000000..b8564015 --- /dev/null +++ b/ast_tester/time_equinox.asdf @@ -0,0 +1,65 @@ +#ASDF 1.0.0 +#ASDF_STANDARD 1.6.0 +%YAML 1.1 +%TAG ! tag:stsci.edu:asdf/ +--- !core/asdf-1.1.0 +asdf_library: !core/software-1.0.0 {author: The ASDF Developers, homepage: 'http://github.com/asdf-format/asdf', + name: asdf, version: 5.1.0} +history: + extensions: + - !core/extension_metadata-1.0.0 + extension_class: asdf.extension._manifest.ManifestExtension + extension_uri: asdf://asdf-format.org/core/extensions/core-1.6.0 + manifest_software: !core/software-1.0.0 {name: asdf_standard, version: 1.5.0} + software: !core/software-1.0.0 {name: asdf, version: 5.1.0} + - !core/extension_metadata-1.0.0 + extension_class: asdf.extension._manifest.ManifestExtension + extension_uri: asdf://asdf-format.org/transform/extensions/transform-1.6.0 + manifest_software: !core/software-1.0.0 {name: asdf_transform_schemas, version: 0.6.0} + software: !core/software-1.0.0 {name: asdf-astropy, version: 0.10.0} + - !core/extension_metadata-1.0.0 + extension_class: asdf.extension._manifest.ManifestExtension + extension_uri: asdf://asdf-format.org/astronomy/coordinates/extensions/coordinates-1.3.0 + manifest_software: !core/software-1.0.0 {name: asdf_coordinates_schemas, version: 0.5.1} + software: !core/software-1.0.0 {name: asdf-astropy, version: 0.10.0} + - !core/extension_metadata-1.0.0 + extension_class: asdf.extension._manifest.ManifestExtension + extension_uri: asdf://asdf-format.org/astronomy/gwcs/extensions/gwcs-1.4.0 + manifest_software: !core/software-1.0.0 {name: asdf_wcs_schemas, version: 0.5.0} + software: !core/software-1.0.0 {name: gwcs, version: 1.0.3} + - !core/extension_metadata-1.0.0 + extension_class: asdf.extension._manifest.ManifestExtension + extension_uri: asdf://astropy.org/astropy/extensions/units-1.3.0 + software: !core/software-1.0.0 {name: asdf-astropy, version: 0.10.0} + - !core/extension_metadata-1.0.0 + extension_class: asdf.extension._manifest.ManifestExtension + extension_uri: asdf://asdf-format.org/astronomy/extensions/astronomy-1.2.0 + manifest_software: !core/software-1.0.0 {name: asdf_standard, version: 1.5.0} + software: !core/software-1.0.0 {name: asdf-astropy, version: 0.10.0} +wcs: ! + name: '' + pixel_shape: null + steps: + - ! + frame: ! + axes_names: [x, y] + axes_order: [0, 1] + axis_physical_types: ['custom:x', 'custom:y'] + name: detector + unit: [!unit/unit-1.0.0 pixel, !unit/unit-1.0.0 pixel] + transform: !transform/identity-1.3.0 + inputs: [x0, x1] + n_dims: 2 + outputs: [x0, x1] + - ! + frame: ! + axes_names: [lon, lat] + axes_order: [0, 1] + axis_physical_types: [pos.eq.ra, pos.eq.dec] + name: fk5 + reference_frame: ! + frame_attributes: + equinox: !time/time-1.1.0 {scale: tt, value: J2010.000} + unit: [!unit/unit-1.0.0 deg, !unit/unit-1.0.0 deg] + transform: null +... diff --git a/ast_tester/transforms_1d.asdf b/ast_tester/transforms_1d.asdf new file mode 100644 index 00000000..7d778fc0 --- /dev/null +++ b/ast_tester/transforms_1d.asdf @@ -0,0 +1,74 @@ +#ASDF 1.0.0 +#ASDF_STANDARD 1.6.0 +%YAML 1.1 +%TAG ! tag:stsci.edu:asdf/ +--- !core/asdf-1.1.0 +asdf_library: !core/software-1.0.0 {author: The ASDF Developers, homepage: 'http://github.com/asdf-format/asdf', + name: asdf, version: 5.1.0} +history: + extensions: + - !core/extension_metadata-1.0.0 + extension_class: asdf.extension._manifest.ManifestExtension + extension_uri: asdf://asdf-format.org/core/extensions/core-1.6.0 + manifest_software: !core/software-1.0.0 {name: asdf_standard, version: 1.5.0} + software: !core/software-1.0.0 {name: asdf, version: 5.1.0} + - !core/extension_metadata-1.0.0 + extension_class: asdf.extension._manifest.ManifestExtension + extension_uri: asdf://asdf-format.org/transform/extensions/transform-1.6.0 + manifest_software: !core/software-1.0.0 {name: asdf_transform_schemas, version: 0.6.0} + software: !core/software-1.0.0 {name: asdf-astropy, version: 0.10.0} + - !core/extension_metadata-1.0.0 + extension_class: asdf.extension._manifest.ManifestExtension + extension_uri: asdf://asdf-format.org/astronomy/gwcs/extensions/gwcs-1.4.0 + manifest_software: !core/software-1.0.0 {name: asdf_wcs_schemas, version: 0.5.0} + software: !core/software-1.0.0 {name: gwcs, version: 1.0.3} + - !core/extension_metadata-1.0.0 + extension_class: asdf.extension._manifest.ManifestExtension + extension_uri: asdf://astropy.org/astropy/extensions/units-1.3.0 + software: !core/software-1.0.0 {name: asdf-astropy, version: 0.10.0} +wcs: ! + name: '' + pixel_shape: null + steps: + - ! + frame: ! + axes_names: [x] + axes_order: [0] + axes_type: [SPATIAL] + axis_physical_types: ['custom:SPATIAL'] + name: in + naxes: 1 + unit: [!unit/unit-1.0.0 pixel] + transform: !transform/compose-1.3.0 + forward: + - !transform/compose-1.3.0 + forward: + - !transform/constant-1.5.0 + dimensions: 1 + inputs: [x] + outputs: [y] + value: 2.5 + - !transform/linear1d-1.0.0 + inputs: [x] + intercept: 1.0 + outputs: [y] + slope: 2.0 + inputs: [x] + outputs: [y] + - !transform/multiplyscale-1.0.0 + factor: 3.0 + inputs: [x] + outputs: [y] + inputs: [x] + outputs: [y] + - ! + frame: ! + axes_names: [y] + axes_order: [0] + axes_type: [SPATIAL] + axis_physical_types: ['custom:SPATIAL'] + name: out + naxes: 1 + unit: [!unit/unit-1.0.0 pixel] + transform: null +... diff --git a/ast_tester/transforms_2d.asdf b/ast_tester/transforms_2d.asdf new file mode 100644 index 00000000..a6a59d25 --- /dev/null +++ b/ast_tester/transforms_2d.asdf @@ -0,0 +1,72 @@ +#ASDF 1.0.0 +#ASDF_STANDARD 1.6.0 +%YAML 1.1 +%TAG ! tag:stsci.edu:asdf/ +--- !core/asdf-1.1.0 +asdf_library: !core/software-1.0.0 {author: The ASDF Developers, homepage: 'http://github.com/asdf-format/asdf', + name: asdf, version: 5.1.0} +history: + extensions: + - !core/extension_metadata-1.0.0 + extension_class: asdf.extension._manifest.ManifestExtension + extension_uri: asdf://asdf-format.org/core/extensions/core-1.6.0 + manifest_software: !core/software-1.0.0 {name: asdf_standard, version: 1.5.0} + software: !core/software-1.0.0 {name: asdf, version: 5.1.0} + - !core/extension_metadata-1.0.0 + extension_class: asdf.extension._manifest.ManifestExtension + extension_uri: asdf://asdf-format.org/transform/extensions/transform-1.6.0 + manifest_software: !core/software-1.0.0 {name: asdf_transform_schemas, version: 0.6.0} + software: !core/software-1.0.0 {name: asdf-astropy, version: 0.10.0} + - !core/extension_metadata-1.0.0 + extension_class: asdf.extension._manifest.ManifestExtension + extension_uri: asdf://asdf-format.org/astronomy/gwcs/extensions/gwcs-1.4.0 + manifest_software: !core/software-1.0.0 {name: asdf_wcs_schemas, version: 0.5.0} + software: !core/software-1.0.0 {name: gwcs, version: 1.0.3} + - !core/extension_metadata-1.0.0 + extension_class: asdf.extension._manifest.ManifestExtension + extension_uri: asdf://astropy.org/astropy/extensions/units-1.3.0 + software: !core/software-1.0.0 {name: asdf-astropy, version: 0.10.0} +wcs: ! + name: '' + pixel_shape: null + steps: + - ! + frame: ! + axes_names: [x] + axes_order: [0] + axes_type: [SPATIAL] + axis_physical_types: ['custom:SPATIAL'] + name: in + naxes: 1 + unit: [!unit/unit-1.0.0 pixel] + transform: !transform/compose-1.3.0 + forward: + - !transform/fix_inputs-1.2.0 + forward: + - !transform/rotate2d-1.3.0 + angle: 29.999999999999996 + inputs: [x, y] + outputs: [x, y] + - keys: [0] + values: [1.5] + inputs: [y] + outputs: [x, y] + - !transform/planar2d-1.0.0 + inputs: [x, y] + intercept: 3.0 + outputs: [z] + slope_x: 1.0 + slope_y: 2.0 + inputs: [y] + outputs: [z] + - ! + frame: ! + axes_names: [y] + axes_order: [0] + axes_type: [SPATIAL] + axis_physical_types: ['custom:SPATIAL'] + name: out + naxes: 1 + unit: [!unit/unit-1.0.0 pixel] + transform: null +... diff --git a/src/loader.c b/src/loader.c index b87d5c2e..33968659 100644 --- a/src/loader.c +++ b/src/loader.c @@ -188,6 +188,7 @@ if ( !strcmp( class, #name ) ) return (AstLoaderType *) astLoad##name##_ LOAD(WcsMap); LOAD(WinMap); LOAD(XmlChan); + LOAD(YamlChan); LOAD(XphMap); LOAD(ZoomMap); diff --git a/src/object.c b/src/object.c index a250144f..73db0e8e 100644 --- a/src/object.c +++ b/src/object.c @@ -238,9 +238,14 @@ f - AST_VERSION: Return the verson of the AST library being used. * structure by 20 bytes. If this turns out to be problematic * this facility could be controlled using a configure option. * 21-NOV-2019 (DSB): -* Include thrThread in public metrhod list, and change it so +* Include thrThread in public method list, and change it so * that it does not report an error if the supplied object handle * is owned by a different thread. +* 16-JUN-2026 (EMB): +* Add the protected astGetKeyMap method, which returns a KeyMap that +* may be associated with any Object for storing extra internal data. +* The companion astHasKeyMap method tests whether an Object already has +* an associated KeyMap without creating one. *class-- */ @@ -1415,6 +1420,13 @@ f function is invoked with STATUS set to an error value, or if it new->ident = astStore( NULL, this->ident, strlen( this->ident ) + 1 ); } +/* Take a deep-copy of any associated KeyMap, if any. */ + if( this->keymap ) { + new->keymap = astCopy( this->keymap ); + } else { + new->keymap = NULL; + } + /* Create a new mutex for the new Object, and lock it for use by the current thread. */ #ifdef THREAD_SAFE @@ -1556,6 +1568,10 @@ f value this->id = astFree( this->id ); this->ident = astFree( this->ident ); +/* Annul any associated KeyMap (the Object holds the only reference). */ + if( this->keymap ) + this->keymap = astAnnul( this->keymap ); + /* Attempt to unlock the Object and destroy its mutexes. */ #if defined(THREAD_SAFE) (void) ManageLock( this, AST__UNLOCK, 0, NULL, status ); @@ -1712,6 +1728,16 @@ static void Dump( AstObject *this, AstChannel *channel, int *status ) { astWriteInt( channel, "Nobj", 0, 0, vtab->nobject, "Count of active Objects in same class" ); +/* KeyMap. */ +/* ------- */ +/* Write out any associated KeyMap, but only if it exists and is not + empty. This keeps the external representation of Objects that have no + associated data (the overwhelming majority) unchanged. */ + if( this->keymap && astMapSize( this->keymap ) > 0 ) { + astWriteObject( channel, "KeyMap", 1, 1, this->keymap, + "KeyMap of associated data" ); + } + /* Terminate the information above with an "IsA" item for the base Object class. */ astWriteIsA( channel, "Object", "AST Object" ); @@ -2332,11 +2358,21 @@ static size_t GetObjSize( AstObject *this, int *status ) { *- */ +/* Local Variables: */ + size_t result; /* Result value to return */ + /* Check the global error status. */ - if ( !astOK ) return 0; + if ( !astOK ) + return 0; -/* Return the object size. */ - return this->size; +/* Start with the size of the Object structure itself, and then add on the + size of any associated KeyMap. */ + result = this->size; + if ( this->keymap ) + result += astGetObjSize( this->keymap ); + +/* Return the total size. */ + return result; } void *astGetProxy_( AstObject *this, int *status ) { @@ -2383,6 +2419,121 @@ void *astGetProxy_( AstObject *this, int *status ) { return this ? this->proxy : NULL; } +AstKeyMap *astGetKeyMap_( AstObject *this, int *status ) { +/* +*+ +* Name: +* astGetKeyMap + +* Purpose: +* Get a pointer to the KeyMap associated with an Object. + +* Type: +* Protected function. + +* Synopsis: +* #include "object.h" +* AstKeyMap *astGetKeyMap( AstObject *this ) + +* Class Membership: +* Object method. + +* Description: +* This function returns a pointer to a KeyMap that is associated with +* the supplied Object and which may be used to stash arbitrary +* additional data within the Object. If the Object does not yet have +* an associated KeyMap, a new, empty KeyMap is created. +* +* The KeyMap is retained by the Object: it is deep-copied whenever the +* Object is copied (using astCopy), it is annulled when the Object is +* deleted, and it is written out through a Channel (as part of the +* Object's serialisation) only if it is not empty. This provides a +* general-purpose scratch area for internal use that survives copying +* and serialisation. + +* Parameters: +* this +* Pointer to the Object. + +* Returned Value: +* A pointer to the associated KeyMap, or NULL if an error occurs. This +* is a cloned pointer (a new reference to the KeyMap retained by the +* Object) and should be annulled by the caller when no longer needed. + +* Notes: +* - This is a protected method, intended for internal use within the +* AST library only. It is deliberately not part of the public +* interface, partly in order to avoid becoming a rat's nest of user- +* supplied unstructured data. +* - A NULL pointer will be returned if this function is invoked with +* the AST error status set, or if it should fail for any reason. +*- +*/ + +/* Check the global error status. */ + if ( !astOK ) + return NULL; + +/* If the Object does not yet have an associated KeyMap, create a new + empty one, retained by the Object. */ + if ( !this->keymap ) + this->keymap = astKeyMap( "", status ); + +/* Return a a new reference to the retained KeyMap, which the caller + should annul when no longer needed. */ + return astClone( this->keymap ); +} + +int astHasKeyMap_( AstObject *this, int *status ) { +/* +*+ +* Name: +* astHasKeyMap + +* Purpose: +* Does an Object already have an associated KeyMap? + +* Type: +* Protected function. + +* Synopsis: +* #include "object.h" +* int astHasKeyMap( AstObject *this ) + +* Class Membership: +* Object method. + +* Description: +* This function reports whether the supplied Object already has an +* associated KeyMap (see astGetKeyMap). Unlike astGetKeyMap, it does +* not create a new KeyMap if none exists, so it can be used to test for +* the presence of stashed data without the side effect of attaching an +* empty KeyMap to the Object. + +* Parameters: +* this +* Pointer to the Object. + +* Returned Value: +* A non-zero value if the Object has an associated KeyMap, or zero if it +* does not (or if an error occurs). + +* Notes: +* - This is a protected method, intended for internal use within the +* AST library only. +* - Zero will be returned if this function is invoked with the AST error +* status set, or if it should fail for any reason. +*- +*/ + +/* Check the global error status. */ + if ( !astOK ) + return 0; + +/* Report whether the Object has an associated KeyMap. */ + return ( this->keymap != NULL ); +} + int astGetRefCount_( AstObject *this, int *status ) { /* *+ @@ -2963,6 +3114,12 @@ static int ManageLock( AstObject *this, int mode, int extra, /* If the operation failed, return a pointer to the failed object. */ if( result && fail ) *fail = this; +/* If the operation on "this" succeeded, perform the same operation on any + KeyMap associated with the Object (and, in turn, on any Objects stored + within that KeyMap). */ + if( !result && this->keymap ) + result = astManageLock( this->keymap, mode, extra, fail ); + /* Return the status value */ return result; } @@ -5677,6 +5834,10 @@ AstObject *astInitObject_( void *mem, size_t size, int init, /* Initialise the pointer to an external object that acts as a proxy for the AST Object within foreign language interfaces. */ new->proxy = NULL; + +/* Initialise the pointer to the optional KeyMap of associated data. This + is created on demand by astGetKeyMap. */ + new->keymap = NULL; } /* If an error occurred, clean up by deleting the new Object. Otherwise @@ -5837,6 +5998,10 @@ AstObject *astLoadObject_( void *mem, size_t size, the AST Object within foreign language interfaces. */ new->proxy = NULL; +/* Read any associated KeyMap. This will be a NULL pointer if the external + representation did not include one (the usual case). */ + new->keymap = astReadObject( channel, "keymap", NULL ); + /* If an error occurred, clean up by deleting the new Object. */ if ( !astOK ) new = astDelete( new ); } diff --git a/src/object.h.in b/src/object.h.in index 8b68e92a..d95cd755 100644 --- a/src/object.h.in +++ b/src/object.h.in @@ -127,10 +127,15 @@ * Obtain the value of the ID attribute for an Object. * astGetIdent * Obtain the value of the Ident attribute for an Object. +* astGetKeyMap +* Obtain a (cloned) pointer to the Object's associated KeyMap, +* creating an empty KeyMap if it does not yet exist. * astGetNobject * Obtain the value of the Nobject attribute for an Object. * astGetRefCount * Obtain the value of the RefCount attribute for an Object. +* astHasKeyMap +* Does the Object already have an associated KeyMap? * astSetAttrib * Set the value of a specified attribute for an Object. * astSetCopy @@ -339,6 +344,14 @@ * output is now identical (17 significant digits) whether AST * is built as C99 or C11, which keeps the test fixtures matching * regardless of the compiler's default C standard. +* 16-JUN-2026 (EMB): +* Added the protected astGetKeyMap method, which returns a KeyMap +* that may be associated with any Object as a scratch area for storing +* extra internal data. May be useful to add to the public API later, +* though there is a danger there of users building ad-hoc data models +* around it; for now it is only intended for internal use. The +* companion astHasKeyMap method tests whether an Object already has an +* associated KeyMap without creating one. */ /* Include files. */ @@ -1543,6 +1556,9 @@ typedef struct AstObject { void *proxy; /* A pointer to an external object that acts as a foreign language proxy for the AST object */ + struct AstKeyMap *keymap; /* Optional KeyMap holding arbitrary data + associated with the Object, for internal + use. Created on demand by astGetKeyMap. */ #if defined(THREAD_SAFE) int locker; /* Thread that has locked this Object */ pthread_mutex_t mutex1; /* Guards access to all elements of the @@ -1569,6 +1585,7 @@ typedef struct AstClassIdentifier { now. */ struct AstChannel; struct KeyMap; +struct AstKeyMap; /* This table contains all information that is the same for all objects in the class (e.g. pointers to its virtual functions). */ @@ -1786,9 +1803,11 @@ const char *astGetAttrib_( AstObject *, const char *, int * ); const char *astGetClass_( const AstObject *, int * ); const char *astGetID_( AstObject *, int * ); const char *astGetIdent_( AstObject *, int * ); +struct AstKeyMap *astGetKeyMap_( AstObject *, int * ); int astClassCompare_( AstObjectVtab *, AstObjectVtab *, int * ); int astGetNobject_( const AstObject *, int * ); int astGetRefCount_( AstObject *, int * ); +int astHasKeyMap_( AstObject *, int * ); int astTestAttrib_( AstObject *, const char *, int * ); int astTestID_( AstObject *, int * ); int astTestIdent_( AstObject *, int * ); @@ -1964,6 +1983,8 @@ astINVOKE(V,astGetAttrib_(astCheckObject(this),attrib,STATUS_PTR)) #define astGetClass(this) astINVOKE(V,astGetClass_((const AstObject *)(this),STATUS_PTR)) #define astGetID(this) astINVOKE(V,astGetID_(astCheckObject(this),STATUS_PTR)) #define astGetIdent(this) astINVOKE(V,astGetIdent_(astCheckObject(this),STATUS_PTR)) +#define astGetKeyMap(this) astINVOKE(O,astGetKeyMap_(astCheckObject(this),STATUS_PTR)) +#define astHasKeyMap(this) astINVOKE(V,astHasKeyMap_(astCheckObject(this),STATUS_PTR)) #define astGetNobject(this) astINVOKE(V,astGetNobject_(astCheckObject(this),STATUS_PTR)) #define astClassCompare(class1,class2) astClassCompare_(class1,class2,STATUS_PTR) #define astGetRefCount(this) astINVOKE(V,astGetRefCount_(astCheckObject(this),STATUS_PTR)) diff --git a/src/yamlchan.c b/src/yamlchan.c index 6f85c2f0..ff191e73 100644 --- a/src/yamlchan.c +++ b/src/yamlchan.c @@ -106,6 +106,19 @@ f The YamlChan class does not define any new routines beyond those * - Fix missing degree to radian conversion in ReadRotateSequence3d. * - Fix handling of rotation_type parameter in ReadRotateSequence3d. * - Fix handling of null transform in the final WCS step. +* 1-JUL-2026 (EMB): +* Fix a crash and a spurious error that could occur when writing +* certain WCS objects to ASDF, caused by mishandling of degree/radian +* unit conversions when simplifying the transform chain. +* 2-JUL-2026 (EMB): +* - Fix reading of the ASDF linear1d transform, which used the wrong +* property name and constructed an incorrect Mapping. +* - Fix reading and writing of observer earth locations, which failed +* to recognise the earthlocation class and wrote scalar quantities in a +* form that could not be read back. +* - Fix serialisation of a YamlChan object itself: it was missing from +* the AST object loader and had an out-of-bounds array access when +* dumping a channel that uses the NATIVE encoding. *class-- */ @@ -331,7 +344,7 @@ static AstObject *(* parent_read)( AstChannel *, int * ); /* Text values used to represent YamlEncoding values externally. These should be in the order defined by the associated constants above. */ -static const char *xencod[1] = { ASDF_STRING }; +static const char *xencod[] = { ASDF_STRING, NATIVE_STRING }; /* Address of this static variable is used as a unique identifier for member of this class. */ @@ -490,8 +503,8 @@ static double GetTime( AstKeyMap *, const char *, AstFrame *, int * ); static int FindAffine( int, int *, AstMapping **, int *, int * ); static int FindDivide( AstYamlChan *, int, int *, AstMapping **, int *, int * ); static int FindRotate3d( int, int *, AstMapping **, int *, int * ); -static int FindSphericalCartesian( int, int *, AstMapping **, int *, int * ); static void CompactMapList( int *, AstMapping **, int * ); +static void CopyProxyKeyMap( AstMapping *, AstKeyMap *, int * ); static void Delete( AstObject *, int * ); static AstMathMap *GetRefMap( AstYamlChan *, const char *, int * ); static int Get0I( AstKeyMap *, const char *, int, int, int * ); @@ -2336,6 +2349,51 @@ static void ExpandAsdf( AstYamlChan *this, AstKeyMap *km, } } +static void CopyProxyKeyMap( AstMapping *map, AstKeyMap *km, int *status ){ +/* +* Name: +* CopyProxyKeyMap + +* Purpose: +* Store a summary of an equivalent ASDF transform in a Mapping. + +* Type: +* Private function. + +* Synopsis: +* #include "yamlchan.h" +* void CopyProxyKeyMap( AstMapping *map, AstKeyMap *km, int *status ) + +* Description: +* This function copies the contents of the supplied KeyMap, which holds +* a "PROXY_TYPE" summary of an equivalent ASDF transform, into the KeyMap +* associated with the supplied Mapping. The various WriteProxy... functions +* look for such a summary when deciding how to write the Mapping out as an +* ASDF transform. + +* Parameters: +* map +* Pointer to the Mapping in which the summary is to be stored. +* km +* Pointer to the KeyMap holding the summary. The caller retains +* ownership of this pointer. +* status +* Pointer to the inherited status variable. +*/ + +/* Local Variables: */ + AstKeyMap *ikm; + +/* Check the global error status. */ + if( !astOK ) + return; + +/* Copy the summary into the Mapping's associated KeyMap. */ + ikm = astGetKeyMap( map ); + astMapCopy( ikm, km ); + ikm = astAnnul( ikm ); +} + static int FindAffine( int series, int *nmap, AstMapping **map_list, int *invert_list, int *status ){ /* @@ -2488,9 +2546,9 @@ static int FindAffine( int series, int *nmap, AstMapping **map_list, map_list[ imap ] = astAnnul( map_list[ imap ] ); map_list[ imap + 1 ] = astAnnul( map_list[ imap + 1 ] ); -/* Store the KeyMap as the proxy pointer in the new CmpMap. Note the - KeyMap pointer is not cloned, so we must not annull it in this function. */ - astSetProxy( new, km ); +/* Store a summary of the affine in the new CmpMap's associated KeyMap. */ + CopyProxyKeyMap( (AstMapping *) new, km, status ); + km = astAnnul( km ); /* Store the new CmpMap pointer in place of the first Note, we do not use astClone here, we must not then annull "new" in this */ @@ -2729,9 +2787,9 @@ static int FindRotate3d( int series, int *nmap, AstMapping **map_list, map_list[ imap + 1 ] = astAnnul( map_list[ imap + 1 ] ); map_list[ imap + 2 ] = astAnnul( map_list[ imap + 2 ] ); -/* Store the KeyMap as the proxy pointer in the new CmpMap. Note the - KeyMap pointer is not clone, so we must not annull it in this function. */ - astSetProxy( new, km ); +/* Store a summary of the rotate3d in the new CmpMap's associated KeyMap. */ + CopyProxyKeyMap( (AstMapping *) new, km, status ); + km = astAnnul( km ); /* Store the new CmpMap pointer in place of the first Note, we do not use astClone here, we must not then annull "new" in this */ @@ -2759,133 +2817,6 @@ static int FindRotate3d( int series, int *nmap, AstMapping **map_list, return result; } -static int FindSphericalCartesian( int series, int *nmap, AstMapping **map_list, - int *invert_list, int *status ){ -/* -* Name: -* FindSphericalCartesian - -* Purpose: -* Search a list of Mappings for a sequence corresponding to an ASDF -* gwcs/spherical_cartesian transform. - -* Type: -* Private function. - -* Synopsis: -* #include "yamlchan.h" -* int FindSphericalCartesian( int series, int *nmap, AstMapping **map_list, -* int *invert_list, int *status ) - -* Class Membership: -* YamlChan member function - -* Description: -* This function searches the supplied list of Mappings for sequences -* that correspond to a gwcs/spherical_cartesian. If no matching -* sequence is found, 0 is returned and the list is left unchanged. -* If one or more matching sequences are found, 1 is returned and the -* list is changed so that each whole sequence is contained in a single -* element. Each such element is a CmpMap with its proxy pointer set -* to a KeyMap with a "SPHERICAL_TO_CARTESIAN" integer entry. -* -* Two patterns are recognised: -* - spherical_to_cartesian: ZoomMap(Nin=2, Zoom=DD2R, not inverted) -* followed by SphMap (inverted). -* - cartesian_to_spherical: SphMap (not inverted) followed by -* ZoomMap(Nout=2, Zoom=DR2D, not inverted). - -* Parameters: -* series -* If non-zero, the Mappings are applied in series (only series -* combinations are checked). -* nmap -* Address of the number of Mappings in the list, updated on exit. -* map_list -* Array of Mapping pointers, updated on exit. -* invert_list -* Array of invert flags, updated on exit. -* status -* Pointer to the inherited status variable. - -* Returned Value: -* Non-zero if a matching sequence was found, zero otherwise. - -*/ - -/* Local Variables: */ - AstCmpMap *new; - AstKeyMap *km; - double zoom; - int imap; - int oldinv0; - int oldinv1; - int result; - int s2c; - -/* Initialise */ - result = 0; - -/* Check inherited status. Only series combinations make sense. */ - if( !astOK || !series ) return result; - -/* Loop through the Mappings in the list, stopping before the last since - we need at least two Mappings for a match. */ - for( imap = 0; imap < *nmap - 1; imap++ ) { - s2c = -1; - -/* Check for spherical_to_cartesian: ZoomMap(Nin=2, Zoom~DD2R, !inv) - followed by SphMap(inv). */ - if( astIsAZoomMap( map_list[ imap ] ) && !invert_list[ imap ] && - astIsASphMap( map_list[ imap + 1 ] ) && invert_list[ imap + 1 ] ) { - zoom = astGetZoom( map_list[ imap ] ); - if( astGetNin( map_list[ imap ] ) == 2 && - fabs( zoom - AST__DD2R ) < 1.0E-12 ) { - s2c = 1; - } - -/* Check for cartesian_to_spherical: SphMap(!inv) followed by - ZoomMap(Nout=2, Zoom~DR2D, !inv). */ - } else if( astIsASphMap( map_list[ imap ] ) && !invert_list[ imap ] && - astIsAZoomMap( map_list[ imap + 1 ] ) && !invert_list[ imap + 1 ] ) { - zoom = astGetZoom( map_list[ imap + 1 ] ); - if( astGetNout( map_list[ imap + 1 ] ) == 2 && - fabs( zoom - AST__DR2D ) < 1.0E-8 ) { - s2c = 0; - } - } - -/* If a matching pair was found, package it as a CmpMap with a proxy KeyMap. */ - if( s2c >= 0 ) { - km = astKeyMap( " ", status ); - astMapPut0C( km, "PROXY_TYPE", "spherical_cartesian", NULL ); - astMapPut0I( km, "SPHERICAL_TO_CARTESIAN", s2c, NULL ); - - oldinv0 = astGetInvert( map_list[ imap ] ); - oldinv1 = astGetInvert( map_list[ imap + 1 ] ); - astSetInvert( map_list[ imap ], invert_list[ imap ] ); - astSetInvert( map_list[ imap + 1 ], invert_list[ imap + 1 ] ); - new = astCmpMap( map_list[ imap ], map_list[ imap + 1 ], 1, " ", status ); - astSetInvert( map_list[ imap ], oldinv0 ); - astSetInvert( map_list[ imap + 1 ], oldinv1 ); - - astSetProxy( new, km ); - - map_list[ imap ] = astAnnul( map_list[ imap ] ); - map_list[ imap + 1 ] = astAnnul( map_list[ imap + 1 ] ); - map_list[ imap ] = (AstMapping *) new; - - imap++; - result = 1; - } - } - -/* Compact the list to remove nullified slots. */ - if( result ) CompactMapList( nmap, map_list, invert_list ); - - return result; -} - static int FindDivide( AstYamlChan *this, int series, int *nmap, AstMapping **map_list, int *invert_list, int *status ){ /* @@ -2913,9 +2844,8 @@ static int FindDivide( AstYamlChan *this, int series, int *nmap, * matching sequence is found, 0 is returned and the list is left * unchanged. If one or more matching sequences are found, 1 is returned * and the list is changed so that each whole sequence is contained in a -* single element. Each such element is a CmpMap with its proxy pointer -* set to a KeyMap with "IS_DIVIDE", "DIVIDE_MAPA" and "DIVIDE_MAPB" -* entries. +* single element. Each such element is a CmpMap whose associated KeyMap +* holds "PROXY_TYPE", "DIVIDE_MAPA" and "DIVIDE_MAPB" entries. * * The divide pattern is a 4-element series chain (as produced by * ReadDivide) consisting of: @@ -3060,7 +2990,6 @@ static int FindDivide( AstYamlChan *this, int series, int *nmap, /* Build the proxy KeyMap. */ km = astKeyMap( " ", status ); astMapPut0C( km, "PROXY_TYPE", "divide", NULL ); - astMapPut0I( km, "IS_DIVIDE", 1, NULL ); astMapPut0A( km, "DIVIDE_MAPA", mapa_clone, NULL ); astMapPut0A( km, "DIVIDE_MAPB", mapb_clone, NULL ); mapa_clone = astAnnul( mapa_clone ); @@ -3087,8 +3016,9 @@ static int FindDivide( AstYamlChan *this, int series, int *nmap, astSetInvert( map_list[ imap + i ], oldinv[ i ] ); } -/* Attach the proxy KeyMap to the packed CmpMap. */ - astSetProxy( new, km ); +/* Store a summary of the divide in the packed CmpMap's associated KeyMap. */ + CopyProxyKeyMap( (AstMapping *) new, km, status ); + km = astAnnul( km ); /* Annul the four individual mapping pointers and replace the first with the packed CmpMap. Leave the other three as NULL (they will @@ -4479,7 +4409,7 @@ static double GetQuantity( AstKeyMap *km, const char *name, const char *unit, * KeyMap entry to use. * unit * A string describing the units in which the Quantity value is to -* be returned. An error is reported if the QUantity cannot be +* be returned. An error is reported if the Quantity cannot be * converted to these units. No conversion occurrs if this is NULL. * usedef * Return the supplied default value without error if the name is @@ -5356,7 +5286,7 @@ static int IsA( AstKeyMap *km, const char *class, int *status ) { result = IsANDArray( km_class, status ); } - } else if( !strncmp( km_class, "astropy/coordinates/earthlocation/", 34 ) ) { + } else if( !strncmp( km_class, "astropy/coordinates/earthlocation-", 34 ) ) { if( !strcmp( "earthlocation", class ) ){ result = IsAEarthLocation( km_class, status ); } @@ -5469,62 +5399,62 @@ MAKE_TEST(Step,gwcs,1,3) MAKE_TEST(Celestial_Frame,gwcs,1,2) MAKE_TEST(Frame2d,gwcs,1,2) MAKE_TEST(Spherical_Cartesian,gwcs,1,3) -MAKE_TEST(Identity,asdf/transform,1,3) -MAKE_TEST(Scale,asdf/transform,1,3) -MAKE_TEST(MultiplyScale,asdf/transform,1,0) -MAKE_TEST(Remap_Axes,asdf/transform,1,4) -MAKE_TEST(Shift,asdf/transform,1,3) -MAKE_TEST(Compose,asdf/transform,1,3) -MAKE_TEST(Concatenate,asdf/transform,1,3) -MAKE_TEST(Constant,asdf/transform,1,5) -MAKE_TEST(Divide,asdf/transform,1,3) -MAKE_TEST(Fix_Inputs,asdf/transform,1,2) -MAKE_TEST(Affine,asdf/transform,1,4) -MAKE_TEST(Rotate2d,asdf/transform,1,3) +MAKE_TEST(Identity,asdf/transform,1,4) +MAKE_TEST(Scale,asdf/transform,1,4) +MAKE_TEST(MultiplyScale,asdf/transform,1,2) +MAKE_TEST(Remap_Axes,asdf/transform,1,5) +MAKE_TEST(Shift,asdf/transform,1,4) +MAKE_TEST(Compose,asdf/transform,1,4) +MAKE_TEST(Concatenate,asdf/transform,1,6) +MAKE_TEST(Constant,asdf/transform,1,6) +MAKE_TEST(Divide,asdf/transform,1,4) +MAKE_TEST(Fix_Inputs,asdf/transform,1,4) +MAKE_TEST(Affine,asdf/transform,1,5) +MAKE_TEST(Rotate2d,asdf/transform,1,5) MAKE_TEST(Rotate_Sequence_3d,asdf/transform,1,3) -MAKE_TEST(Rotate3d,asdf/transform,1,3) -MAKE_TEST(Linear1d,asdf/transform,1,0) -MAKE_TEST(Ortho_Polynomial,asdf/transform,1,0) -MAKE_TEST(Planar2d,asdf/transform,1,0) -MAKE_TEST(Polynomial,asdf/transform,1,2) -MAKE_TEST(Conic_Equal_Area,asdf/transform,1,3) -MAKE_TEST(Conic_Equidistant,asdf/transform,1,3) -MAKE_TEST(Conic_Orthomorphic,asdf/transform,1,3) -MAKE_TEST(Conic_Perspective,asdf/transform,1,3) -MAKE_TEST(Cylindrical_Equal_Area,asdf/transform,1,3) -MAKE_TEST(Cylindrical_Perspective,asdf/transform,1,3) -MAKE_TEST(Mercator,asdf/transform,1,2) -MAKE_TEST(Plate_Carree,asdf/transform,1,2) -MAKE_TEST(Healpix,asdf/transform,1,2) -MAKE_TEST(Healpix_Polar,asdf/transform,1,2) -MAKE_TEST(Bonne_Equal_Area,asdf/transform,1,3) -MAKE_TEST(PolyConic,asdf/transform,1,2) -MAKE_TEST(Hammer_Aitoff,asdf/transform,1,2) -MAKE_TEST(Molleweide,asdf/transform,1,2) -MAKE_TEST(Parabolic,asdf/transform,1,2) -MAKE_TEST(Sanson_Flamsteed,asdf/transform,1,2) -MAKE_TEST(Cobe_Quad_Spherical_Cube,asdf/transform,1,2) -MAKE_TEST(Quad_Spherical_Cube,asdf/transform,1,2) -MAKE_TEST(Tangential_Spherical_Cube,asdf/transform,1,2) -MAKE_TEST(Airy,asdf/transform,1,2) -MAKE_TEST(Gnomonic,asdf/transform,1,2) -MAKE_TEST(Slant_Orthographic,asdf/transform,1,2) -MAKE_TEST(Slant_Zenithal_Perspective,asdf/transform,1,2) -MAKE_TEST(Stereographic,asdf/transform,1,2) -MAKE_TEST(Zenithal_Equal_Area,asdf/transform,1,2) -MAKE_TEST(Zenithal_Equidistant,asdf/transform,1,2) -MAKE_TEST(Zenithal_Perspective,asdf/transform,1,3) -MAKE_TEST(Fk4,astropy/coordinates/frames,1,0) -MAKE_TEST(Fk4Noeterms,astropy/coordinates/frames,1,0) -MAKE_TEST(Fk5,astropy/coordinates/frames,1,0) +MAKE_TEST(Rotate3d,asdf/transform,1,5) +MAKE_TEST(Linear1d,asdf/transform,1,2) +MAKE_TEST(Ortho_Polynomial,asdf/transform,1,2) +MAKE_TEST(Planar2d,asdf/transform,1,2) +MAKE_TEST(Polynomial,asdf/transform,1,3) +MAKE_TEST(Conic_Equal_Area,asdf/transform,1,5) +MAKE_TEST(Conic_Equidistant,asdf/transform,1,5) +MAKE_TEST(Conic_Orthomorphic,asdf/transform,1,5) +MAKE_TEST(Conic_Perspective,asdf/transform,1,5) +MAKE_TEST(Cylindrical_Equal_Area,asdf/transform,1,5) +MAKE_TEST(Cylindrical_Perspective,asdf/transform,1,5) +MAKE_TEST(Mercator,asdf/transform,1,4) +MAKE_TEST(Plate_Carree,asdf/transform,1,4) +MAKE_TEST(Healpix,asdf/transform,1,4) +MAKE_TEST(Healpix_Polar,asdf/transform,1,4) +MAKE_TEST(Bonne_Equal_Area,asdf/transform,1,5) +MAKE_TEST(PolyConic,asdf/transform,1,4) +MAKE_TEST(Hammer_Aitoff,asdf/transform,1,4) +MAKE_TEST(Molleweide,asdf/transform,1,4) +MAKE_TEST(Parabolic,asdf/transform,1,4) +MAKE_TEST(Sanson_Flamsteed,asdf/transform,1,4) +MAKE_TEST(Cobe_Quad_Spherical_Cube,asdf/transform,1,4) +MAKE_TEST(Quad_Spherical_Cube,asdf/transform,1,4) +MAKE_TEST(Tangential_Spherical_Cube,asdf/transform,1,4) +MAKE_TEST(Airy,asdf/transform,1,4) +MAKE_TEST(Gnomonic,asdf/transform,1,4) +MAKE_TEST(Slant_Orthographic,asdf/transform,1,4) +MAKE_TEST(Slant_Zenithal_Perspective,asdf/transform,1,4) +MAKE_TEST(Stereographic,asdf/transform,1,4) +MAKE_TEST(Zenithal_Equal_Area,asdf/transform,1,4) +MAKE_TEST(Zenithal_Equidistant,asdf/transform,1,4) +MAKE_TEST(Zenithal_Perspective,asdf/transform,1,5) +MAKE_TEST(Fk4,astropy/coordinates/frames,1,2) +MAKE_TEST(Fk4Noeterms,astropy/coordinates/frames,1,2) +MAKE_TEST(Fk5,astropy/coordinates/frames,1,2) MAKE_TEST(Ecliptic,astropy/coordinates/frames,1,0) MAKE_TEST(Altaz,astropy/coordinates/frames,1,0) -MAKE_TEST(Galactic,astropy/coordinates/frames,1,0) +MAKE_TEST(Galactic,astropy/coordinates/frames,1,2) MAKE_TEST(SuperGalactic,astropy/coordinates/frames,1,0) -MAKE_TEST(Icrs,astropy/coordinates/frames,1,1) -MAKE_TEST(Time,asdf/time,1,1) -MAKE_TEST(EarthLocation,astropy/coordinates/earthlocation,1,0) -MAKE_TEST(Quantity,asdf/unit,1,1) +MAKE_TEST(Icrs,astropy/coordinates/frames,1,3) +MAKE_TEST(Time,asdf/time,1,4) +MAKE_TEST(EarthLocation,astropy/coordinates/earthlocation,1,2) +MAKE_TEST(Quantity,asdf/unit,1,3) MAKE_TEST(NDArray,asdf/core,1,1) #undef MAKE_TEST @@ -5766,7 +5696,6 @@ static AstKeyMap *IsAsdfTransform( AstYamlChan *this, AstCmpMap *map, int old_inv0; int old_inv1; int series; - void *proxy; /* Initialise returned values. */ ret = NULL; @@ -5782,23 +5711,15 @@ static AstKeyMap *IsAsdfTransform( AstYamlChan *this, AstCmpMap *map, to a single ASDF transform, return immediately. The "proxy" pointer provided by the AstObject class is subverted here to provide an indication of whether the CmpMap has been checked. If the proxy pointer - associated with the supplied CmpMap is NULL (the default), then it has - not previously been checked. If the proxy pointer is equal to the value - of macro NOTASDF, then it has been checked and found not to be equivalent - to an ASDF transform. If the proxy pointer has any other value, the - CmpMap has been checked and has been found to be equivalent to an ASDF - transform. The proxy pointer value will then be a pointer to a KeyMap - holding the properties of the equivalent ASDF transform. */ - proxy = astGetProxy( map ); - if( proxy == NOTASDF ) { + is equal to the value of macro NOTASDF, then it has been checked and + found not to be equivalent to an ASDF transform. Note, a CmpMap that is + equivalent to an ASDF transform is flagged differently, by storing a + summary of the transform in its associated KeyMap (see CopyProxyKeyMap); + such CmpMaps are written out by WriteMapping before they reach this + function. */ + if( astGetProxy( map ) == NOTASDF ) { return ret; -/* If the supplied CmpMap has already been checked and is equivalent - to a single ASDF transform, write out the equivalent ASDF transform - and return. This annulls the KeyMap pointer. */ - } else if( proxy ){ - ret = WriteProxy( this, (AstMapping *) map, mapinv, name, status ); - /* If the supplied CmpMap has not been checked, we check it now. */ } else { @@ -5813,8 +5734,7 @@ static AstKeyMap *IsAsdfTransform( AstYamlChan *this, AstCmpMap *map, &nmap, &map_list, &invert_list ); /* Now search the list for sequences that match an equivalent ASDF - transform. Currently we check for affine, rotate3d, spherical_cartesian - and divide. + transform. Currently we check for affine, rotate3d and divide. If no matching sequence is found, 0 will be returned and the list will be left unchanged. If one or more matching sequences are found, 1 will be @@ -5824,7 +5744,6 @@ static AstKeyMap *IsAsdfTransform( AstYamlChan *this, AstCmpMap *map, properties of the equivalent ASDF transform. */ changed = FindRotate3d( series, &nmap, map_list, invert_list, status ); changed |= FindAffine( series, &nmap, map_list, invert_list, status ); - changed |= FindSphericalCartesian( series, &nmap, map_list, invert_list, status ); changed |= FindDivide( this, series, &nmap, map_list, invert_list, status ); /* If the list was changed, the supplied CmpMap either is, or contains, @@ -7652,6 +7571,7 @@ static AstMapping *ReadDivide( AstYamlChan *this, AstKeyMap *km, int *status ){ AstCmpMap *t1; AstCmpMap *t2; AstKeyMap *map_kms[2]; + AstKeyMap *pkm; AstMapping *mapa; AstMapping *mapb; AstMapping *mm1d; @@ -7773,6 +7693,18 @@ static AstMapping *ReadDivide( AstYamlChan *this, AstKeyMap *km, int *status ){ if( divmap ) divmap = astAnnul( divmap ); } +/* Store a summary of the equivalent ASDF divide in the KeyMap associated + with the returned Mapping. On write-back this lets WriteMapping emit the + divide directly (via WriteProxyDivide) without re-running the structural + matching in FindDivide. */ + if( result ){ + pkm = astGetKeyMap( result ); + astMapPut0C( pkm, "PROXY_TYPE", "divide", NULL ); + astMapPut0A( pkm, "DIVIDE_MAPA", mapa, NULL ); + astMapPut0A( pkm, "DIVIDE_MAPB", mapb, NULL ); + pkm = astAnnul( pkm ); + } + if( mapa ) mapa = astAnnul( mapa ); if( mapb ) mapb = astAnnul( mapb ); } @@ -8312,7 +8244,7 @@ static AstMapping *ReadLinear1d( AstKeyMap *km, int *status ){ AstMapping *result; double ina; double inb; - double offset; + double intercept; double outa; double outb; double slope; @@ -8331,9 +8263,9 @@ static AstMapping *ReadLinear1d( AstKeyMap *km, int *status ){ /* Create the returned Mapping. */ } else { -/* Get the slope and offset. Report an error if the slope is zero. */ +/* Get the slope and intercept. Report an error if the slope is zero. */ slope = Get0D( km, "slope", 0, AST__BAD, status ); - offset = Get0D( km, "offset", 0, AST__BAD, status ); + intercept = Get0D( km, "intercept", 0, AST__BAD, status ); if( slope == 0.0 ) { if( astOK ) { astError( AST__BYAML, "astRead(YamlChan): Supplied ASDF " @@ -8341,12 +8273,14 @@ static AstMapping *ReadLinear1d( AstKeyMap *km, int *status ){ status ); } -/* Ortherwise, create a corresponding winmap. */ +/* Otherwise, create a corresponding WinMap. The linear1d transform is + y = slope*x + intercept, so map the window [0,1] on the input onto the + window [intercept, slope+intercept] on the output. */ } else { ina = 0.0; - outa = offset; - inb = offset/slope; - outa = 2*offset; + inb = 1.0; + outa = intercept; + outb = slope + intercept; result = (AstMapping *) astWinMap( 1, &ina, &inb, &outa, &outb, " ", status ); } @@ -11935,7 +11869,7 @@ static int SimplifyAsdf( AstYamlChan *this, AstKeyMap **km, int *status ){ if( *pkm ) *(pw++) = *pkm; } nkm = ( pw - km_list ); - while( *pw < *pkm ){ + while( pw < pkm ){ *(pw++) = NULL; } } @@ -11985,6 +11919,12 @@ static int SimplifyAsdf( AstYamlChan *this, AstKeyMap **km, int *status ){ ret = 1; /* Some simplification has taken place */ fillgaps = 1; /* Shuffle down to fill vacated slots */ nr2d_removed += 2; /* Increment no. of removed conversions */ + +/* The current transform has been removed, so it must not be treated as the + "preceding" transform on the next pass (otherwise a third adjacent + conversion would try to cancel against this now-NULL slot). Mark it + neutral. */ + r2d = 0; } /* Likewise, if the current transform is used to convert from degrees @@ -11997,6 +11937,10 @@ static int SimplifyAsdf( AstYamlChan *this, AstKeyMap **km, int *status ){ ret = 1; fillgaps = 1; nr2d_removed += 2; + +/* As above, mark this removed transform neutral so it is not used as the + preceding transform on the next pass. */ + r2d = 0; } } @@ -12013,7 +11957,7 @@ static int SimplifyAsdf( AstYamlChan *this, AstKeyMap **km, int *status ){ if( *pkm ) *(pw++) = *pkm; } nkm = ( pw - km_list ); - while( *pw < *pkm ){ + while( pw < pkm ){ *(pw++) = NULL; } } @@ -12088,7 +12032,7 @@ static int SimplifyAsdf( AstYamlChan *this, AstKeyMap **km, int *status ){ if( *pkm ) *(pw++) = *pkm; } nkm = ( pw - km_list ); - while( *pw < *pkm ){ + while( pw < pkm ){ *(pw++) = NULL; } } @@ -14902,9 +14846,14 @@ static AstKeyMap *WriteAsdfQuantity( AstYamlChan *this, int ndim, const int *dim /* Create the returned KeyMap and store the appropriate ASDF tag. */ ret = StartAsdfKeyMap( this, 0, "asdf/unit/quantity-1.1.0", status ); -/* Write out the ND-array. */ - km = WriteAsdfNdArray( this, ndim, dims, vals, status ); - ret = StoreKeyMap( this, "value", ret, &km, status ); +/* Write out the value. ndim=0 is written as a scalar value; + an array is written as an ndarray. */ + if( ndim == 0 ) { + Store0D( this, "value", ret, vals[ 0 ], status ); + } else { + km = WriteAsdfNdArray( this, ndim, dims, vals, status ); + ret = StoreKeyMap( this, "value", ret, &km, status ); + } /* Write out the units. */ Store0C( this, "unit", 0, ret, units, NULL, status ); @@ -15785,52 +15734,70 @@ static AstKeyMap *WriteMapping( AstYamlChan *this, AstMapping *map, */ /* Local Variables: */ + AstKeyMap *km; AstKeyMap *ret; /* Initialise */ ret = NULL; /* Check the global error status. */ - if ( !astOK ) return ret; + if ( !astOK ) + return ret; + +/* If the Mapping carries a summary of an equivalent ASDF transform in its + associated KeyMap, because it was created by reading such a transform + (e.g. ReadAffine) or by the structural matching performed by + IsAsdfTransform (e.g. FindAffine), write that transform out directly, + skipping any further structural analysis. */ + if( astHasKeyMap( map ) ) { + km = astGetKeyMap( map ); + if( astMapHasKey( km, "PROXY_TYPE" ) ) { + ret = WriteProxy( this, map, mapinv, name, status ); + } + km = astAnnul( km ); + } -/* Currently, only the following classes of AST Mapping can be converted to +/* Otherwise, only the following classes of AST Mapping can be converted to ASDF Yaml. */ - if( astIsACmpMap( map ) ) { - ret = WriteCmpMap( this, (AstCmpMap *) map, mapinv, name, status ); + if( !ret ) { + if( astIsACmpMap( map ) ) { + ret = WriteCmpMap( this, (AstCmpMap *) map, mapinv, name, status ); - } else if( astIsATranMap( map ) ) { - ret = WriteTranMap( this, (AstTranMap *) map, mapinv, name, status ); + } else if( astIsATranMap( map ) ) { + ret = WriteTranMap( this, (AstTranMap *) map, mapinv, name, status ); - } else if( astIsAUnitMap( map ) ) { - ret = WriteUnitMap( this, (AstUnitMap *) map, mapinv, name, status ); + } else if( astIsAUnitMap( map ) ) { + ret = WriteUnitMap( this, (AstUnitMap *) map, mapinv, name, status ); - } else if( astIsAZoomMap( map ) ) { - ret = WriteZoomMap( this, (AstZoomMap *) map, mapinv, name, status ); + } else if( astIsAZoomMap( map ) ) { + ret = WriteZoomMap( this, (AstZoomMap *) map, mapinv, name, status ); - } else if( astIsAShiftMap( map ) ) { - ret = WriteShiftMap( this, (AstShiftMap *) map, mapinv, name, status ); + } else if( astIsAShiftMap( map ) ) { + ret = WriteShiftMap( this, (AstShiftMap *) map, mapinv, name, status ); - } else if( astIsAWinMap( map ) ) { - ret = WriteWinMap( this, (AstWinMap *) map, mapinv, name, status ); + } else if( astIsAWinMap( map ) ) { + ret = WriteWinMap( this, (AstWinMap *) map, mapinv, name, status ); - } else if( astIsAMatrixMap( map ) ) { - ret = WriteMatrixMap( this, (AstMatrixMap *) map, mapinv, name, status ); + } else if( astIsAMatrixMap( map ) ) { + ret = WriteMatrixMap( this, (AstMatrixMap *) map, mapinv, name, status ); - } else if( astIsAPermMap( map ) ) { - ret = WritePermMap( this, (AstPermMap *) map, mapinv, name, status ); + } else if( astIsAPermMap( map ) ) { + ret = WritePermMap( this, (AstPermMap *) map, mapinv, name, status ); - } else if( astIsAPolyMap( map ) ) { - ret = WritePolyMap( this, (AstPolyMap *) map, mapinv, name, status ); + } else if( astIsAPolyMap( map ) ) { + ret = WritePolyMap( this, (AstPolyMap *) map, mapinv, name, status ); - } else if( astIsAWcsMap( map ) ) { - ret = WriteWcsMap( this, (AstWcsMap *) map, mapinv, name, status ); + } else if( astIsAWcsMap( map ) ) { + ret = WriteWcsMap( this, (AstWcsMap *) map, mapinv, name, status ); - } else if( astIsASphMap( map ) ) { - ret = WriteSphMap( this, (AstSphMap *) map, mapinv, name, status ); + } else if( astIsASphMap( map ) ) { + ret = WriteSphMap( this, (AstSphMap *) map, mapinv, name, status ); + } } /* Annul the returned object if an error occurred. */ - if( !astOK ) ret = astAnnul( ret ); + if( !astOK ) + ret = astAnnul( ret ); /* Return the answer. */ return ret; @@ -16396,17 +16363,6 @@ static AstKeyMap *WriteProxyAffine( AstYamlChan *this, AstKeyMap *km, return ret; } -static AstKeyMap *WriteProxySphericalCartesian( AstYamlChan *this, AstKeyMap *km, - AstMapping *map, AstObject *mapinv, - const char *name, int *status ) { - int s2c; - AstKeyMap *ret = NULL; - if( astMapGet0I( km, "SPHERICAL_TO_CARTESIAN", &s2c ) ) { - ret = WriteAsdfSphericalCartesian( this, s2c, mapinv, name, status ); - } - return ret; -} - static AstKeyMap *WriteProxyDivide( AstYamlChan *this, AstKeyMap *km, AstMapping *map, AstObject *mapinv, const char *name, int *status ) { @@ -16443,24 +16399,25 @@ static AstKeyMap *WriteProxy( AstYamlChan *this, AstMapping *map, AstObject *map * Description: * This function creates and returns a new KeyMap holding the full ASDF -* description of an ASDF transform that is summarised in a KeyMap stored -* as the proxy pointer in a supplied Mapping. +* description of an ASDF transform that is summarised in the KeyMap +* associated with a supplied Mapping. * -* Each Find... function (FindRotate3d, FindAffine, etc.) stores a -* "PROXY_TYPE" string in the proxy KeyMap identifying which ASDF -* transform type was found. WriteProxy looks up that tag in the -* proxy_writers dispatch table and calls the corresponding handler. -* To add support for a new proxy type, add a Find... function that -* stores the appropriate PROXY_TYPE, a WriteProxy* handler, and an -* entry in the table below. +* The summary holds a "PROXY_TYPE" string identifying which ASDF +* transform type the Mapping is equivalent to. It is stored either by +* the Read... functions when reading such a transform (e.g. ReadAffine) +* or by the Find... functions when matching the structure of a hand-built +* Mapping (e.g. FindAffine). WriteProxy looks up that tag in the +* proxy_writers dispatch table and calls the corresponding handler. To +* add support for a new proxy type, store the appropriate PROXY_TYPE, add +* a WriteProxy* handler, and add an entry in the table below. * Parameters: * this * Pointer to the YamlChan. * map -* Pointer to a Mapping that has a proxy KeyMap holding a summary -* of the ASDF transform to write out. The KeyMap is deleted and -* the proxy pointer reset by this function. +* Pointer to a Mapping whose associated KeyMap holds a summary of the +* ASDF transform to write out. The summary is left unchanged so that +* the Mapping can be written out again if required. * mapinv * Pointer to an optional custom inverse mapping. The forward * transformation of the supplied mapping (if any) is used to define @@ -16475,8 +16432,8 @@ static AstKeyMap *WriteProxy( AstYamlChan *this, AstMapping *map, AstObject *map * Returned Value: * A new KeyMap containing the full ASDF description of the transform -* summarised in the proxy KeyMap of the supplied Mapping, or NULL if -* there is no proxy. +* summarised in the associated KeyMap of the supplied Mapping, or NULL +* if the Mapping has no such summary. * Notes: * - A NULL pointer will be returned if this function is invoked @@ -16489,10 +16446,9 @@ static AstKeyMap *WriteProxy( AstYamlChan *this, AstMapping *map, AstObject *map const char *type; ProxyWriter writer; } proxy_writers[] = { - { "rotate3d", WriteProxyRotate3d }, - { "affine", WriteProxyAffine }, - { "spherical_cartesian", WriteProxySphericalCartesian }, - { "divide", WriteProxyDivide }, + { "rotate3d", WriteProxyRotate3d }, + { "affine", WriteProxyAffine }, + { "divide", WriteProxyDivide }, }; static const int nwriters = sizeof(proxy_writers)/sizeof(proxy_writers[0]); @@ -16501,7 +16457,6 @@ static AstKeyMap *WriteProxy( AstYamlChan *this, AstMapping *map, AstObject *map AstKeyMap *ret; const char *proxy_type; int i; - void *proxy; /* Assume failure. */ ret = NULL; @@ -16509,39 +16464,37 @@ static AstKeyMap *WriteProxy( AstYamlChan *this, AstMapping *map, AstObject *map /* Check the global error status. */ if ( !astOK ) return ret; -/* Get the proxy KeyMap. */ - proxy = astGetProxy( map ); - if( proxy ) { - km = (AstKeyMap *) proxy; +/* If the Mapping has no associated KeyMap, there is no summary to write. */ + if( !astHasKeyMap( map ) ) + return ret; + +/* Get the Mapping's associated KeyMap, which should hold a summary of an + equivalent ASDF transform. */ + km = astGetKeyMap( map ); /* Look up the PROXY_TYPE tag and dispatch to the appropriate handler. */ - proxy_type = NULL; - if( astMapGet0C( km, "PROXY_TYPE", &proxy_type ) ) { - const char *resolved_name = GetName( this, name, map, status ); - for( i = 0; i < nwriters; i++ ) { - if( !strcmp( proxy_type, proxy_writers[ i ].type ) ) { - ret = proxy_writers[ i ].writer( this, km, map, mapinv, - resolved_name, status ); - break; - } - } - if( i == nwriters ) { - astError( AST__INTER, "WriteProxy(YamlChan): proxy KeyMap has " - "unrecognised PROXY_TYPE \"%s\" -- was it added to the " - "proxy_writers table? (internal AST programming error)", - status, proxy_type ); + proxy_type = NULL; + if( astMapGet0C( km, "PROXY_TYPE", &proxy_type ) ) { + const char *resolved_name = GetName( this, name, map, status ); + for( i = 0; i < nwriters; i++ ) { + if( !strcmp( proxy_type, proxy_writers[ i ].type ) ) { + ret = proxy_writers[ i ].writer( this, km, map, mapinv, + resolved_name, status ); + break; } - } else { - astError( AST__INTER, "WriteProxy(YamlChan): proxy KeyMap has no " - "PROXY_TYPE entry -- did the Find function " - "forget to set it? (internal AST programming error)", - status ); } - -/* Annull the KeyMap and reset the proxy pointer to NULL. */ - astSetProxy( map, astAnnul( km ) ); + if( i == nwriters ) { + astError( AST__INTER, "WriteProxy(YamlChan): KeyMap has " + "unrecognised PROXY_TYPE \"%s\" -- was it added to the " + "proxy_writers table? (internal AST programming error)", + status, proxy_type ); + } } +/* Annul our reference to the KeyMap. The summary is retained by the Mapping + so it can be written out again if required. */ + km = astAnnul( km ); + /* Return the answer. */ return ret; }