Skip to content

Conversation

@thisisaaronland
Copy link

  • UpdateleafletRasterLayer to dispatch empty image if source.getZxy returns an empty array (no raster data).
  • Run npm audit fix

This change means that the done callback is always invoked, which is dispatched by the Leaflet.GridLayer _tileReady thus ensuring that the layer's "load" event will be fired correctly.

…turns an empty array (no raster data); run npm audit fix
@bdon
Copy link
Member

bdon commented Dec 18, 2025

How can I reproduce the original issue?

@thisisaaronland
Copy link
Author

Example web page/app demonstrating the problem included below. The pmtiles_layer.on("load") event never fires.

<html>
    <head>
	<title>Test</title>
	<link rel="stylesheet" href="https://unpkg.com/[email protected]/dist/leaflet.css" integrity="sha256-p4NxAoJBhIIN+hmNHrzRCf9tD/miZyoHS5obTRR9BMY=" crossorigin="" />
	<style type="text/css">
	 #map { width: 100%; height: 90vh; border: solid thin; }
	</style>
    </head>    
    <body>
	<div id="map">
	</div>
    </body>
    <script src="https://unpkg.com/[email protected]/dist/leaflet.js"></script>
    <script src="https://unpkg.com/[email protected]/dist/pmtiles.js"></script>
    <script type="text/javascript">

     const map = L.map("map");
     map.setView([37.621131, -122.384292], 15);

     const osm_layer = L.tileLayer('https://tile.openstreetmap.org/{z}/{x}/{y}.png', {
	maxZoom: 19,
	attribution: '&copy; <a href="http://www.openstreetmap.org/copyright">OpenStreetMap</a>'
     });

     osm_layer.addTo(map);

     osm_layer.on("load", function(){
	 console.log("OSM layer loaded");
     });
     
     const pmtiles_url = "https://static.sfomuseum.org/aerial/193X.pmtiles";
     
     const p = new pmtiles.PMTiles(pmtiles_url);
     
     const pmtiles_layer =  pmtiles.leafletRasterLayer(p);

     pmtiles_layer.on("load", function(e){
	 console.log("PMTiles layer loaded");
     });
     
     pmtiles_layer.addTo(map);
     
    </script>
</html>

@bdon
Copy link
Member

bdon commented Dec 21, 2025

https://static.sfomuseum.org/aerial/193X.pmtiles

This archive is gzipped PNGs that has a header indicating PNG data, can you please generate a correct archive with TileCompression=None and TileType=PNG so we can eliminate other issues with loading the example?

@thisisaaronland
Copy link
Author

