1+ import { logger } from '@alleninstitute/vis-core' ;
12import { Box2D , type Interval , Vec2 , type box2D , type vec2 } from '@alleninstitute/vis-geometry' ;
23
34type DziTilesRoot = `${string } _files/`;
5+ type DziFormat = 'jpeg' | 'png' | 'jpg' | 'JPG' | 'PNG' ;
6+ const isDziFormat = ( format : string ) : format is DziFormat => [ 'jpeg' , 'png' , 'jpg' , 'JPG' , 'PNG' ] . includes ( format ) ;
7+
48// see https://learn.microsoft.com/en-us/previous-versions/windows/silverlight/dotnet-windows-silverlight/cc645077(v=vs.95)?redirectedfrom=MSDN
59// TODO find a less ancient spec...
610export type DziImage = {
711 imagesUrl : DziTilesRoot ; // lets say you found a dzi at http://blah.com/deepzoom.dzi
812 // imagesUrl would be the path which contains all the files for the actual image tiles:
913 // in this example:
1014 // http://blah.com/deepzoom_files/
11- format : 'jpeg' | 'png' | 'jpg' | 'JPG' | 'PNG' ;
15+ format : DziFormat ;
1216 overlap : number ; // in pixels, ADDED every side of any given tile (for example, with overlap=1 and tilesize=256, you could see a jpeg of size 258x258).
1317 // note that tiles on the edge wont have padding (on a per edge basis!)
1418 tileSize : number ;
@@ -27,6 +31,67 @@ export type DziTile = {
2731 relativeLocation : box2D ;
2832 layer : number ;
2933} ;
34+
35+ /**
36+ * Fetches the metadata for a Deep Zoom Image (DZI) from a given URL.
37+ *
38+ * @param url The URL to a DZI metadata file, which should be an XML file containing the metadata for a Deep Zoom Image
39+ * @returns A DZI image object containing the metadata for the Deep Zoom Image
40+ */
41+ export async function fetchDziMetadata ( url : string ) : Promise < DziImage | undefined > {
42+ return fetch ( url )
43+ . then ( ( response ) => response . text ( ) )
44+ . then ( ( xmlString ) => decodeDzi ( xmlString , url ) ) ;
45+ }
46+
47+ function decodeDzi ( xmlString : string , url : string ) : DziImage | undefined {
48+ const parser = new DOMParser ( ) ;
49+ const doc = parser . parseFromString ( xmlString , 'text/xml' ) ;
50+ const err = doc . querySelector ( 'parsererror' ) ;
51+ if ( err ) {
52+ logger . error ( `Failed to parse DZI XML from ${ url } with content:` , xmlString ) ;
53+ return undefined ;
54+ }
55+
56+ const img = doc . getElementsByTagName ( 'Image' ) [ 0 ] ;
57+ const size = doc . getElementsByTagName ( 'Size' ) ?. [ 0 ] ;
58+ const [ format , overlap , tileSize ] = [
59+ img . getAttribute ( 'Format' ) ,
60+ img . getAttribute ( 'Overlap' ) ,
61+ img . getAttribute ( 'TileSize' ) ,
62+ ] ;
63+
64+ if ( ! size || ! format || ! overlap || ! tileSize ) {
65+ logger . error ( `Failed to parse DZI XML from ${ url } : Missing required attributes` ) ;
66+ return undefined ;
67+ }
68+
69+ const width = size . getAttribute ( 'Width' ) ;
70+ const height = size . getAttribute ( 'Height' ) ;
71+ const splits = url . split ( '.dzi' ) ;
72+
73+ if ( ! width || ! height || ! splits || splits . length < 1 ) {
74+ logger . error ( `Failed to parse DZI XML from ${ url } : Missing size or URL splits` ) ;
75+ return undefined ;
76+ }
77+
78+ if ( ! isDziFormat ( format ) ) {
79+ logger . error ( `Failed to parse DZI XML from ${ url } : Invalid format "${ format } "` ) ;
80+ return undefined ;
81+ }
82+
83+ return {
84+ imagesUrl : `${ splits [ 0 ] } _files/` ,
85+ format : format ,
86+ overlap : Number . parseInt ( overlap , 10 ) ,
87+ tileSize : Number . parseInt ( tileSize , 10 ) ,
88+ size : {
89+ width : Number . parseInt ( width , 10 ) ,
90+ height : Number . parseInt ( height , 10 ) ,
91+ } ,
92+ } ;
93+ }
94+
3095function tileUrl ( dzi : DziImage , level : number , tile : TileIndex ) : string {
3196 return `${ dzi . imagesUrl } ${ level . toFixed ( 0 ) } /${ tile . col . toFixed ( 0 ) } _${ tile . row . toFixed ( 0 ) } .${ dzi . format } ` ;
3297}
0 commit comments