def from_geo_file(geo_file: str, res, buffer_accuracy=1, to_crs: str = ''): """ Get geo grid from existing geo file (such as shapefile) :param geo_file: path to geo file (e.g. shapefile) :param res: resolution of the grid :param buffer_accuracy: accuracy of the buffer surrounded the region (in degrees) :param to_crs: final coordinate reference system of the geo grid (empty string for keeping the original CRS, under the form 'epsg:number' or pyproj name otherwise) :return: GeoGrid """ # Import locally (to avoid conflicts) from gistools.projections import proj4_from check_type(geo_file, str, res, (int, float), buffer_accuracy, (int, float), to_crs, str) if os.path.isfile(geo_file): geo_ds = gpd.read_file(geo_file) else: raise GeoGridError("{} is not a valid geo file".format(geo_file)) try: to_crs = pyproj.CRS(to_crs) except ValueError: warnings.warn( "Empty or invalid CRS/proj name. Geo grid matching geo file projection", GeoGridWarning) else: geo_ds = geo_ds.to_crs(to_crs) return GeoGrid.from_geopandas(geo_ds, res, buffer_accuracy)
def check_string(string: str, list_of_strings: Collection) -> str: """ Check validity of and return string against list of valid strings :param string: searched string :type string: str :param list_of_strings: list/tuple/set of valid strings string is to be checked against :type list_of_strings: list, tuple, set :return: validate string from list of strings if match :rtype: str :Example: >>> check_string("well", ["Well Done", "no match", "etc."]) "Well Done" >>> check_string("slow", ("slow", "slowdown")) ValueError: input match more than one valid value among ["slow", "slowdown"] >>> check_string("few", {"missed", "little"}) ValueError: input must match one of those: ["missed", "little"] """ check_type(string, str, list_of_strings, Collection) [check_type(x, str) for x in list_of_strings] output_string = [] for item in list_of_strings: if item.lower().startswith(string.lower()): output_string.append(item) if len(output_string) == 1: return output_string[0] elif len(output_string) == 0: raise ValueError("input must match one of those: {}".format(list_of_strings)) elif len(output_string) > 1: raise ValueError("input match more than one valid value among {}".format(list_of_strings))
def __init__(self, roads, nodes, *args, **kwargs): """ :param roads: Road instance :param nodes: road nodes """ check_type(roads, Road, nodes, RoadNode) super().__init__(roads, nodes, *args, **kwargs)
def polygonize(self, field_name, layer_name="layer", is_8_connected=False): """ Convert raster into vector polygon(s) :param field_name: name of the corresponding field in the final shape file :param layer_name: name of resulting layer :param is_8_connected: pixel connectivity used for polygon :return: """ check_type(is_8_connected, bool) with ShapeTempFile() as out_shp: self._gdal_polygonize(out_shp, layer_name, field_name, is_8_connected) return PolygonLayer(out_shp, name=layer_name)
def __init__(self, raster, geo_grid: GeoGrid = None, no_data_value=None, crs=None): """ RasterMap constructor :param raster: raster file or numpy array :param geo_grid: GeoGrid instance :param no_data_value: data to be regarded as "no_data" :param crs: projection used for the raster :Example: >>> """ check_type(raster, (str, np.ndarray)) if type(raster) == np.ndarray: raster_file = None try: test_fit = geo_grid.data_fits(raster) crs = pyproj.CRS(crs) # crs = proj4_from(crs) if not test_fit: raise RasterMapError("Input geo grid does not fit raster") except AttributeError: raise RasterMapError("Geo grid argument has not been set") except (ValueError, TypeError): raise RasterMapError("Invalid projection: crs='{}'".format(crs)) else: raster_file = raster try: geo_grid = GeoGrid.from_raster_file(raster) crs = crs_from_raster(raster) raster = raster_to_array(raster) except RuntimeError: raise RasterMapError("Invalid/unknown file '%s'" % raster_file) # Set attributes self._raster_file = raster_file self._geo_grid = geo_grid self._raster_array = np.array(raster, dtype='float64') # Ensure compatibility with NaNs self._crs = crs self._res = self._geo_grid.res self._x_origin = self._geo_grid.geo_transform[0] self._y_origin = self._geo_grid.geo_transform[3] self._x_size = self._geo_grid.num_x self._y_size = self._geo_grid.num_y self._shape = self._raster_array.shape self._no_data_value = no_data_value if no_data_value is not None: self._raster_array[self._raster_array == no_data_value] = np.nan # Use attribute (raster_array) rather than # instance (self == ...) to avoid 'recursion' error with decorator above # Available filters self._filters = {"majority_filter": self._majority_filter, "sieve": self._gdal_sieve}
def shape_to_raster(shapefile: str, raster_file: str, geo_grid: GeoGrid, geo_crs: str = "WGS84", burn_value=1, no_data_value=0): """ Convert shape to raster Convert input file (e.g shapefile) geometry into raster (see https://pcjericks.github.io/py-gdalogr-cookbook/raster_layers.html) :param shapefile: path to shapefile to convert :param raster_file: path to raster file where to write :param geo_grid: geo grid which raster must rely on :param geo_crs: geographic coordinate system (see http://spatialreference.org/ref/epsg/wgs-84/ for the WGS84 CRS for instance) :param burn_value: value to be burned from shape into raster :param no_data_value: value of raster cells where there are no data """ check_type(shapefile, str, raster_file, str, geo_grid, GeoGrid, geo_crs, str) source_ds = ogr.Open(shapefile) if source_ds is None: raise RuntimeError("Unable to open {}".format(shapefile)) # Retrieve bounding box of geometry source_layer = source_ds.GetLayer() # Create the destination data source driver = gdal.GetDriverByName('GTiff') # Be very careful here with geo_grid.num_x # and geo_grid.num_y : they are not the number # of pixels but the number of edges regarding # raster computation... target_ds = driver.Create(raster_file, geo_grid.num_x, geo_grid.num_y, 1, gdal.GDT_Byte) target_ds.SetGeoTransform(geo_grid.geo_transform) band = target_ds.GetRasterBand(1) band.SetNoDataValue(no_data_value) # Set geographic coordinate system projection = osr.SpatialReference() projection.SetWellKnownGeogCS(geo_crs) target_ds.SetProjection(projection.ExportToWkt()) # Rasterization gdal.RasterizeLayer(target_ds, [1], source_layer, burn_values=[burn_value]) return 0
def raster_to_array(raster_file: str, band=None): """ Get numpy array from raster file :param raster_file: path to raster file :param band: raster band number :return: numpy array :Example: >>> array = raster_to_array("/path/to/my/raster.tif") """ check_type(raster_file, str) source_ds = gdal.Open(raster_file) if band: source_ds = source_ds.GetRasterBand(band) return source_ds.ReadAsArray()
def __init__(self, edges, nodes, match_edge_nodes=True, tolerance=1): """ Network class constructor :param edges: Edge instance :param nodes: Node instance :param match_edge_nodes: Boolean --> match edge nodes with respect to tolerance :param tolerance: distance tolerance for considering nodes and edge nodes the same (in m) """ check_type(edges, Edge, nodes, Node) self._edges = edges self._nodes = nodes # Retrieve edge nodes corresponding to nodes if match_edge_nodes: edge_nodes = self._edges.get_nodes() distance, nn = nodes.distance_and_nearest_neighbor(edge_nodes) self._nodes["geometry"] = [edge_nodes.geometry[n] for n in nn] self._nodes = self._nodes[distance <= tolerance]
def compute_surface(lb_pixel, rb_pixel, ub_pixel, bb_pixel, geo_type: str, ellipsoid: Ellipsoid): """ Compute surface of earth pixels Combine surface element in spherical coordinates and local spherical approximation of the given ellipsoid to compute pixel surface. Or return surface from evenly spaced pixels from equiarea map projection. :param lb_pixel: left border pixel coordinate :param rb_pixel: right border pixel coordinate :param ub_pixel: upper border pixel coordinate :param bb_pixel: bottom border pixel coordinate :param geo_type: geo projection ("latlon" or "equal") :param ellipsoid: ellipsoid model :return: """ latitude = (ub_pixel + bb_pixel) / 2 def ellipsoid_normal(): return ellipsoid.a / sqrt(1 - (ellipsoid.e**2) * (sin(latitude * pi / 180))**2) def local_sphere_radius(): p = ellipsoid.a * (1 - ellipsoid.e**2) / ( 1 - ellipsoid.e**2 * sin(latitude * pi / 180)**2)**(3 / 2) return sqrt(ellipsoid_normal() * p) * 10**-3 check_type(geo_type, str, ellipsoid, Ellipsoid) if geo_type == "latlon": surface = local_sphere_radius() ** 2 * (sin(ub_pixel * pi / 180) - sin(bb_pixel * pi / 180)) *\ (rb_pixel * pi / 180 - lb_pixel * pi / 180) elif geo_type == "equal": surface = abs(ub_pixel - bb_pixel) * abs(rb_pixel - lb_pixel) else: raise ValueError( "Projection {} has not been defined yet".format(geo_type)) return surface
def from_raster_file(raster_file: str): """ Retrieve geo grid from raster file :param raster_file: path to raster file :return: GeoGrid """ check_type(raster_file, str) try: source_ds = gdal.Open(raster_file) except RuntimeError as error: raise error geo_transform = source_ds.GetGeoTransform() # Warning: ll_corner and ur_corner correspond to grid corners # that is pixel centers, but gdal rasters are defined with respect # to pixel corners... That's why "+- res/2" ll_corner = (geo_transform[3] + source_ds.RasterYSize * geo_transform[5] - geo_transform[5] / 2, geo_transform[0] + geo_transform[1] / 2) ur_corner = (geo_transform[3] + geo_transform[5] / 2, geo_transform[0] + source_ds.RasterXSize * geo_transform[1] - geo_transform[1] / 2) # Geo type if crs_from_raster(raster_file).is_geographic: geo_type = "latlon" else: geo_type = "equal" if math.isclose(math.fabs(geo_transform[1]), math.fabs(geo_transform[5])): geo_grid = GeoGrid(ll_corner, ur_corner, math.fabs(geo_transform[1]), geo_type) else: geo_grid = None warnings.warn('No regular raster: no GeoGrid implemented', Warning) return geo_grid
def from_geopandas(geopandas_series, res, buffer_accuracy=1): """ Get geo grid from specific geopandas database :param geopandas_series: :param res: :param buffer_accuracy: :return: """ check_type(geopandas_series, (gpd.GeoDataFrame, gpd.GeoSeries), res, (int, float), buffer_accuracy, (int, float)) # Default type for geo grid geo_type = 'latlon' try: # If resolution in km/m, type = 'equal' if not pyproj.Proj(geopandas_series.crs).crs.is_geographic: geo_type = 'equal' except AttributeError: raise ValueError("Input GeoSeries does not seem to be valid") try: bounds = geopandas_series.bounds except ValueError as e: raise GeoGridError("GeoSeries has invalid bounds:\n {}".format(e)) try: ll_corner = (buffer_accuracy * math.floor( (bounds.miny.min() + res / 2) / buffer_accuracy), buffer_accuracy * math.floor( (bounds.minx.min() + res / 2) / buffer_accuracy)) ur_corner = (buffer_accuracy * math.ceil( (bounds.maxy.max() - res / 2) / buffer_accuracy), buffer_accuracy * math.ceil( (bounds.maxx.max() - res / 2) / buffer_accuracy)) except (ValueError, ZeroDivisionError, FloatingPointError): raise ValueError( "Value of buffer accuracy ({}) is not valid".format( buffer_accuracy)) else: return GeoGrid(ll_corner, ur_corner, res, geo_type)
def plot_geolayer(gl, ax=None, attribute=None, layer_label: str = None, layer_color=None, labels: str = None, marker=None, **kwargs): """ Plot geo layer :param gl: :param ax: :param attribute: attribute of the geo layer :param layer_label: :param layer_color: :param labels: :param marker: :param kwargs: :return: """ def patch_point(): return mlines.Line2D([0], [0], linestyle="none", marker=marker, color=layer_color, label=layer_label) def patch_line(): return mlines.Line2D([], [], color=layer_color, label=layer_label) def patch_polygon(): return mpatches.Patch(color=layer_color, label=layer_label) # Marker type for point geometry if marker is None: marker = "o" handles = [] if ax is not None: try: handles = ax.handles except AttributeError: pass else: ax = plt.gca() patch_options = { 'Line': patch_line, 'Polygon': patch_polygon, 'Point': patch_point } if layer_color is not None: try: color_patch = patch_options[gl.geom_type]() except ValueError: raise ValueError("'{}' is not a valid color".format(layer_color)) handles.append(color_patch) # Use geopandas plotting function if gl.geom_type == "Point": ax = gl._gpd_df.plot(ax=ax, column=attribute, color=layer_color, marker=marker, label=layer_label, **kwargs) else: ax = gl._gpd_df.plot(ax=ax, column=attribute, color=layer_color, label=layer_label, **kwargs) if labels is not None: check_type(labels, str) # label_list = [] new_layer = gl.shallow_copy() new_layer['label_point'] = new_layer.geometry.apply( lambda x: x.representative_point().coords[:]) new_layer['label_point'] = [lb[0] for lb in new_layer["label_point"]] for _, row in new_layer.iterrows(): xy = row["label_point"] s = row["name"] ax.annotate(s=s, xy=xy) # label_list.append(pyplot.text(xy[0], xy[1], s)) # adjust_text(label_list, only_move={'text': 'xy'}, force_points=0.15) # This function return a new attribute to Axes object: handles ax.handles = handles return ax