1+ from __future__ import annotations
2+
3+ from enum import Enum
4+ from typing import Any , Dict , Sequence , Tuple , Union
5+
6+
7+ class Orientation (Enum ):
8+ SCREEN = (1 , - 1 ) # Origin in top left, y increases in the down direction
9+ CARTESIAN = (1 , 1 ) # Origin in bottom left, y increases in upward direction
10+
11+
12+ def convert_coordinate (old_t , old_t_max , new_t_max , t_orientation ):
13+ """Convert a coordinate into another system along an axis using a linear transformation"""
14+ return (
15+ (1 - old_t / old_t_max ) * (1 - t_orientation ) / 2
16+ + old_t / old_t_max * (1 + t_orientation ) / 2
17+ ) * new_t_max
18+
19+
20+ class CoordinateSystem :
21+ """A finite coordinate plane with given width and height."""
22+
23+ orientation : Orientation
24+
25+ def __init__ (self , width : Union [int , float ], height : Union [int , float ]):
26+ self .width = width
27+ self .height = height
28+
29+ def __eq__ (self , other : object ):
30+ if not isinstance (other , CoordinateSystem ):
31+ return False
32+ return (
33+ str (self .__class__ .__name__ ) == str (other .__class__ .__name__ )
34+ and self .width == other .width
35+ and self .height == other .height
36+ and self .orientation == other .orientation
37+ )
38+
39+ def convert_from_relative (
40+ self ,
41+ x : Union [float , int ],
42+ y : Union [float , int ],
43+ ) -> Tuple [Union [float , int ], Union [float , int ]]:
44+ """Convert to this coordinate system from a relative coordinate system."""
45+ x_orientation , y_orientation = self .orientation .value
46+ new_x = convert_coordinate (x , 1 , self .width , x_orientation )
47+ new_y = convert_coordinate (y , 1 , self .height , y_orientation )
48+ return new_x , new_y
49+
50+ def convert_to_relative (
51+ self ,
52+ x : Union [float , int ],
53+ y : Union [float , int ],
54+ ) -> Tuple [Union [float , int ], Union [float , int ]]:
55+ """Convert from this coordinate system to a relative coordinate system."""
56+ x_orientation , y_orientation = self .orientation .value
57+ new_x = convert_coordinate (x , self .width , 1 , x_orientation )
58+ new_y = convert_coordinate (y , self .height , 1 , y_orientation )
59+ return new_x , new_y
60+
61+ def convert_coordinates_to_new_system (
62+ self ,
63+ new_system : CoordinateSystem ,
64+ x : Union [float , int ],
65+ y : Union [float , int ],
66+ ) -> Tuple [Union [float , int ], Union [float , int ]]:
67+ """Convert from this coordinate system to another given coordinate system."""
68+ rel_x , rel_y = self .convert_to_relative (x , y )
69+ return new_system .convert_from_relative (rel_x , rel_y )
70+
71+ def convert_multiple_coordinates_to_new_system (
72+ self ,
73+ new_system : CoordinateSystem ,
74+ coordinates : Sequence [Tuple [Union [float , int ], Union [float , int ]]],
75+ ) -> Tuple [Tuple [Union [float , int ], Union [float , int ]], ...]:
76+ """Convert (x, y) coordinates from current system to another coordinate system."""
77+ new_system_coordinates = []
78+ for x , y in coordinates :
79+ new_system_coordinates .append (
80+ self .convert_coordinates_to_new_system (new_system = new_system , x = x , y = y ),
81+ )
82+ return tuple (new_system_coordinates )
83+
84+
85+ class RelativeCoordinateSystem (CoordinateSystem ):
86+ """Relative coordinate system where x and y are on a scale from 0 to 1."""
87+
88+ orientation = Orientation .CARTESIAN
89+
90+ def __init__ (self ):
91+ self .width = 1
92+ self .height = 1
93+
94+
95+ class PixelSpace (CoordinateSystem ):
96+ """Coordinate system representing a pixel space, such as an image. The origin is at the top
97+ left."""
98+
99+ orientation = Orientation .SCREEN
100+
101+
102+ class PointSpace (CoordinateSystem ):
103+ """Coordinate system representing a point space, such as a pdf. The origin is at the bottom
104+ left."""
105+
106+ orientation = Orientation .CARTESIAN
107+
108+
109+ TYPE_TO_COORDINATE_SYSTEM_MAP : Dict [str , Any ] = {
110+ "PixelSpace" : PixelSpace ,
111+ "PointSpace" : PointSpace ,
112+ "CoordinateSystem" : CoordinateSystem ,
113+ }
0 commit comments