Editing the header in the PMTiles generates a file that returns broken images in the browser. I tried building a PMTiles database from scratch, converting it from an MBTiles database of raster images (produced by the tilezen/go-tilepacks build tool.

This was done using a freshly compiled instance of the pmtiles tool taken from the most recent commit (9095aaa9ad2d1111153816124f591cad9cf00c25).

Step 1: Convert MBTiles to PMTiles

$> pmtiles convert /usr/local/sfomuseum/tiles/sqlite/193X.db 193X.pmtiles
2025/12/22 15:10:08 convert.go:158: Pass 1: Assembling TileID set
2025/12/22 15:10:08 convert.go:189: Pass 2: writing tiles
 100% |████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████| (153/153, 1317 it/s)        
2025/12/22 15:10:08 convert.go:243: # of addressed tiles:  153
2025/12/22 15:10:08 convert.go:244: # of tile entries (after RLE):  147
2025/12/22 15:10:08 convert.go:245: # of tile contents:  132
2025/12/22 15:10:08 convert.go:268: Total dir bytes:  558
2025/12/22 15:10:08 convert.go:269: Average bytes per addressed tile: 3.65
2025/12/22 15:10:08 convert.go:238: Finished in  119.425959ms

Step 2: Show new PMTiles data

$> pmtiles show 193X.pmtiles 
pmtiles spec version: 3
tile type: mvt
bounds: (long: -122.455382, lat: 37.567660) (long: -122.332747, lat: 37.673950)
min zoom: 12
max zoom: 16
center: (long: -122.394064, lat: 37.620805)
center zoom: 12
addressed tiles count: 153
tile entries count: 147
tile contents count: 132
clustered: true
internal compression: gzip
tile compression: gzip
format pbf
maxzoom 16
minzoom 12
name tileset

Step 3: Show new PMTiles header data

> pmtiles show --header-json 193X.pmtiles 
{
    "tile_compression": "gzip",
    "tile_type": "mvt",
    "minzoom": 12,
    "maxzoom": 16,
    "bounds": [
        -122.455382,
        37.5676599,
        -122.332747,
        37.67395
    ],
    "center": [
        -122.394064,
        37.620805,
        12
    ]
}

Step 4: Edit header to set "tile_compression" = "none" and "tile_type" = "png":

$> pmtiles edit --header-json=header.json 193X.pmtiles

Step 5: Show updated PMTiles data

$> pmtiles show 193X.pmtiles 
pmtiles spec version: 3
tile type: png
bounds: (long: -122.455382, lat: 37.567660) (long: -122.332747, lat: 37.673950)
min zoom: 12
max zoom: 16
center: (long: -122.394064, lat: 37.620805)
center zoom: 12
addressed tiles count: 153
tile entries count: 147
tile contents count: 132
clustered: true
internal compression: gzip
tile compression: none
name tileset
format pbf
maxzoom 16
minzoom 12

When I try to load the newly edit PMTiles database in the application (above) this is what I see:

Screenshot 2025-12-22 at 15 09 23

@bdon
Copy link
Member

bdon commented Dec 23, 2025

No tool should ever write PNGs with gzip compression applied as it is a waste of CPU cycles. Can you provide 193X.db?

@thisisaaronland
Copy link
Author

SQLite database is here:

https://static.sfomuseum.org/aerial/193X.db

The images should not be compressed as this was addressed in the go-tilepacks code a while back:

tilezen/go-tilepacks#19

The SQLite databases we generate are created with a call like this:

$> bin/build \
	-inverted-y \
	-bounds '37.5986945643,-122.410080799,37.6421167813,-122.346055861' \
	-zooms '12-20' \
	-url-template 'file:///{z}/{x}/{y}.png' \
	-file-transport-root ./2017 \
	-ensure-gzip=false	
	-dsn 2017.db 

Note: This is not 193X. It is the example from our internal docs but it's the template we use for all SQLite databases derived from gdal2tiles output.

@bdon
Copy link
Member

bdon commented Dec 24, 2025

https://static.sfomuseum.org/aerial/193X.db

This is a SQLite database of PNG images, but the metadata table's format row is set to pbf.

Instead of doing gdal2tiles + pmtiles-convert why not use rio pmtiles directly?

@thisisaaronland
Copy link
Author

Because the current setup produces PMTiles databases that work fine, save for not triggering the "load" event, and rio pmtiles doesn't work for contemporary imagery which is often 20-25GB uncompressed.

bdon added a commit that referenced this pull request Dec 24, 2025
@bdon bdon mentioned this pull request Dec 24, 2025
bdon added a commit that referenced this pull request Dec 24, 2025
* Update js/src/adapters.ts to dispatch empty image if source.getZxy returns an empty array (no raster data); run npm audit fix

* js: simplify leaflet missing tile case [#620]

---------

Co-authored-by: thisisaaronland <thisisaaronland@localhost>
@bdon
Copy link
Member

bdon commented Dec 24, 2025

Merged in #624

The default for an Img element seems to be fine without setting display: none

@bdon bdon closed this Dec 24, 2025
@bdon
Copy link
Member

bdon commented Dec 24, 2025

4.3.1 released on https://www.npmjs.com/package/pmtiles

@thisisaaronland
Copy link
Author

The display:none style is necessary in browsers like Firefox which will draw an empty "container" rectangle for images with no src attribute. For example:

Screenshot 2025-12-24 at 10 32 44

The same tile but with a display:none style:

Screenshot 2025-12-24 at 10 33 40

@bdon
Copy link
Member

bdon commented Dec 26, 2025

Published 4.3.2 with the fix

@thisisaaronland
Copy link
Author

Confirmed that 4.3.2 works as expected. Thanks!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants