1
1
import math
2
2
import logging
3
3
from enum import Enum
4
- from typing import Optional , List , Tuple
4
+ from typing import Optional , List , Tuple , Any , Union
5
5
from concurrent .futures import ThreadPoolExecutor
6
6
from io import BytesIO
7
7
15
15
from pydantic import BaseModel , validator
16
16
from pydantic .class_validators import root_validator
17
17
18
+ from labelbox .data .annotation_types .geometry .polygon import Polygon
19
+ from labelbox .data .annotation_types .geometry .point import Point
20
+ from labelbox .data .annotation_types .geometry .line import Line
21
+ from labelbox .data .annotation_types .geometry .rectangle import Rectangle
22
+
18
23
from ..geometry import Point
19
24
from .base_data import BaseData
20
25
from .raster import RasterData
@@ -326,56 +331,33 @@ class EPSGTransformer(BaseModel):
326
331
Requires as input a Point object.
327
332
"""
328
333
329
- class ProjectionTransformer (Transformer ):
330
- """Custom class to help represent a Transformer that will play
331
- nicely with Pydantic.
332
-
333
- Accepts a PyProj Transformer object.
334
- """
335
-
336
- @classmethod
337
- def __get_validators__ (cls ):
338
- yield cls .validate
339
-
340
- @classmethod
341
- def validate (cls , v ):
342
- if not isinstance (v , Transformer ):
343
- raise Exception ("Needs to be a Transformer class" )
344
- return v
334
+ class Config :
335
+ arbitrary_types_allowed = True
345
336
346
- transform_function : Optional [ ProjectionTransformer ] = None
337
+ transformer : Any
347
338
348
- def _is_simple (self , epsg : EPSG ) -> bool :
339
+ @staticmethod
340
+ def _is_simple (epsg : EPSG ) -> bool :
349
341
return epsg == EPSG .SIMPLEPIXEL
350
342
351
- def _get_ranges (self , bounds : np .ndarray ):
343
+ @staticmethod
344
+ def _get_ranges (bounds : np .ndarray ):
352
345
"""helper function to get the range between bounds.
353
346
354
347
returns a tuple (x_range, y_range)"""
355
348
x_range = np .max (bounds [:, 0 ]) - np .min (bounds [:, 0 ])
356
349
y_range = np .max (bounds [:, 1 ]) - np .min (bounds [:, 1 ])
357
350
return (x_range , y_range )
358
351
359
- def _min_max_x_y (self , bounds : np .ndarray ):
352
+ @staticmethod
353
+ def _min_max_x_y (bounds : np .ndarray ):
360
354
"""returns the min x, max x, min y, max y of a numpy array
361
355
"""
362
356
return np .min (bounds [:, 0 ]), np .max (bounds [:, 0 ]), np .min (
363
357
bounds [:, 1 ]), np .max (bounds [:, 1 ])
364
358
365
- def geo_and_geo (self , src_epsg : EPSG , tgt_epsg : EPSG ) -> None :
366
- """method to change from one projection to another projection.
367
-
368
- supports EPSG transformations not Simple.
369
- """
370
- if self ._is_simple (src_epsg ) or self ._is_simple (tgt_epsg ):
371
- raise Exception (
372
- f"Cannot be used for Simple transformations. Found { src_epsg } and { tgt_epsg } "
373
- )
374
- self .transform_function = Transformer .from_crs (src_epsg .value ,
375
- tgt_epsg .value ,
376
- always_xy = True ).transform
377
-
378
- def geo_and_pixel (self ,
359
+ @classmethod
360
+ def geo_and_pixel (cls ,
379
361
src_epsg ,
380
362
pixel_bounds : TiledBounds ,
381
363
geo_bounds : TiledBounds ,
@@ -386,11 +368,8 @@ def geo_and_pixel(self,
386
368
geo_bounds_epsg = geo_bounds .epsg
387
369
geo_bounds = geo_bounds .bounds
388
370
389
- #TODO: think about renaming local/global?
390
- #local = pixel
391
- #global = geo
392
371
local_bounds = np .array ([(point .x , point .y ) for point in pixel_bounds ],
393
- dtype = np . int )
372
+ dtype = int )
394
373
#convert geo bounds to pixel bounds. assumes geo bounds are in wgs84/EPS4326 per leaflet
395
374
global_bounds = np .array ([
396
375
PygeoPoint .from_latitude_longitude (latitude = point .y ,
@@ -399,16 +378,16 @@ def geo_and_pixel(self,
399
378
])
400
379
401
380
#get the range of pixels for both sets of bounds to use as a multiplification factor
402
- local_x_range , local_y_range = self ._get_ranges (local_bounds )
403
- global_x_range , global_y_range = self ._get_ranges (global_bounds )
381
+ local_x_range , local_y_range = cls ._get_ranges (bounds = local_bounds )
382
+ global_x_range , global_y_range = cls ._get_ranges (bounds = global_bounds )
404
383
405
384
if src_epsg == EPSG .SIMPLEPIXEL :
406
385
407
386
def transform (x : int , y : int ):
408
387
scaled_xy = (x * (global_x_range ) / (local_x_range ),
409
388
y * (global_y_range ) / (local_y_range ))
410
389
411
- minx , _ , miny , _ = self ._min_max_x_y (global_bounds )
390
+ minx , _ , miny , _ = cls ._min_max_x_y (bounds = global_bounds )
412
391
x , y = map (lambda i , j : i + j , scaled_xy , (minx , miny ))
413
392
414
393
point = PygeoPoint .from_pixel (pixel_x = x , pixel_y = y ,
@@ -419,23 +398,22 @@ def transform(x: int, y: int):
419
398
always_xy = True ).transform (
420
399
point [1 ], point [0 ])
421
400
422
- self . transform_function = transform
401
+ return transform
423
402
424
- #geo to pixel - converts a point in geo coords to pixel coords
425
403
#handles 4326 from lat,lng
426
404
elif src_epsg == EPSG .EPSG4326 :
427
405
428
406
def transform (x : int , y : int ):
429
407
point_in_px = PygeoPoint .from_latitude_longitude (
430
408
latitude = y , longitude = x ).pixels (zoom )
431
409
432
- minx , _ , miny , _ = self ._min_max_x_y (global_bounds )
410
+ minx , _ , miny , _ = cls ._min_max_x_y (global_bounds )
433
411
x , y = map (lambda i , j : i - j , point_in_px , (minx , miny ))
434
412
435
413
return (x * (local_x_range ) / (global_x_range ),
436
414
y * (local_y_range ) / (global_y_range ))
437
415
438
- self . transform_function = transform
416
+ return transform
439
417
440
418
#handles 3857 from meters
441
419
elif src_epsg == EPSG .EPSG3857 :
@@ -444,17 +422,67 @@ def transform(x: int, y: int):
444
422
point_in_px = PygeoPoint .from_meters (meter_y = y ,
445
423
meter_x = x ).pixels (zoom )
446
424
447
- minx , _ , miny , _ = self ._min_max_x_y (global_bounds )
425
+ minx , _ , miny , _ = cls ._min_max_x_y (global_bounds )
448
426
x , y = map (lambda i , j : i - j , point_in_px , (minx , miny ))
449
427
450
428
return (x * (local_x_range ) / (global_x_range ),
451
429
y * (local_y_range ) / (global_y_range ))
452
430
453
- self .transform_function = transform
431
+ return transform
432
+
433
+ @classmethod
434
+ def create_geo_to_geo_transformer (cls , src_epsg : EPSG ,
435
+ tgt_epsg : EPSG ) -> None :
436
+ """method to change from one projection to another projection.
437
+
438
+ supports EPSG transformations not Simple.
439
+ """
440
+ if cls ._is_simple (epsg = src_epsg ) or cls ._is_simple (epsg = tgt_epsg ):
441
+ raise Exception (
442
+ f"Cannot be used for Simple transformations. Found { src_epsg } and { tgt_epsg } "
443
+ )
454
444
455
- def __call__ (self , point : Point ):
456
- if self .transform_function is not None :
457
- res = self .transform_function (point .x , point .y )
458
- return Point (x = res [0 ], y = res [1 ])
445
+ return EPSGTransformer (transformer = Transformer .from_crs (
446
+ src_epsg .value , tgt_epsg .value , always_xy = True ).transform )
447
+
448
+ @classmethod
449
+ def create_geo_to_pixel_transformer (cls ,
450
+ src_epsg ,
451
+ pixel_bounds : TiledBounds ,
452
+ geo_bounds : TiledBounds ,
453
+ zoom = 0 ):
454
+ """method to change from a geo projection to Simple"""
455
+
456
+ transform_function = cls .geo_and_pixel (src_epsg = src_epsg ,
457
+ pixel_bounds = pixel_bounds ,
458
+ geo_bounds = geo_bounds ,
459
+ zoom = zoom )
460
+ return EPSGTransformer (transformer = transform_function )
461
+
462
+ @classmethod
463
+ def create_pixel_to_geo_transformer (cls ,
464
+ src_epsg ,
465
+ pixel_bounds : TiledBounds ,
466
+ geo_bounds : TiledBounds ,
467
+ zoom = 0 ):
468
+ """method to change from a geo projection to Simple"""
469
+ transform_function = cls .geo_and_pixel (src_epsg = src_epsg ,
470
+ pixel_bounds = pixel_bounds ,
471
+ geo_bounds = geo_bounds ,
472
+ zoom = zoom )
473
+ return EPSGTransformer (transformer = transform_function )
474
+
475
+ def _get_point_obj (self , point ) -> Point :
476
+ point = self .transformer (point .x , point .y )
477
+ return Point (x = point [0 ], y = point [1 ])
478
+
479
+ def __call__ (self , shape : Union [Point , Line , Rectangle , Polygon ]):
480
+ if isinstance (shape , Point ):
481
+ return self ._get_point_obj (shape )
482
+ if isinstance (shape , Line ) or isinstance (shape , Polygon ):
483
+ return Line (points = [self ._get_point_obj (p ) for p in shape .points ])
484
+ if isinstance (shape , Rectangle ):
485
+ return Rectangle (start = self ._get_point_obj (shape .start ),
486
+ end = self ._get_point_obj (shape .end ))
459
487
else :
460
- raise Exception ( "No transformation has been set. " )
488
+ raise ValueError ( f"Unsupported type found: { type ( shape ) } " )
0 commit comments