def test_show_tru_color_image(self): image = Raster \ .read \ .format("geotiff") \ .options(crs=Crs("epsg:4326")) \ .load( TEST_IMAGE_PATH) image.show(true_color=True)
def test_different_crs_and_extent_crs(self): with self.assertRaises(CrsException): image = Raster \ .read \ .format("geotiff") \ .options(crs=Crs("epsg:4326")) \ .load(TEST_IMAGE_PATH) label = Raster \ .read \ .format("shp") \ .options( extent=image.extent, crs=Crs("epsg:2008"), pixel=image.pixel ) \ .load(self.shape_path)
def test_split_image_to_cnn(self): px = Pixel(1.0, 1.0) epsg_4326 = Crs("epsg:4326") image_array = np.array([[*list(range(1, 8))], [*list(range(2, 9))], [*list(range(3, 10))], [*list(range(4, 11))], [*list(range(5, 12))], [*list(range(10, 17))], [*list(range(20, 27))]]).reshape(7, 7, 1) image = Raster.from_array(image_array, extent=Extent.from_coordinates( [0, 0, 7.0, 7.0], epsg_4326), pixel=px) target_result = [ Raster.from_array(image_array[0:5, 0:5], extent=Extent.from_coordinates([0, 0, 1, 1], crs=epsg_4326), pixel=px), Raster.from_array(image_array[0:5, 1:6], extent=Extent.from_coordinates([0, 0, 1, 1], crs=epsg_4326), pixel=px), Raster.from_array(image_array[0:5, 2:7], extent=Extent.from_coordinates([0, 0, 1, 1], crs=epsg_4326), pixel=px), Raster.from_array(image_array[1:6, 0:5], extent=Extent.from_coordinates([0, 0, 1, 1], crs=epsg_4326), pixel=px), Raster.from_array(image_array[1:6, 1:6], extent=Extent.from_coordinates([0, 0, 1, 1], crs=epsg_4326), pixel=px), Raster.from_array(image_array[1:6, 2:7], extent=Extent.from_coordinates([0, 0, 1, 1], crs=epsg_4326), pixel=px), Raster.from_array(image_array[2:7, 0:5], extent=Extent.from_coordinates([0, 0, 1, 1], crs=epsg_4326), pixel=px), Raster.from_array(image_array[2:7, 1:6], extent=Extent.from_coordinates([0, 0, 1, 1], crs=epsg_4326), pixel=px), Raster.from_array(image_array[2:7, 2:7], extent=Extent.from_coordinates([0, 0, 1, 1], crs=epsg_4326), pixel=px) ] function_result = split_images_to_cnn(image=image, window_size=5) self.assertEqual(target_result, function_result)
def load_from_file(cls, path: str, crs=None): """ class method which based on path returns GdalImage object, It validates path location and its format :return: GdalImage instance """ file = ImageFile(path) ds: gdal.Dataset = gdal.Open(file.path) if crs is None: projection = ds.GetProjection() srs = osr.SpatialReference(wkt=projection) crs_gdal = srs.GetAttrValue('AUTHORITY', 0).lower() + ":" + srs.GetAttrValue('AUTHORITY', 1) crs = Crs(crs_gdal) return cls(ds, path, crs)
def _find_extent_from_multiple_wkt(cls, wkt_value_list, crs=Crs("epsg:4326")): from gis import Extent, Point bottom_corners = [el[0].extent.left_down for el in wkt_value_list] top_corners = [el[0].extent.right_up for el in wkt_value_list] min_x = min(bottom_corners, key=lambda x: x.x).x min_y = min(bottom_corners, key=lambda x: x.y).y max_x = max(top_corners, key=lambda x: x.x).x max_y = max(top_corners, key=lambda x: x.y).y extent = Extent(Point(min_x, min_y), Point(max_x, max_y), crs=crs) return extent
class Polygon(ShapelyPolygon): NAME = "Polygon" coordinates = attr.ib(type=List[List[float]]) crs = attr.ib(default=Crs("epsg:4326")) def __attrs_post_init__(self): super().__init__(self.coordinates) @classmethod def from_wkt(cls, wkt, crs): polygon = wkt_loader.loads(wkt) try: assert polygon.type == cls.NAME except AssertionError: raise TypeError(f"wkt is type {polygon.type}") return cls(polygon.exterior.coords, crs)
def from_file(cls, path, driver="ESRI Shapefile") -> 'GeometryFrame': """ :param path: File location :param driver: Driver for operning the file, currentyl supported drivers are ex. ESRI Shapefile GeoJSON GPKG OpenFileGDB For all supported drivers look at ```python import fiona fiona.supported_drivers ``` :return: GeometryFrame """ geometry = gpd.read_file(path, driver=driver) GeoFrame = cls(geometry, "geom", Crs(geometry.crs["init"])) return GeoFrame
class TestAnnPreparation(TestCase): empty_array_image = np.array([[[1, 2, 3], [5, 4, 7], [9, 3, 5]], [[5, 4, 7], [5, 4, 7], [9, 3, 5]], [[9, 3, 5], [5, 4, 7], [9, 3, 5]]]) empty_array_label = np.array([[3, 20, 4], [11, 5, 10], [20, 5, 4]]).reshape(3, 3, 1) image = r.Raster.from_array(empty_array_image, pixel=rc.Pixel(1.0, 1.0), extent=Extent.from_coordinates( [0, 0, 1, 1], crs=Crs(epsg="epsg:2180"))) label = r.Raster.from_array(empty_array_label, pixel=image.pixel, extent=image.extent) ann_data = AnnDataCreator(label=empty_array_label, image=empty_array_image) def test_label_wide(self): target_res = np.array([[3], [20], [4], [11], [5], [10], [20], [5], [4]]) wide_label = self.ann_data.wide_label self.assertEqual(np.array_equal(wide_label, target_res), True) def test_image_wide(self): target_res = np.array([[1, 2, 3], [5, 4, 7], [9, 3, 5], [5, 4, 7], [5, 4, 7], [9, 3, 5], [9, 3, 5], [5, 4, 7], [9, 3, 5]]) wide_image = self.ann_data.wide_image self.assertEqual(np.array_equal(wide_image, target_res), True) def test_concat_arrays(self): target_res = np.array([[1, 2, 3, 3], [5, 4, 7, 20], [9, 3, 5, 4], [5, 4, 7, 11], [5, 4, 7, 5], [9, 3, 5, 10], [9, 3, 5, 20], [5, 4, 7, 5], [9, 3, 5, 4]]) concat_ = self.ann_data.concat_arrays() self.assertEqual(np.array_equal(concat_, target_res), True) def test_create(self): target_test_x = np.array([[5, 4, 7], [9, 3, 5]]) target_test_y = np.array([[11], [4]]) self.assertEqual( np.array_equal(target_test_x, self.ann_data.create().x_test), True) self.assertEqual( np.array_equal(target_test_y, self.ann_data.create().y_test), True) def test_split_image_to_cnn(self): px = Pixel(1.0, 1.0) epsg_4326 = Crs("epsg:4326") image_array = np.array([[*list(range(1, 8))], [*list(range(2, 9))], [*list(range(3, 10))], [*list(range(4, 11))], [*list(range(5, 12))], [*list(range(10, 17))], [*list(range(20, 27))]]).reshape(7, 7, 1) image = Raster.from_array(image_array, extent=Extent.from_coordinates( [0, 0, 7.0, 7.0], epsg_4326), pixel=px) target_result = [ Raster.from_array(image_array[0:5, 0:5], extent=Extent.from_coordinates([0, 0, 1, 1], crs=epsg_4326), pixel=px), Raster.from_array(image_array[0:5, 1:6], extent=Extent.from_coordinates([0, 0, 1, 1], crs=epsg_4326), pixel=px), Raster.from_array(image_array[0:5, 2:7], extent=Extent.from_coordinates([0, 0, 1, 1], crs=epsg_4326), pixel=px), Raster.from_array(image_array[1:6, 0:5], extent=Extent.from_coordinates([0, 0, 1, 1], crs=epsg_4326), pixel=px), Raster.from_array(image_array[1:6, 1:6], extent=Extent.from_coordinates([0, 0, 1, 1], crs=epsg_4326), pixel=px), Raster.from_array(image_array[1:6, 2:7], extent=Extent.from_coordinates([0, 0, 1, 1], crs=epsg_4326), pixel=px), Raster.from_array(image_array[2:7, 0:5], extent=Extent.from_coordinates([0, 0, 1, 1], crs=epsg_4326), pixel=px), Raster.from_array(image_array[2:7, 1:6], extent=Extent.from_coordinates([0, 0, 1, 1], crs=epsg_4326), pixel=px), Raster.from_array(image_array[2:7, 2:7], extent=Extent.from_coordinates([0, 0, 1, 1], crs=epsg_4326), pixel=px) ] function_result = split_images_to_cnn(image=image, window_size=5) self.assertEqual(target_result, function_result)
def test_geometry_frame_from_file(self): gdf = GeometryFrame.from_file(self.shape_path) self.assertEqual(gdf.crs, Crs("epsg:26917"))
def test_extent_comparison(self): extent1 = Extent.from_coordinates([100.0, 100.0, 120.0, 120.0], crs=Crs("epsg:4326")) extent2 = Extent.from_coordinates([100.00, 100.00, 120.00, 120.00], crs=Crs("epsg:4326")) assert extent1 == extent2
def test_polygon_from_wkt(self): poly = Polygon.from_wkt( "POLYGON((0.0 0.0, 0.0 1.0, 1.0 1.0, 1.0 0.0, 0.0 0.0))", Crs("local")) self.assertEqual(poly.area, 1.0)
class Extent: left_down = attr.ib(default=Point(0, 0)) right_up = attr.ib(default=Point(1, 1)) crs = attr.ib(default=Crs("epsg:4326")) origin = attr.ib(init=False) dx = attr.ib(init=False) dy = attr.ib(init=False) def __attrs_post_init__(self): self.origin = Origin(self.left_down.x, self.left_down.y) self.dx = count_delta(self.left_down.x, self.right_up.x) self.dy = count_delta(self.left_down.y, self.right_up.y) def transform(self, to_crs): pass def scale(self, x, y, origin=Point(0, 0)): """ This function takes x and y as the scaling values and divide extent dx and dy by them If origin Point is not passed by default it is Point(0, 0) :param x: Scaling value x :param y: Scaling value y :param origin: is the left down corner from which scaled extent will have origin :return: returns New instance of extent """ scaled_point = Point(int(self.dx / x + origin.x), int(self.dy / y + origin.y)) shrinked = Extent(origin, scaled_point) return shrinked def translate(self, x, y): """ Translates extent coordinates :param x: :param y: :return: """ return Extent(self.left_down, self.right_up.translate(x, y)) @classmethod def from_coordinates(cls, coordinates: List[float], crs="local"): point_a = Point(*coordinates[:2]) point_b = Point(*coordinates[2:]) return cls(point_a, point_b, crs) def expand(self, dx, dy): ld = self.left_down.translate(-dx, -dy) ru = self.right_up.translate(dx, dy) return Extent(ld, ru, crs=self.crs) def expand_percentage(self, percent_x, percent_y): return self.expand(int(self.dx * percent_x), int(self.dy * percent_y)) def expand_percentage_equally(self, percent): return self.expand_percentage(percent, percent) def expand_equally(self, value): return self.expand(value, value) def to_wkt(self): coordinates = [ self.left_down, self.left_down.translate(0, self.dy), self.right_up, self.left_down.translate(self.dx, 0), self.left_down ] coordinates_text = ", ".join([f"{el.x} {el.y}" for el in coordinates]) return f"POLYGON(({coordinates_text}))" def divide_dy(self, tile_size): tiles_number_dy = int(float(self.dy) // float(tile_size)) extents = [] for tile in range(0, tiles_number_dy): extents.append( Extent(self.right_up.translate(-self.dx, -(tile + 1) * tile_size), self.right_up.translate(0, (-tile) * tile_size), crs=self.crs)) if int(float(self.dy) // float(tile_size)) != float(self.dy) / float(tile_size): extents.append( Extent( self.right_up.translate(-self.dx, -self.dy), self.right_up.translate(0, -tiles_number_dy * tile_size), self.crs)) return extents def divide_dx(self, tile_size): tiles_number_dx = int(float(self.dx) // float(tile_size)) extents = [] for tile in range(tiles_number_dx): extents.append( Extent(self.left_down.translate(tile * tile_size, 0), self.left_down.translate((tile + 1) * tile_size, self.dy), crs=self.crs)) if int(float(self.dx) // float(tile_size)) == float(self.dx) / float(tile_size): extents.append( Extent( self.left_down.translate(tiles_number_dx * tile_size, 0), self.left_down.translate(self.dx, self.dy), self.crs)) return extents def divide(self, dx, dy): if all([dx, dy]): dy_divided = self.divide_dy(dy) extents = [] for dy_tile in dy_divided: dx_divided = dy_tile.divide_dx(dx) for dx_tile in dx_divided: extents.append(dx_tile) return extents else: raise AttributeError("You have to pass all the arguments") pass
class GeometryFrame: frame = attr.ib() geometry_column = attr.ib() crs = attr.ib(default=Crs("epsg:4326")) def __attr__post_init__(self): self.type = self.__class__.__name__ def to_wkt(self) -> 'GeometryFrame': """ Based on geometry column this method returns wkt representation of it, it does not modify passed instance, it create new one :return: GeometryFrame with new wkt column """ frame_copy = self.frame.copy() frame_copy["wkt"] = frame_copy["geometry"].apply(lambda x: x.wkt) return self.__class__(frame_copy, "geometry", crs=self.crs) @classmethod def from_file(cls, path, driver="ESRI Shapefile") -> 'GeometryFrame': """ :param path: File location :param driver: Driver for operning the file, currentyl supported drivers are ex. ESRI Shapefile GeoJSON GPKG OpenFileGDB For all supported drivers look at ```python import fiona fiona.supported_drivers ``` :return: GeometryFrame """ geometry = gpd.read_file(path, driver=driver) GeoFrame = cls(geometry, "geom", Crs(geometry.crs["init"])) return GeoFrame def transform(self, crs) -> 'GeometryFrame': """ Transform GeometryFrame to other coordinate reference system :param crs: crs code example 'epsg:2180' :return: GeometryFrame with target coordinate reference system """ return self.__class__( self.frame.to_crs({"init": crs}), crs=crs, geometry_column=self.geometry_column ) def union(self, attribute) -> 'GeometryFrame': """ :param attribute: name of column based on which dissolve will be done on geometry column :return: GeometryFrame """ dissolved = self.frame.dissolve(by=attribute, aggfunc='sum') geoframe = self.__class__(dissolved, "geometry") geoframe.type = "Multi" + self.type geoframe.crs = self.crs return geoframe def _assert_geom_type(self): unique_geometries = [el for el in set(self.frame.type) if el is not None] if unique_geometries.__len__() != 1: raise GeometryCollectionError("Object can not be collection of geometries") try: assert str(list(set(self.frame.type))[0]) + "Frame" == self.type except AssertionError: raise GeometryTypeError("Your input geometry type is incorrect") def show(self, limit=5, truncate=True) -> NoReturn: """ This function will show GeometryFrame in Apache Spark ASCII style :param limit: how many rows to show :param truncate: decide if record values should be truncated to 20 chars :return: NoReturn """ DataFrameShower(self.frame).show(limit, truncate) def plot(self, interactive=False, **kwargs) -> NoReturn: """ :param interactive: decide if this function should use folium or geopandas plotting function if Interactive is set to True than folium library will be used if False than geopandas plotting library. :param kwargs: Other arguments for plotting, look at geoframe plot function and folium Map constructor :return: NoReturn """ if interactive: return InteractiveGeometryPlotter(self).plot(**kwargs) else: self.frame.plot(**kwargs) def head(self, limit=5): """ This function returns number of rows specified in argument limit :param limit: How many rows to return :return: """ return self.frame.head(limit)
def in_memory(cls, x_shape, y_shape, crs=Crs("epsg:4326")): memory_ob = gdal.GetDriverByName('MEM') raster = memory_ob.Create('', x_shape, y_shape, 1, gdal.GDT_Byte) if raster is None: raise ValueError("Your image is to huge, please increase pixel size, by using pixel option in loading options, example pixel=Pixel(20.0, 20.0)") return cls(raster, crs=crs)
class GdalImage: ds = attr.ib(type=gdal.Dataset) path = attr.ib(default=None) crs = attr.ib(default=Crs("local")) def __attrs_post_init__(self): from gis import Pixel self.__transform_params = self.ds.GetGeoTransform() self.left_x = self.__transform_params[0] self.pixel_size_x = self.__transform_params[1] self.top_y = self.__transform_params[3] self.pixel_size_y = -self.__transform_params[5] self.x_size = self.ds.RasterXSize self.y_size = self.ds.RasterYSize self.pixel = Pixel(abs(self.pixel_size_x), abs(self.pixel_size_y)) self.extent = Extent.from_coordinates([ self.left_x, self.top_y - (self.y_size * abs(self.pixel_size_y)), self.left_x + abs(self.pixel_size_x) * self.x_size, self.top_y ], self.crs) self.band_number = self.ds.RasterCount @classmethod def load_from_file(cls, path: str, crs=None): """ class method which based on path returns GdalImage object, It validates path location and its format :return: GdalImage instance """ file = ImageFile(path) ds: gdal.Dataset = gdal.Open(file.path) if crs is None: projection = ds.GetProjection() srs = osr.SpatialReference(wkt=projection) crs_gdal = srs.GetAttrValue('AUTHORITY', 0).lower() + ":" + srs.GetAttrValue('AUTHORITY', 1) crs = Crs(crs_gdal) return cls(ds, path, crs) @classmethod def in_memory(cls, x_shape, y_shape, crs=Crs("epsg:4326")): memory_ob = gdal.GetDriverByName('MEM') raster = memory_ob.Create('', x_shape, y_shape, 1, gdal.GDT_Byte) if raster is None: raise ValueError("Your image is to huge, please increase pixel size, by using pixel option in loading options, example pixel=Pixel(20.0, 20.0)") return cls(raster, crs=crs) @classmethod def from_extent(cls, extent, pixel): new_extent = extent.scale(pixel.x, pixel.y) extent_new = Extent(Point(extent.origin.x, extent.origin.y), Point((new_extent.origin.x + new_extent.dx) * pixel.x, (new_extent.origin.y + new_extent.dy) * pixel.y)) raster = cls.in_memory(int(new_extent.dx), int(new_extent.dy), crs=extent.crs) transformed_raster = raster.transform(extent.origin, pixel) return transformed_raster, extent_new def __read_as_array(self, ds: gdal.Dataset) -> np.ndarray: if not hasattr(self, "__array"): setattr(self, "__array", ds.ReadAsArray()) return getattr(self, "__array") def __str__(self): return "\n".join([f"{key}: {value}" for key, value in self.__dict__.items()]) @property def array(self) -> np.ndarray: """ Property which returns numpy.ndarray representation of file :return: np.ndarray """ return self.__read_as_array(self.ds) def to_raster(self) -> Tuple['Pixel', 'ReferencedArray']: if self.array.shape.__len__() == 3: arr = self.array elif self.array.shape.__len__() == 2: arr = self.array.reshape(1, *self.array.shape) else: raise DimensionException("Array should be shape 2 or 3") ref = ReferencedArray( array=arr.transpose([1, 2, 0]), crs=self.crs, extent=self.extent, band_number=self.band_number, shape=[self.pixel_size_y, self.pixel_size_x] ) return self.pixel, ref def transform(self, origin: 'Origin', pixel: 'Pixel', projection='LOCAL_CS["arbitrary"]'): self.ds.SetGeoTransform((origin.x, pixel.x, 0.0, origin.y + (self.y_size * pixel.y), 0, -pixel.y)) left_top_corner_x, pixel_size_x, _, left_top_corner_y, _, pixel_size_y = self.ds.GetGeoTransform() self.ds.SetProjection(projection) return GdalImage(self.ds, crs=self.crs) def insert_polygon(self, wkt, value): srs = osr.SpatialReference('LOCAL_CS["arbitrary"]') rast_ogr_ds = ogr.GetDriverByName('Memory').CreateDataSource('wrk') rast_mem_lyr = rast_ogr_ds.CreateLayer('poly', srs=srs) feat = ogr.Feature(rast_mem_lyr.GetLayerDefn()) feat.SetGeometryDirectly(ogr.Geometry(wkt=wkt)) rast_mem_lyr.CreateFeature(feat) err = gdal.RasterizeLayer(self.ds, [1], rast_mem_lyr, None, None, [value], ['ALL_TOUCHED=TRUE']) return GdalImage(self.ds)