#!/usr/bin/env python3 """This scripts benchmarks both the database reading accesses as well as the projections between polar and cartesian representations. The function :func:`_query_database` queries the database for some location and radius which were deemed a realistic load scenario. The function :func:`_project_to_cartesian_and_back` takes the result of such a query and projects it to the cartesian representation and back. Keep in mind, that these operations might only be performed very seldom on an actual Atlantic crossing (maybe every couple of hours). The results and details are now included in :ref:`benchmarking-db-and-local-projections` (in the documentation of the :mod:`pyrate.plan` module). This script was initially developed as part of `issue #40 `__, in order to evaluate whether "custom" local projections are a feasible option on a Raspberry Pi 3B/4B. Since then, the implementation has changed. In particular, the database creation as been moved into a separate script (see :ref:`script-s57_charts_to_db`). """ # Standard library from argparse import ArgumentDefaultsHelpFormatter from argparse import ArgumentParser from time import perf_counter # Typing from typing import Any from typing import Callable from typing import List # Data modeling import numpy # Geospatial from pyrate.plan.geometry import PolarGeometry from pyrate.plan.geometry import PolarLocation # Database from pyrate.common.charts import SpatialiteDatabase #: Around Miami, Florida, US. #: The point was chosen simply because charts are available nearby. QUERY_AROUND = PolarLocation(longitude=-80.10955810546875, latitude=25.851808634972723) def _query_database(path_to_db: str, around: PolarLocation, radius: float) -> List[PolarGeometry]: """Queries some polygons from the database. Args: path_to_db: The path to the database around: The location around which to query for chart objects radius: The radius within which to query for chart objects in meters Returns: The resulting polygons """ with SpatialiteDatabase(path_to_db) as database: return list(database.read_geometries_around(around=around, radius=radius)) def _project_to_cartesian_and_back(data: List[PolarGeometry]) -> None: """Projects some PolarPolygons to their cartesian representation and back to test the performance. Args: data: Some polygons to project """ assert data # non-emptiness first = data[0] center = first if isinstance(first, PolarLocation) else first.locations[0] for polygon in data: polygon.to_cartesian(center).to_polar() def _measure_func(func: Callable[..., Any], name: str, iterations: int, *params, **kw_params) -> None: """Measures and prints the running time of a given method. Args: func: The callable to execute name: The name to use for printing iterations: The number of iterations to average over *params: Positional arguments to be passed to the callable **kw_params: Keyword arguments to be passed to the callable """ results = numpy.empty((iterations,)) for i in range(iterations): start = perf_counter() func(*params, **kw_params) end = perf_counter() results[i] = end - start print(f'Executed "{name}" {iterations} times:') print(f"\taverage:\t {numpy.mean(results):.6f} seconds") print(f"\tstd dev:\t {numpy.std(results):.6f} seconds") print(f"\tvariance:\t {numpy.var(results):.6f} seconds") def benchmark(path_to_db: str, iterations: int, around: PolarLocation, radius: float) -> None: """Performs the benchmark and prints the results to the console. Args: path_to_db: The path to the database iterations: The number of iterations to average over around: The location around which to query for chart objects radius: The radius within which to query for chart objects in meters """ print("Information on the setting:") with SpatialiteDatabase(path_to_db) as database: print(f"\tnumber of rows/polygons in database:\t\t\t {len(database)}") print(f"\tsum of vertices of all rows/polygons of in database:\t {database.count_vertices()}") data = _query_database(path_to_db, around, radius) print(f"\textracted number of polygons:\t\t\t\t {len(data)}") vertex_count = sum(1 if isinstance(poly, PolarLocation) else len(poly.locations) for poly in data) print(f"\textracted total number of vertices:\t\t\t {vertex_count}") print() # newline _measure_func(_query_database, "query_database", iterations, path_to_db, around, radius) print() # newline _measure_func(_project_to_cartesian_and_back, "project_to_cartesian_and_back", iterations, data) def _main() -> None: """The main function.""" parser = ArgumentParser( description="Benchmark DB queries and projections for a fixed location (see docs/scripts).", formatter_class=ArgumentDefaultsHelpFormatter, ) parser.add_argument("path_to_db", type=str) parser.add_argument("--iterations", type=int, default=10) parser.add_argument("--radius", type=float, default=100, help="The query radius in kilometers") args = parser.parse_args() benchmark( path_to_db=args.path_to_db, iterations=args.iterations, around=QUERY_AROUND, radius=args.radius * 1000 ) if __name__ == "__main__": _main()