@@ -245,6 +245,169 @@ describe("WmtsCapabilities1", () => {
245245 expect ( googleTms ?. length ) . toEqual ( 2 ) ;
246246 } ) ;
247247
248+ it ( "should parse TileMatrixSetLimits from great-artesian-basin" , ( ) => {
249+ // Minimal WMTS capabilities XML with TileMatrixSetLimits to test parsing.
250+ // Based on the great-artesian-basin.xml fixture structure.
251+ const xml = `<?xml version="1.0" encoding="UTF-8"?>
252+ <Capabilities xmlns="http://www.opengis.net/wmts/1.0" xmlns:ows="http://www.opengis.net/ows/1.1" version="1.0.0">
253+ <Contents>
254+ <Layer>
255+ <ows:Title>TestLayer</ows:Title>
256+ <ows:Identifier>test-layer</ows:Identifier>
257+ <Style isDefault="true"><ows:Identifier>default</ows:Identifier></Style>
258+ <Format>image/png</Format>
259+ <TileMatrixSetLink>
260+ <TileMatrixSet>EPSG:3857</TileMatrixSet>
261+ <TileMatrixSetLimits>
262+ <TileMatrixLimits>
263+ <TileMatrix>EPSG:3857:0</TileMatrix>
264+ <MinTileRow>0</MinTileRow>
265+ <MaxTileRow>0</MaxTileRow>
266+ <MinTileCol>0</MinTileCol>
267+ <MaxTileCol>0</MaxTileCol>
268+ </TileMatrixLimits>
269+ <TileMatrixLimits>
270+ <TileMatrix>EPSG:3857:5</TileMatrix>
271+ <MinTileRow>16</MinTileRow>
272+ <MaxTileRow>19</MaxTileRow>
273+ <MinTileCol>27</MinTileCol>
274+ <MaxTileCol>30</MaxTileCol>
275+ </TileMatrixLimits>
276+ </TileMatrixSetLimits>
277+ </TileMatrixSetLink>
278+ </Layer>
279+ <TileMatrixSet>
280+ <ows:Identifier>EPSG:3857</ows:Identifier>
281+ <ows:SupportedCRS>urn:ogc:def:crs:EPSG::3857</ows:SupportedCRS>
282+ <TileMatrix><ows:Identifier>EPSG:3857:0</ows:Identifier><ScaleDenominator>559082264</ScaleDenominator><TopLeftCorner>-20037508 20037508</TopLeftCorner><TileWidth>256</TileWidth><TileHeight>256</TileHeight><MatrixWidth>1</MatrixWidth><MatrixHeight>1</MatrixHeight></TileMatrix>
283+ </TileMatrixSet>
284+ </Contents>
285+ </Capabilities>` ;
286+
287+ const capabilities = WmtsCapabilities . createFromXml ( xml ) ;
288+ expect ( capabilities ) . toBeDefined ( ) ;
289+
290+ const layer = capabilities ?. contents ?. layers [ 0 ] ;
291+ expect ( layer ) . toBeDefined ( ) ;
292+ expect ( layer ! . identifier ) . toEqual ( "test-layer" ) ;
293+
294+ const epsg3857Link = layer ?. tileMatrixSetLinks . find ( ( link ) => link . tileMatrixSet === "EPSG:3857" ) ;
295+ expect ( epsg3857Link ) . toBeDefined ( ) ;
296+
297+ // BUG: TileMatrixSetLimits are never parsed because inner getElementsByTagName uses
298+ // "TileMatrixSetLimits" (the container tag) instead of "TileMatrixLimits" (the child tag).
299+ // This test SHOULD pass once the parsing bug is fixed.
300+ expect ( epsg3857Link ! . tileMatrixSetLimits . length ) . toEqual ( 2 ) ;
301+
302+ // Verify the first limit entry (level 0)
303+ const firstLimit = epsg3857Link ! . tileMatrixSetLimits [ 0 ] ;
304+ expect ( firstLimit . tileMatrix ) . toEqual ( "EPSG:3857:0" ) ;
305+ expect ( firstLimit . limits ) . toBeDefined ( ) ;
306+ expect ( firstLimit . limits ! . low . x ) . toEqual ( 0 ) ; // MinTileCol
307+ expect ( firstLimit . limits ! . low . y ) . toEqual ( 0 ) ; // MinTileRow
308+ expect ( firstLimit . limits ! . high . x ) . toEqual ( 0 ) ; // MaxTileCol
309+ expect ( firstLimit . limits ! . high . y ) . toEqual ( 0 ) ; // MaxTileRow
310+
311+ // Verify level 5 limit entry
312+ const level5Limit = epsg3857Link ! . tileMatrixSetLimits [ 1 ] ;
313+ expect ( level5Limit . tileMatrix ) . toEqual ( "EPSG:3857:5" ) ;
314+ expect ( level5Limit . limits ) . toBeDefined ( ) ;
315+ expect ( level5Limit . limits ! . low . x ) . toEqual ( 27 ) ; // MinTileCol
316+ expect ( level5Limit . limits ! . low . y ) . toEqual ( 16 ) ; // MinTileRow
317+ expect ( level5Limit . limits ! . high . x ) . toEqual ( 30 ) ; // MaxTileCol
318+ expect ( level5Limit . limits ! . high . y ) . toEqual ( 19 ) ; // MaxTileRow
319+ } ) ;
320+
321+ it ( "should parse Propeller Aero NDOT capabilities" , ( ) => {
322+ // Minimal XML representative of Propeller Aero WMTS: localized dataset,
323+ // no TileMatrixSetLimits, resource URL template, small WGS84 bounding box.
324+ const xml = `<?xml version="1.0" encoding="UTF-8"?>
325+ <Capabilities xmlns="http://www.opengis.net/wmts/1.0" xmlns:ows="http://www.opengis.net/ows/1.1" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.0.0">
326+ <ows:ServiceIdentification>
327+ <ows:Title>PropellerAero WMTS Service</ows:Title>
328+ <ows:ServiceType>OGC WMTS</ows:ServiceType>
329+ <ows:ServiceTypeVersion>1.0.0</ows:ServiceTypeVersion>
330+ </ows:ServiceIdentification>
331+ <Contents>
332+ <Layer>
333+ <ows:Title>Oct 30, 2025</ows:Title>
334+ <ows:Identifier>dsce0d0b33</ows:Identifier>
335+ <ows:WGS84BoundingBox crs="urn:ogc:def:crs:OGC:2:84">
336+ <ows:LowerCorner>-96.18430645001274 40.81005980948186</ows:LowerCorner>
337+ <ows:UpperCorner>-96.17298696901085 40.81541280531352</ows:UpperCorner>
338+ </ows:WGS84BoundingBox>
339+ <Style isDefault="true">
340+ <ows:Title>default</ows:Title>
341+ <ows:Identifier>default</ows:Identifier>
342+ </Style>
343+ <Format>image/png</Format>
344+ <TileMatrixSetLink>
345+ <TileMatrixSet>PropellerAeroTileMatrix</TileMatrixSet>
346+ </TileMatrixSetLink>
347+ <ResourceURL format="image/png" resourceType="tile" template="https://cdn.example.com/tiles/{TileMatrixSet}/{TileMatrix}/{TileCol}/{TileRow}.png"/>
348+ </Layer>
349+ <TileMatrixSet>
350+ <ows:Title>PropellerAeroTileMatrix</ows:Title>
351+ <ows:Identifier>PropellerAeroTileMatrix</ows:Identifier>
352+ <ows:SupportedCRS>urn:ogc:def:crs:EPSG::3857</ows:SupportedCRS>
353+ <WellKnownScaleSet>urn:ogc:def:wkss:OGC:1.0:GoogleMapsCompatible</WellKnownScaleSet>
354+ <TileMatrix><ows:Identifier>0</ows:Identifier><ScaleDenominator>559082264.0287178</ScaleDenominator><TopLeftCorner>-20037508.3427892 20037508.3427892</TopLeftCorner><TileWidth>256</TileWidth><TileHeight>256</TileHeight><MatrixWidth>1</MatrixWidth><MatrixHeight>1</MatrixHeight></TileMatrix>
355+ <TileMatrix><ows:Identifier>1</ows:Identifier><ScaleDenominator>279541132.0143589</ScaleDenominator><TopLeftCorner>-20037508.3427892 20037508.3427892</TopLeftCorner><TileWidth>256</TileWidth><TileHeight>256</TileHeight><MatrixWidth>2</MatrixWidth><MatrixHeight>2</MatrixHeight></TileMatrix>
356+ <TileMatrix><ows:Identifier>20</ows:Identifier><ScaleDenominator>533.182395962446</ScaleDenominator><TopLeftCorner>-20037508.3427892 20037508.3427892</TopLeftCorner><TileWidth>256</TileWidth><TileHeight>256</TileHeight><MatrixWidth>1048576</MatrixWidth><MatrixHeight>1048576</MatrixHeight></TileMatrix>
357+ </TileMatrixSet>
358+ </Contents>
359+ </Capabilities>` ;
360+
361+ const capabilities = WmtsCapabilities . createFromXml ( xml ) ;
362+ expect ( capabilities ) . toBeDefined ( ) ;
363+ expect ( capabilities ?. version ) . toEqual ( "1.0.0" ) ;
364+
365+ // Service identification
366+ expect ( capabilities ?. serviceIdentification ?. title ) . toEqual ( "PropellerAero WMTS Service" ) ;
367+ expect ( capabilities ?. serviceIdentification ?. serviceType ) . toEqual ( "OGC WMTS" ) ;
368+
369+ // Layer
370+ expect ( capabilities ?. contents ?. layers . length ) . toEqual ( 1 ) ;
371+ const layer = capabilities ! . contents ! . layers [ 0 ] ;
372+ expect ( layer . identifier ) . toEqual ( "dsce0d0b33" ) ;
373+
374+ // The layer covers a small area near Lincoln, NE
375+ expect ( layer . wsg84BoundingBox ) . toBeDefined ( ) ;
376+ expect ( layer . wsg84BoundingBox ! . west ) . toBeDefined ( ) ;
377+ const area = layer . wsg84BoundingBox ! . globalLocationArea ;
378+ // Longitude ~ -96.18 to -96.17 (small site in Nebraska)
379+ expect ( area . southwest . longitudeDegrees ) . toBeCloseTo ( - 96.184 , 2 ) ;
380+ expect ( area . northeast . longitudeDegrees ) . toBeCloseTo ( - 96.173 , 2 ) ;
381+ expect ( area . southwest . latitudeDegrees ) . toBeCloseTo ( 40.810 , 2 ) ;
382+ expect ( area . northeast . latitudeDegrees ) . toBeCloseTo ( 40.815 , 2 ) ;
383+
384+ // Tile matrix set
385+ expect ( capabilities ?. contents ?. tileMatrixSets . length ) . toEqual ( 1 ) ;
386+ const tms = capabilities ! . contents ! . tileMatrixSets [ 0 ] ;
387+ expect ( tms . identifier ) . toEqual ( "PropellerAeroTileMatrix" ) ;
388+ expect ( tms . supportedCrs ) . toContain ( "EPSG::3857" ) ;
389+ expect ( tms . wellKnownScaleSet ) . toContain ( "GoogleMapsCompatible" ) ;
390+
391+ // Verify it's recognized as Google Maps compatible
392+ const googleTms = capabilities ?. contents ?. getGoogleMapsCompatibleTileMatrixSet ( ) ;
393+ expect ( googleTms ?. length ) . toEqual ( 1 ) ;
394+ expect ( googleTms ! [ 0 ] . identifier ) . toEqual ( "PropellerAeroTileMatrix" ) ;
395+
396+ // No TileMatrixSetLimits — this Propeller endpoint doesn't publish them,
397+ // which is part of why sparse tile requests fail.
398+ const tmLink = layer . tileMatrixSetLinks [ 0 ] ;
399+ expect ( tmLink . tileMatrixSet ) . toEqual ( "PropellerAeroTileMatrix" ) ;
400+ expect ( tmLink . tileMatrixSetLimits . length ) . toEqual ( 0 ) ;
401+
402+ // Resource URL should be present
403+ expect ( layer . resourceUrls . length ) . toBeGreaterThan ( 0 ) ;
404+ const pngUrl = layer . resourceUrls . find ( ( u ) => u . format . includes ( "png" ) ) ;
405+ expect ( pngUrl ) . toBeDefined ( ) ;
406+ expect ( pngUrl ! . template ) . toContain ( "{TileMatrix}" ) ;
407+ expect ( pngUrl ! . template ) . toContain ( "{TileCol}" ) ;
408+ expect ( pngUrl ! . template ) . toContain ( "{TileRow}" ) ;
409+ } ) ;
410+
248411
249412
250413 it ( "should request proper URL" , async ( ) => {
0 commit comments