def coordinate_system(self, domain: GeoPolygon) -> str: raster_files = self.directory.glob("*.tif") for filepath in raster_files: geo_polygon = GeoPolygon.from_raster_file(filepath=filepath) if domain.intersects(geo_polygon): return geo_polygon.crs.to_proj4() raise RuntimeError( "Defining polygon does not intersect with dem raster data.")
def test_get(): x0 = 8.54758671814368 y0 = 60.898468 repo = RasterRepository(directory=Path(data_dir) / "dem_archive") input_crs = pyproj.CRS.from_string("+init=EPSG:4326") polygon = Polygon.from_bounds(xmin=x0 - 0.1, xmax=x0 + 0.1, ymin=y0 - 0.1, ymax=y0 + 0.1) geo_polygon = GeoPolygon(crs=input_crs, polygon=polygon) result = repo.read(domain=geo_polygon) assert result
def polygon(): # Create polygonal approximation to a circle N = 117 x, y = array([0.51, 0.5]) r = 0.4 epsg_id = 32633 polygon = Polygon([(x + r * cos(t), y + r * sin(t)) for t in linspace(0, 2 * pi, N + 1)[:-1]]) return GeoPolygon(polygon=polygon, crs=pyproj.CRS.from_epsg(epsg_id))
def land_cover(self, *, land_types: Optional[List[LandCoverType]], geo_points: GeoPoints, domain: GeoPolygon) -> np.ndarray: tree = etree.parse(str(self.fn), self.parser) root = tree.getroot() self._assign_data_crs(root) pts_crs = geo_points.crs if pts_crs.to_authority() != self.data_crs.to_authority(): xy = np.dstack( Transformer.from_crs(pts_crs, self.data_crs).transform( geo_points.xy[:, 0], geo_points.xy[:, 1])).reshape((-1, 2)) else: xy = geo_points.xy # Add rel_buffer_size buffer on domain to make sure we hit all geo_points in data projection rel_buffer_size = 0.02 domain = domain.transform(target_crs=self.data_crs) bbox = domain.polygon.bounds buffer_size = (bbox[2] - bbox[0]) * rel_buffer_size domain = domain.buffer(buffer_size) # At this point, we have a consistent coordinate system for domain and xy all in the self.data_crs, so # omit checks for speed. land_cover_types = self.read(domain) # TODO: Move to C++ for speed! faces = np.zeros(len(xy), dtype="int") for i, _pt in enumerate(xy): pt = Point(_pt) found = False for key, p_list in land_cover_types.items(): for p in p_list: if p.intersects(pt): faces[i] = key.value found = True break if found: break if not found: raise RuntimeError(f"Illegal point {pt}") return faces
def test_gml_repository(): if "RASPUTIN_DATA_DIR" not in os.environ: raise RuntimeError("Please set RASPUTIN_DATA_DIR") path = Path(os.environ["RASPUTIN_DATA_DIR"]) / "corine" input_crs = pyproj.CRS.from_string("+init=EPSG:4326") target_crs = pyproj.CRS.from_epsg(32633) x = np.array([8.5, 8.52, 8.52, 8.5]) y = np.array([60.55, 60.55, 60.50, 60.50]) x, y = pyproj.Transformer.from_crs(input_crs, target_crs).transform(x, y) domain = GeoPolygon(polygon=Polygon(shell=list(zip(x, y))), crs=target_crs) repos = GMLRepository(path=path) plot = False if plot: response = repos.read(domain=domain) fig = plt.figure() ax = fig.gca() for key in response: color = [c / 255 for c in LandCoverType.color(land_cover_type=key)] for p in response[key]: ax.add_patch(PolygonPatch(p, fc=color, alpha=0.5)) ax.set_xbound(min(x), max(x)) ax.set_ybound(min(y), max(y)) plt.show() dem_archive = Path(os.environ["RASPUTIN_DATA_DIR"]) / "dem_archive" rr = RasterRepository(directory=dem_archive) raster_domain = domain.transform( target_crs=pyproj.CRS.from_proj4(rr.coordinate_system(domain=domain))) raster_data_list = rr.read(domain=raster_domain) mesh = Mesh.from_raster(data=raster_data_list, domain=raster_domain) cmesh = mesh.simplify(ratio=0.1) geo_cell_centers = GeoPoints(xy=cmesh.cell_centers[:, :2], crs=target_crs) terrain_cover = repos.land_cover(land_types=None, geo_points=geo_cell_centers, domain=domain) assert terrain_cover is not None
def get_intersections(self, *, target_polygon: GeoPolygon) -> List[Rasterdata]: parts = [] raster_files = self.directory.glob("*.tif") logger = getLogger() for filepath in raster_files: geo_polygon = GeoPolygon.from_raster_file(filepath=filepath) if target_polygon.intersects(geo_polygon): logger.info(f"Using file: {filepath}") polygon = target_polygon.transform( target_crs=geo_polygon.crs).polygon part = read_raster_file(filepath=filepath, polygon=polygon) parts.append(part) target_polygon = target_polygon.difference(geo_polygon) if target_polygon.polygon.area < 1e-10: break return parts
def polygon_w_hole(): # Create polygonal approximation to a circle N1, N2 = 117, 77 x, y = array([0.51, 0.5]) r1, r2 = 0.4, 0.25 epsg_id = 32633 polygon_1 = Polygon((x + r1 * cos(t), y + r1 * sin(t)) for t in linspace(0, 2 * pi, N1 + 1)[:-1]) polygon_2 = Polygon((x + r2 * cos(t), y + r2 * sin(t)) for t in linspace(0, 2 * pi, N2 + 1)[:-1]) return GeoPolygon(polygon=polygon_1.difference(polygon_2), crs=pyproj.CRS.from_epsg(epsg_id))
def read(self, domain: GeoPolygon) -> Dict[LandCoverType, List[Polygon]]: tree = etree.parse(str(self.fn), self.parser) root = tree.getroot() self._assign_data_crs(root) if domain.crs.to_authority() != self.data_crs.to_authority(): domain = domain.transform(target_crs=self.data_crs) assert "gml" in root.nsmap, "Not a GML file!" assert "ogr" in root.nsmap, "Can not find land cover types!" gml = f"{{{root.nsmap['gml']}}}" ogr = f"{{{root.nsmap['ogr']}}}" result = {} for elm in root.iter(f"{gml}featureMember"): code = LandCoverType(int(next(elm.iter(f"{ogr}clc18_kode")).text)) polygon = self._parse_polygon(next(elm.iter(f"{gml}Polygon")), root.nsmap) if polygon.intersects(domain.polygon): if code not in result: result[code] = [] result[code].append(polygon.intersection(domain.polygon)) return result
def polygon(self): return GeoPolygon(polygon=Polygon.from_bounds(*self.box), crs=pyproj.CRS.from_proj4(self.coordinate_system))
def store_tin(): """ Avalance Forecast Visualization Example. Items to develop: * Construct several color maps for each avalanche danger, and make the web app offer selections * Alternative approach is to partition the whole area into disjoint topologies and switch between these * Use different textures for different terrain types (under development) * Use more of the information from varsom.no (and perhaps alpha blending) to better display avalanche dangers """ logging.basicConfig(level=logging.CRITICAL, format='Rasputin[%(levelname)s]: %(message)s') logger = logging.getLogger() arg_parser = argparse.ArgumentParser() arg_parser.add_argument("-x", nargs="+", type=float, help="x-coordinates of polygon", default=None) arg_parser.add_argument("-y", nargs="+", type=float, help="y-coordinates of polygon", default=None) arg_parser.add_argument("-polyfile", type=str, help="Polygon definition in WKT or WKB format", default="") arg_parser.add_argument("-target-coordinate-system", type=str, default="EPSG:32633", help="Target coordinate system") arg_parser.add_argument("-ratio", type=float, default=0.4, help="Mesh coarsening factor in [0, 1]") arg_parser.add_argument("-override", action="store_true", help="Replace existing archive entry") arg_parser.add_argument("-land-type-partition", type=str, default="", choices=["corine", "globcov"], help="Partition mesh by land type") arg_parser.add_argument("uid", type=str, help="Unique ID for the result TIN") arg_parser.add_argument("-silent", action="store_true", help="Run in silent mode") res = arg_parser.parse_args(sys.argv[1:]) if not res.silent: logger.setLevel(logging.INFO) if "RASPUTIN_DATA_DIR" in os.environ: dem_archive = Path(os.environ["RASPUTIN_DATA_DIR"]) / "dem_archive" tin_archive = Path(os.environ["RASPUTIN_DATA_DIR"]) / "tin_archive" gc_archive = Path(os.environ["RASPUTIN_DATA_DIR"]) / "globcov" corine_archive = Path(os.environ["RASPUTIN_DATA_DIR"]) / "corine" else: # data_dir = Path(os.environ["HOME"]) /"projects" / "rasputin_data" / "dem_archive" dem_archive = Path(".") / "dem_archive" tin_archive = Path(".") / "tin_archive" gc_archive = Path(".") / "globcov" corine_archive = Path(".") / "corine" logger.critical(f"WARNING: No data directory specified, assuming dem_archive {dem_archive.absolute()}") logger.critical(f"WARNING: No data directory specified, assuming tin_archive {tin_archive.absolute()}") logger.critical(f"WARNING: No data directory specified, assuming globcov_archive {gc_archive.absolute()}") logger.critical(f"WARNING: No data directory specified, assuming corine_archive {corine_archive.absolute()}") # Some sanity checks try: next(dem_archive.glob("*.tif")) except StopIteration as si: raise RuntimeError(f"No GeoTIFF files found in {dem_archive.absolute()}, giving up.") if not tin_archive.exists(): tin_archive.mkdir(parents=True) elif tin_archive.exists() and not tin_archive.is_dir(): raise RuntimeError(f"{tin_archive} exists and is not a directory, giving up.") tr = TinRepository(path=tin_archive) if res.uid in tr.content: if not res.override: raise RuntimeError(f"Tin archive {tin_archive.absolute()} already contains uid {res.uid}.") else: tr.delete(res.uid) # Determine region of interest if res.polyfile: input_domain = GeoPolygon.from_polygon_file(filepath=Path(res.polyfile), crs=pyproj.CRS.from_string("+init=EPSG:4326")) elif (res.x and res.y): assert 3 <= len(res.x) == len(res.y), "x and y coordinates must have equal length greater or equal to 3" source_polygon = Polygon((x, y) for (x, y) in zip(res.x, res.y)) input_domain = GeoPolygon(polygon=source_polygon, crs=pyproj.CRS.from_string("+init=EPSG:4326")) else: raise RuntimeError("A constraining polygon is needed") target_crs = pyproj.CRS.from_string(f"+init={res.target_coordinate_system}") target_domain = input_domain.transform(target_crs=target_crs) raster_repo = RasterRepository(directory=dem_archive) raster_crs = pyproj.CRS.from_string(raster_repo.coordinate_system(domain=target_domain)) raster_domain = input_domain.transform(target_crs=raster_crs) raster_data_list = raster_repo.read(domain=raster_domain) mesh = (Mesh.from_raster(data=raster_data_list, domain=raster_domain) .simplify(ratio=res.ratio)) assert len(mesh.points), "No tin extracted, something went wrong..." if raster_crs.to_authority() != target_crs.to_authority(): points, faces = mesh.points, mesh.faces proj = pyproj.Transformer.from_crs(raster_crs, target_crs) x, y, z = proj.transform(points[:, 0], points[:, 1], points[:, 2]) points = np.dstack([x, y, z]).reshape(-1, 3) mesh = Mesh.from_points_and_faces(points=points, faces=faces, proj4_str=target_crs.to_proj4()) if res.land_type_partition: if res.land_type_partition == "corine": lt_repo = gml_repository.GMLRepository(path=corine_archive) else: lt_repo = globcov_repository.GlobCovRepository(path=gc_archive) geo_cell_centers = GeoPoints(xy=mesh.cell_centers[:, :2], crs=target_crs) terrain_cover = lt_repo.land_cover(land_types=None, geo_points=geo_cell_centers, domain=target_domain) terrain_colors = np.empty((terrain_cover.shape[0], 3), dtype='d') extracted_terrain_types = set() for i, cell in enumerate(terrain_cover): if cell not in extracted_terrain_types: extracted_terrain_types.add(cell) meta_info = lt_repo.land_cover_meta_info_type for tt in extracted_terrain_types: cover = lt_repo.land_cover_type(tt) color = [c/255 for c in meta_info.color(land_cover_type=cover)] terrain_colors[terrain_cover == tt] = color tr.save(uid=res.uid, geometry=Geometry(mesh=mesh, crs=target_crs), land_cover_repository=lt_repo, face_fields={"cover_type": terrain_cover, "cover_color": terrain_colors}) else: tr.save(uid=res.uid, geometry=Geometry(mesh=mesh, crs=target_crs) ) meta = tr.content[res.uid] logger.info(f"Successfully added uid='{res.uid}' to the tin archive {tin_archive.absolute()}, with meta info:") pprint.PrettyPrinter(indent=4).pprint(meta)
def geo_tiff_reader(): logger = getLogger() arg_parser = argparse.ArgumentParser() arg_parser.add_argument("input", type=str, metavar="FILENAME") arg_parser.add_argument("-output", type=str, default="output.off", help="Surface mesh file name") arg_parser.add_argument("-x", nargs="+", type=float, help="x-coordinates of polygon", default=None) arg_parser.add_argument("-y", nargs="+", type=float, help="y-coordinates of polygon", default=None) arg_parser.add_argument("-sun_x", type=float, help="Sun ray x component") arg_parser.add_argument("-sun_y", type=float, help="Sun ray y component") arg_parser.add_argument("-sun_z", type=float, help="Sun ray z component") arg_parser.add_argument("-n", action="store_true", help="Compute surface normals") arg_parser.add_argument("-slope", action="store_true", help="Compute slope") arg_parser.add_argument("-loglevel", type=int, default=1, help="Verbosity") group = arg_parser.add_mutually_exclusive_group(required=True) group.add_argument( "-ratio", type=float, help="Edge ratio between original meshed raster and result") group.add_argument("-size", type=int, help="Number of edges in the generated surface mesh") res = arg_parser.parse_args(sys.argv[1:]) logger.setLevel(res.loglevel) # Determine region of interest x_coords = res.x if res.x else [] y_coords = res.y if res.y else [] if len(x_coords) == len(y_coords) == 0: polygon = None elif 3 <= len(x_coords) == len(y_coords): polygon = Polygon((x, y) for (x, y) in zip(x_coords, y_coords)) else: raise ValueError( "x and y coordinates must have equal length greater or equal to 3") # Read from raster rasterdata = read_raster_file(filepath=Path(res.input), polygon=polygon) m, n = rasterdata.array.shape logger.debug(f"Original: {m * n}") logger.critical(rasterdata.info) domain = GeoPolygon(polygon=polygon, proj=None) mesh = (Mesh.from_raster(rasterda=rasterdata, domain=domain).simplify(ratio=res.ratio, max_size=res.max_size)) pts, faces, normals = mesh.points, mesh.faces, mesh.face_normals logger.debug(f"Result: {len(pts)}") # Compute additional fields fields = {} if res.sun_x is not None or res.sun_y is not None or res.sun_z is not None: assert res.sun_x is not None assert res.sun_y is not None assert res.sun_z is not None fields["shade"] = compute_shade( pts=pts, faces=faces, sun_ray=[res.sun_x, res.sun_y, res.sun_z]) if res.n: fields["surface_normal"] = normals if res.slope: slopes = compute_slopes(normals) fields["slope"] = slopes output_path = Path(res.output) write(pts=pts, faces=faces, filepath=output_path, fields=fields)