@@ -677,17 +677,19 @@ def custom(
677677 ordered_axes : Optional [List [str ]] = None ,
678678 screen_pixel_size : float = 0.28e-3 ,
679679 decimation_base : int = 2 ,
680+ corner_of_origin : Literal ["topLeft" , "bottomLeft" ] = "topLeft" ,
681+ point_of_origin : List [float ] = None ,
680682 ** kwargs : Any ,
681683 ):
682684 """
683685 Construct a custom TileMatrixSet.
684686
685687 Attributes
686688 ----------
687- crs: pyproj.CRS
688- Tile Matrix Set coordinate reference system
689689 extent: list
690690 Bounding box of the Tile Matrix Set, (left, bottom, right, top).
691+ crs: pyproj.CRS
692+ Tile Matrix Set coordinate reference system
691693 tile_width: int
692694 Width of each tile of this tile matrix in pixels (default is 256).
693695 tile_height: int
@@ -713,6 +715,10 @@ def custom(
713715 Rendering pixel size. 0.28 mm was the actual pixel size of a common display from 2005 and considered as standard by OGC.
714716 decimation_base: int, optional
715717 How tiles are divided at each zoom level (default is 2). Must be greater than 1.
718+ corner_of_origin: str, optional
719+ Corner of origin for the TMS, either 'topLeft' or 'bottomLeft'
720+ point_of_origin: list, optional
721+ Point of origin for the TMS, (x, y) coordinates in the TMS CRS.
716722 kwargs: Any
717723 Attributes to forward to the TileMatrixSet
718724
@@ -738,8 +744,20 @@ def custom(
738744 )
739745
740746 bbox = BoundingBox (* extent )
741- x_origin = bbox .left if not is_inverted else bbox .top
742- y_origin = bbox .top if not is_inverted else bbox .left
747+ if not point_of_origin :
748+ if corner_of_origin == "topLeft" :
749+ x_origin = bbox .left if not is_inverted else bbox .top
750+ y_origin = bbox .top if not is_inverted else bbox .left
751+ point_of_origin = [x_origin , y_origin ]
752+ elif corner_of_origin == "bottomLeft" :
753+ x_origin = bbox .left if not is_inverted else bbox .bottom
754+ y_origin = bbox .bottom if not is_inverted else bbox .left
755+ point_of_origin = [x_origin , y_origin ]
756+ else :
757+ raise ValueError (
758+ f"Invalid `corner_of_origin` value: { corner_of_origin } , must be either 'topLeft' or 'bottomLeft'"
759+ )
760+
743761 width = abs (bbox .right - bbox .left )
744762 height = abs (bbox .top - bbox .bottom )
745763 mpu = meters_per_unit (crs )
@@ -758,7 +776,8 @@ def custom(
758776 "id" : str (zoom ),
759777 "scaleDenominator" : res * mpu / screen_pixel_size ,
760778 "cellSize" : res ,
761- "pointOfOrigin" : [x_origin , y_origin ],
779+ "cornerOfOrigin" : corner_of_origin ,
780+ "pointOfOrigin" : point_of_origin ,
762781 "tileWidth" : tile_width ,
763782 "tileHeight" : tile_height ,
764783 "matrixWidth" : matrix_scale [0 ] * decimation_base ** zoom ,
@@ -830,6 +849,7 @@ def matrix(self, zoom: int) -> TileMatrix:
830849 id = str (int (tile_matrix .id ) + 1 ),
831850 scaleDenominator = tile_matrix .scaleDenominator / factor ,
832851 cellSize = tile_matrix .cellSize / factor ,
852+ cornerOfOrigin = tile_matrix .cornerOfOrigin ,
833853 pointOfOrigin = tile_matrix .pointOfOrigin ,
834854 tileWidth = tile_matrix .tileWidth ,
835855 tileHeight = tile_matrix .tileHeight ,
@@ -981,8 +1001,14 @@ def _tile(
9811001 if not math .isinf (xcoord )
9821002 else 0
9831003 )
1004+
1005+ coord = (
1006+ (origin_y - ycoord )
1007+ if matrix .cornerOfOrigin == "topLeft"
1008+ else (ycoord - origin_y )
1009+ )
9841010 ytile = (
985- math .floor (( origin_y - ycoord ) / float (matrix .cellSize * matrix .tileHeight ))
1011+ math .floor (coord / float (matrix .cellSize * matrix .tileHeight ))
9861012 if not math .isinf (ycoord )
9871013 else 0
9881014 )
@@ -1088,10 +1114,16 @@ def _ul(self, *tile: Tile) -> Coords:
10881114 if matrix .variableMatrixWidths is not None
10891115 else 1
10901116 )
1091- return Coords (
1092- origin_x + math .floor (t .x / cf ) * matrix .cellSize * cf * matrix .tileWidth ,
1093- origin_y - t .y * matrix .cellSize * matrix .tileHeight ,
1117+ x_coord = (
1118+ origin_x + math .floor (t .x / cf ) * matrix .cellSize * cf * matrix .tileWidth
10941119 )
1120+ y_coord = (
1121+ origin_y - t .y * matrix .cellSize * matrix .tileHeight
1122+ if matrix .cornerOfOrigin == "topLeft"
1123+ else origin_y + t .y * matrix .cellSize * matrix .tileHeight
1124+ )
1125+
1126+ return Coords (x_coord , y_coord )
10951127
10961128 def _lr (self , * tile : Tile ) -> Coords :
10971129 """
@@ -1116,12 +1148,18 @@ def _lr(self, *tile: Tile) -> Coords:
11161148 if matrix .variableMatrixWidths is not None
11171149 else 1
11181150 )
1119- return Coords (
1151+ x_coord = (
11201152 origin_x
1121- + (math .floor (t .x / cf ) + 1 ) * matrix .cellSize * cf * matrix .tileWidth ,
1122- origin_y - (t .y + 1 ) * matrix .cellSize * matrix .tileHeight ,
1153+ + (math .floor (t .x / cf ) + 1 ) * matrix .cellSize * cf * matrix .tileWidth
1154+ )
1155+ y_coord = (
1156+ origin_y - (t .y + 1 ) * matrix .cellSize * matrix .tileHeight
1157+ if matrix .cornerOfOrigin == "topLeft"
1158+ else origin_y + (t .y + 1 ) * matrix .cellSize * matrix .tileHeight
11231159 )
11241160
1161+ return Coords (x_coord , y_coord )
1162+
11251163 def xy_bounds (self , * tile : Tile ) -> BoundingBox :
11261164 """
11271165 Return the bounding box of the tile in TMS coordinate reference system.
@@ -1147,12 +1185,16 @@ def xy_bounds(self, *tile: Tile) -> BoundingBox:
11471185 )
11481186
11491187 left = origin_x + math .floor (t .x / cf ) * matrix .cellSize * cf * matrix .tileWidth
1150- top = origin_y - t .y * matrix .cellSize * matrix .tileHeight
11511188 right = (
11521189 origin_x
11531190 + (math .floor (t .x / cf ) + 1 ) * matrix .cellSize * cf * matrix .tileWidth
11541191 )
1155- bottom = origin_y - (t .y + 1 ) * matrix .cellSize * matrix .tileHeight
1192+ if matrix .cornerOfOrigin == "topLeft" :
1193+ top = origin_y - t .y * matrix .cellSize * matrix .tileHeight
1194+ bottom = origin_y - (t .y + 1 ) * matrix .cellSize * matrix .tileHeight
1195+ else :
1196+ bottom = origin_y + t .y * matrix .cellSize * matrix .tileHeight
1197+ top = origin_y + (t .y + 1 ) * matrix .cellSize * matrix .tileHeight
11561198
11571199 return BoundingBox (left , bottom , right , top )
11581200
0 commit comments