@@ -485,6 +485,7 @@ async def get_tile_from_pmtiles_only(
485485 z : int ,
486486 x : int ,
487487 y : int ,
488+ label : bool = False ,
488489 ) -> Optional [tuple [bytes , bool , str ]]:
489490 """Get tile directly from PMTiles without metadata lookup.
490491
@@ -495,14 +496,15 @@ async def get_tile_from_pmtiles_only(
495496 z: Zoom level
496497 x: Tile X coordinate
497498 y: Tile Y coordinate
499+ label: If True, serve anchor PMTiles (polygon label points) instead of main tiles
498500
499501 Returns:
500502 Tuple of (tile_data, is_gzip, source) or None if PMTiles not available
501503 """
502504 if not self ._pmtiles_exists (layer_info ):
503505 return None
504506
505- result = await self ._get_tile_from_pmtiles (layer_info , z , x , y )
507+ result = await self ._get_tile_from_pmtiles (layer_info , z , x , y , label = label )
506508 if result is None :
507509 return None
508510
@@ -540,6 +542,7 @@ async def get_tile_from_pmtiles_by_layer_id(
540542 z : int ,
541543 x : int ,
542544 y : int ,
545+ label : bool = False ,
543546 ) -> Optional [tuple [bytes , bool , str ]]:
544547 """Get tile directly from PMTiles using only layer_id (no schema lookup).
545548
@@ -551,20 +554,24 @@ async def get_tile_from_pmtiles_by_layer_id(
551554 z: Zoom level
552555 x: Tile X coordinate
553556 y: Tile Y coordinate
557+ label: If True, serve anchor PMTiles (polygon label points) instead of main tiles
554558
555559 Returns:
556560 Tuple of (tile_data, is_gzip, source) or None if PMTiles not available
557561 """
558562 start_time = time .monotonic ()
559563
564+ # Anchor tiles are cached under a separate key to avoid collision
565+ cache_layer_id = layer_id + "_anchor" if label else layer_id
566+
560567 # Check Redis cache first (fast path for distributed deployments)
561- cached = get_cached_tile (layer_id , z , x , y )
568+ cached = get_cached_tile (cache_layer_id , z , x , y )
562569 if cached is not None :
563570 tile_data , is_gzip = cached
564571 elapsed_ms = (time .monotonic () - start_time ) * 1000
565572 logger .debug (
566573 "Redis cache hit for %s tile %d/%d/%d: %d bytes (%.1fms)" ,
567- layer_id [:8 ],
574+ cache_layer_id [:8 ],
568575 z ,
569576 x ,
570577 y ,
@@ -595,14 +602,15 @@ async def get_tile_from_pmtiles_by_layer_id(
595602
596603 tile_data , is_gzip = result
597604
598- # Step 3: For polygon layers, also read from anchor PMTiles and concatenate
599- tile_data , is_gzip = await self ._concat_anchor_tile (
600- pmtiles_path , z , x , y , tile_data , is_gzip
601- )
605+ # Step 3: When label=True, merge anchor tile (polygon label points)
606+ if label :
607+ tile_data , is_gzip = await self ._merge_anchor_tile (
608+ pmtiles_path , z , x , y , tile_data , is_gzip
609+ )
602610
603611 # Cache in Redis for other pods
604612 if tile_data :
605- cache_tile (layer_id , z , x , y , tile_data , is_gzip )
613+ cache_tile (cache_layer_id , z , x , y , tile_data , is_gzip )
606614
607615 logger .info (
608616 "PMTiles %s tile %d/%d/%d: %d bytes (%.1fms)" ,
@@ -769,7 +777,7 @@ def _read_tile() -> (
769777 return result
770778
771779 async def _get_tile_from_pmtiles (
772- self , layer_info : LayerInfo , z : int , x : int , y : int
780+ self , layer_info : LayerInfo , z : int , x : int , y : int , label : bool = False
773781 ) -> Optional [tuple [bytes , bool ]]:
774782 """Get tile data from PMTiles file using pmtiles library.
775783
@@ -784,6 +792,7 @@ async def _get_tile_from_pmtiles(
784792 z: Zoom level
785793 x: Tile X coordinate
786794 y: Tile Y coordinate
795+ label: If True, serve anchor PMTiles (polygon label points) instead of main tiles
787796
788797 Returns:
789798 Tuple of (tile_data, is_gzip_compressed) or None if tile not found
@@ -907,14 +916,17 @@ def _read_tile() -> (
907916 else :
908917 result = b"" , False
909918
910- # Concatenate anchor tile for polygon layers (separate anchor PMTiles)
911919 tile_data , is_gzip = result
912- tile_data , is_gzip = await self ._concat_anchor_tile (
913- pmtiles_path , z , x , y , tile_data , is_gzip
914- )
920+
921+ # Merge anchor tile when labels are requested
922+ if label :
923+ tile_data , is_gzip = await self ._merge_anchor_tile (
924+ pmtiles_path , z , x , y , tile_data , is_gzip
925+ )
926+
915927 return tile_data , is_gzip
916928
917- async def _concat_anchor_tile (
929+ async def _merge_anchor_tile (
918930 self ,
919931 pmtiles_path : Path ,
920932 z : int ,
@@ -923,11 +935,10 @@ async def _concat_anchor_tile(
923935 tile_data : bytes ,
924936 is_gzip : bool ,
925937 ) -> tuple [bytes , bool ]:
926- """Concatenate anchor tile data for polygon layers .
938+ """Merge anchor tile data into the main tile for polygon label support .
927939
928- Checks if a separate anchor PMTiles file exists (containing label
929- anchor points from --convert-polygons-to-label-points). If so, reads
930- the anchor tile and concatenates the raw MVT bytes with the main tile.
940+ Reads the anchor PMTiles file (label points from --convert-polygons-to-label-points)
941+ and concatenates the raw MVT bytes with the main tile. Only called when label=True.
931942
932943 Args:
933944 pmtiles_path: Path to the main PMTiles file
@@ -936,28 +947,25 @@ async def _concat_anchor_tile(
936947 is_gzip: Whether tile_data is gzip compressed
937948
938949 Returns:
939- Tuple of (concatenated_tile_data , is_gzip)
950+ Tuple of (merged_tile_data , is_gzip)
940951 """
941952 if not tile_data :
942953 return tile_data , is_gzip
943954
944- # Derive anchor path: t_{layer_id}.pmtiles -> t_{layer_id}_anchor.pmtiles
945955 anchor_path = pmtiles_path .with_name (pmtiles_path .stem + "_anchor.pmtiles" )
946956 if not anchor_path .exists ():
947957 return tile_data , is_gzip
948958
949- # Read anchor tile (has its own variable-depth pyramid + overzoom)
950959 anchor_result = await self ._get_tile_from_pmtiles_path (anchor_path , z , x , y )
951960 if not anchor_result or not anchor_result [0 ]:
952961 return tile_data , is_gzip
953962
954963 anchor_data , anchor_is_gzip = anchor_result
955964
956- # Decompress both if needed — MVT concatenation requires raw protobuf
965+ # MVT concatenation requires raw protobuf bytes
957966 main_bytes = gzip .decompress (tile_data ) if is_gzip else tile_data
958967 anchor_bytes = gzip .decompress (anchor_data ) if anchor_is_gzip else anchor_data
959968
960- # MVT is protobuf — multiple layers can be concatenated at the byte level
961969 return main_bytes + anchor_bytes , False
962970
963971 async def _overzoom_tile (
0 commit comments