diff --git a/.github/workflows/workflow.yml b/.github/workflows/workflow.yml index 1457fea..f83ab08 100644 --- a/.github/workflows/workflow.yml +++ b/.github/workflows/workflow.yml @@ -46,15 +46,12 @@ jobs: - name: Install dev dependencies run: | python -m pip install --no-cache-dir -r requirements-dev.txt - - name: Run black + - name: Run ruff format run: | - python -m black -l 100 --check pyinfraformat/ - - name: Run pydocstyle + python -m ruff format --check pyinfraformat/ + - name: Run ruff lint run: | - python -m pydocstyle --add-ignore=D105 --convention=numpy pyinfraformat/ - - name: Run pylint - run: | - python -m pylint pyinfraformat/ + python -m ruff check pyinfraformat - name: Run tests run: | mkdir testing_folder diff --git a/.pylintrc b/.pylintrc deleted file mode 100644 index c7134aa..0000000 --- a/.pylintrc +++ /dev/null @@ -1,497 +0,0 @@ -[MASTER] - -# A comma-separated list of package or module names from where C extensions may -# be loaded. Extensions are loading into the active Python interpreter and may -# run arbitrary code -extension-pkg-whitelist= - -# Add files or directories to the blacklist. They should be base names, not -# paths. -ignore=test_data - -# Add files or directories matching the regex patterns to the blacklist. The -# regex matches against base names, not paths. -ignore-patterns= - -# Python code to execute, usually for sys.path manipulation such as -# pygtk.require(). -#init-hook= - -# Use multiple processes to speed up Pylint. -jobs=1 - -# List of plugins (as comma separated values of python modules names) to load, -# usually to register additional checkers. -load-plugins= - -# Pickle collected data for later comparisons. -persistent=yes - -# Specify a configuration file. -#rcfile= - -# When enabled, pylint would attempt to guess common misconfiguration and emit -# user-friendly hints instead of false-positive error messages -suggestion-mode=yes - -# Allow loading of arbitrary C extensions. Extensions are imported into the -# active Python interpreter and may run arbitrary code. -unsafe-load-any-extension=no - - -[MESSAGES CONTROL] - -# Only show warnings with the listed confidence levels. Leave empty to show -# all. Valid levels: HIGH, INFERENCE, INFERENCE_FAILURE, UNDEFINED -confidence= - -# Disable the message, report, category or checker with the given id(s). You -# can either give multiple identifiers separated by comma (,) or put this -# option multiple times (only on the command line, not in the configuration -# file where it should appear only once).You can also use "--disable=all" to -# disable everything first and then reenable specific checks. For example, if -# you want to run only the similarities checker, you can use "--disable=all -# --enable=similarities". If you want to run only the classes checker, but have -# no Warning level messages displayed, use"--disable=all --enable=classes -# --disable=W" -disable=cyclic-import, - no-else-return, - too-many-arguments, - too-many-locals, - too-many-branches, - too-many-statements, - too-few-public-methods, - import-outside-toplevel, - bare-except, - consider-using-max-builtin, - consider-using-f-string, - consider-using-from-import, - unneeded-not, - unspecified-encoding, - use-list-literal, - use-dict-literal, - no-member, - unsupported-assignment-operation, - unsubscriptable-object - - -# Enable the message, report, category or checker with the given id(s). You can -# either give multiple identifier separated by comma (,) or put this option -# multiple time (only on the command line, not in the configuration file where -# it should appear only once). See also the "--disable" option for examples. -enable=c-extension-no-member - - -[REPORTS] - -# Python expression which should return a note less than 10 (10 is the highest -# note). You have access to the variables errors warning, statement which -# respectively contain the number of errors / warnings messages and the total -# number of statements analyzed. This is used by the global evaluation report -# (RP0004). -evaluation=10.0 - ((float(5 * error + warning + refactor + convention) / statement) * 10) - -# Template used to display messages. This is a python new-style format string -# used to format the message information. See doc for all details -#msg-template= - -# Set the output format. Available formats are text, parseable, colorized, json -# and msvs (visual studio).You can also give a reporter class, eg -# mypackage.mymodule.MyReporterClass. -output-format=text - -# Tells whether to display a full report or only the messages -reports=no - -# Activate the evaluation score. -score=yes - - -[REFACTORING] - -# Maximum number of nested blocks for function / method body -max-nested-blocks=5 - -# Complete name of functions that never returns. When checking for -# inconsistent-return-statements if a never returning function is called then -# it will be considered as an explicit return statement and no message will be -# printed. -never-returning-functions=optparse.Values,sys.exit - - -[FORMAT] - -# Expected format of line ending, e.g. empty (any line ending), LF or CRLF. -expected-line-ending-format= - -# Regexp for a line that is allowed to be longer than the limit. -ignore-long-lines=^\s*(# )??$ - -# Number of spaces of indent required inside a hanging or continued line. -indent-after-paren=4 - -# String used as indentation unit. This is usually " " (4 spaces) or "\t" (1 -# tab). -indent-string=' ' - -# Maximum number of characters on a single line. -max-line-length=100 - -# Maximum number of lines in a module -max-module-lines=1000 - -# List of optional constructs for which whitespace checking is disabled. `dict- -# separator` is used to allow tabulation in dicts, etc.: {1 : 1,\n222: 2}. -# `trailing-comma` allows a space between comma and closing bracket: (a, ). -# `empty-line` allows space-only lines. -#no-space-check=trailing-comma, -# dict-separator - -# Allow the body of a class to be on the same line as the declaration if body -# contains single statement. -single-line-class-stmt=no - -# Allow the body of an if to be on the same line as the test if there is no -# else. -single-line-if-stmt=no - - -[VARIABLES] - -# List of additional names supposed to be defined in builtins. Remember that -# you should avoid to define new builtins when possible. -additional-builtins= - -# Tells whether unused global variables should be treated as a violation. -allow-global-unused-variables=yes - -# List of strings which can identify a callback function by name. A callback -# name must start or end with one of those strings. -callbacks=cb_, - _cb - -# A regular expression matching the name of dummy variables (i.e. expectedly -# not used). -dummy-variables-rgx=_+$|(_[a-zA-Z0-9_]*[a-zA-Z0-9]+?$)|dummy|^ignored_|^unused_ - -# Argument names that match this expression will be ignored. Default to name -# with leading underscore -ignored-argument-names=_.*|^ignored_|^unused_ - -# Tells whether we should check for unused import in __init__ files. -init-import=no - -# List of qualified module names which can have objects that can redefine -# builtins. -redefining-builtins-modules=six.moves,past.builtins,future.builtins - - -[BASIC] - -# Naming style matching correct argument names -argument-naming-style=snake_case - -# Regular expression matching correct argument names. Overrides argument- -# naming-style -#argument-rgx= - -# Naming style matching correct attribute names -attr-naming-style=snake_case - -# Regular expression matching correct attribute names. Overrides attr-naming- -# style -#attr-rgx= - -# Bad variable names which should always be refused, separated by a comma -bad-names=foo, - bar, - baz, - toto, - tutu, - tata - -# Naming style matching correct class attribute names -class-attribute-naming-style=any - -# Regular expression matching correct class attribute names. Overrides class- -# attribute-naming-style -#class-attribute-rgx= - -# Naming style matching correct class names -class-naming-style=PascalCase - -# Regular expression matching correct class names. Overrides class-naming-style -#class-rgx= - -# Naming style matching correct constant names -const-naming-style=UPPER_CASE - -# Regular expression matching correct constant names. Overrides const-naming- -# style -#const-rgx= - -# Minimum line length for functions/classes that require docstrings, shorter -# ones are exempt. -docstring-min-length=-1 - -# Naming style matching correct function names -function-naming-style=snake_case - -# Regular expression matching correct function names. Overrides function- -# naming-style -#function-rgx= - -# Good variable names which should always be accepted, separated by a comma -good-names=f, - i, - j, - k, - t, - n, - x, - y, - z, - ax, - by, - df, - fo, - kj, - logger, - handler, - holes, - -# Include a hint for the correct naming format with invalid-name -include-naming-hint=no - -# Naming style matching correct inline iteration names -inlinevar-naming-style=any - -# Regular expression matching correct inline iteration names. Overrides -# inlinevar-naming-style -#inlinevar-rgx= - -# Naming style matching correct method names -method-naming-style=snake_case - -# Regular expression matching correct method names. Overrides method-naming- -# style -#method-rgx= - -# Naming style matching correct module names -module-naming-style=snake_case - -# Regular expression matching correct module names. Overrides module-naming- -# style -#module-rgx= - -# Colon-delimited sets of names that determine each other's naming style when -# the name regexes allow several styles. -name-group= - -# Regular expression which should only match function or class names that do -# not require a docstring. -no-docstring-rgx=^_ - -# List of decorators that produce properties, such as abc.abstractproperty. Add -# to this list to register other decorators that produce valid properties. -property-classes=abc.abstractproperty - -# Naming style matching correct variable names -variable-naming-style=snake_case - -# Regular expression matching correct variable names. Overrides variable- -# naming-style -#variable-rgx= - - -[LOGGING] - -# Logging modules to check that the string format arguments are in logging -# function parameter format -logging-modules=logging - - -[SIMILARITIES] - -# Ignore comments when computing similarities. -ignore-comments=yes - -# Ignore docstrings when computing similarities. -ignore-docstrings=yes - -# Ignore imports when computing similarities. -ignore-imports=no - -# Minimum lines number of a similarity. -min-similarity-lines=15 - - -[TYPECHECK] - -# List of decorators that produce context managers, such as -# contextlib.contextmanager. Add to this list to register other decorators that -# produce valid context managers. -contextmanager-decorators=contextlib.contextmanager - -# List of members which are set dynamically and missed by pylint inference -# system, and so shouldn't trigger E1101 when accessed. Python regular -# expressions are accepted. -generated-members= - -# Tells whether missing members accessed in mixin class should be ignored. A -# mixin class is detected if its name ends with "mixin" (case insensitive). -ignore-mixin-members=yes - -# This flag controls whether pylint should warn about no-member and similar -# checks whenever an opaque object is returned when inferring. The inference -# can return multiple potential results while evaluating a Python object, but -# some branches might not be evaluated, which results in partial inference. In -# that case, it might be useful to still emit no-member and other checks for -# the rest of the inferred objects. -ignore-on-opaque-inference=yes - -# List of class names for which member attributes should not be checked (useful -# for classes with dynamically set attributes). This supports the use of -# qualified names. -ignored-classes=optparse.Values,thread._local,_thread._local,netCDF4 - -# List of module names for which member attributes should not be checked -# (useful for modules/projects where namespaces are manipulated during runtime -# and thus existing member attributes cannot be deduced by static analysis. It -# supports qualified module names, as well as Unix pattern matching. -ignored-modules= - -# Show a hint with possible names when a member name was not found. The aspect -# of finding the hint is based on edit distance. -missing-member-hint=yes - -# The minimum edit distance a name should have in order to be considered a -# similar match for a missing member name. -missing-member-hint-distance=1 - -# The total number of similar names that should be taken in consideration when -# showing a hint for a missing member. -missing-member-max-choices=1 - - -[MISCELLANEOUS] - -# List of note tags to take in consideration, separated by a comma. -notes=FIXME, - XXX, - TODO - - -[SPELLING] - -# Limits count of emitted suggestions for spelling mistakes -max-spelling-suggestions=4 - -# Spelling dictionary name. Available dictionaries: none. To make it working -# install python-enchant package. -spelling-dict= - -# List of comma separated words that should not be checked. -spelling-ignore-words= - -# A path to a file that contains private dictionary; one word per line. -spelling-private-dict-file= - -# Tells whether to store unknown words to indicated private dictionary in -# --spelling-private-dict-file option instead of raising a message. -spelling-store-unknown-words=no - - -[CLASSES] - -# List of method names used to declare (i.e. assign) instance attributes. -defining-attr-methods=__init__, - __new__, - setUp - -# List of member names, which should be excluded from the protected access -# warning. -exclude-protected=_asdict, - _fields, - _replace, - _source, - _make - -# List of valid names for the first argument in a class method. -valid-classmethod-first-arg=cls - -# List of valid names for the first argument in a metaclass class method. -valid-metaclass-classmethod-first-arg=mcs - - -[IMPORTS] - -# Allow wildcard imports from modules that define __all__. -allow-wildcard-with-all=no - -# Analyse import fallback blocks. This can be used to support both Python 2 and -# 3 compatible code, which means that the block might have code that exists -# only in one or another interpreter, leading to false positives when analysed. -analyse-fallback-blocks=no - -# Deprecated modules which should not be used, separated by a comma -deprecated-modules=optparse,tkinter.tix - -# Create a graph of external dependencies in the given file (report RP0402 must -# not be disabled) -ext-import-graph= - -# Create a graph of every (i.e. internal and external) dependencies in the -# given file (report RP0402 must not be disabled) -import-graph= - -# Create a graph of internal dependencies in the given file (report RP0402 must -# not be disabled) -int-import-graph= - -# Force import order to recognize a module as part of the standard -# compatibility libraries. -known-standard-library= - -# Force import order to recognize a module as part of a third party library. -known-third-party=enchant - - -[DESIGN] - -# Maximum number of arguments for function / method -max-args=10 - -# Maximum number of attributes for a class (see R0902). -max-attributes=10 - -# Maximum number of boolean expressions in a if statement -max-bool-expr=5 - -# Maximum number of branch for function / method body -max-branches=12 - -# Maximum number of locals for function / method body -max-locals=15 - -# Maximum number of parents for a class (see R0901). -max-parents=7 - -# Maximum number of public methods for a class (see R0904). -max-public-methods=20 - -# Maximum number of return / yield for function / method body -max-returns=6 - -# Maximum number of statements in function / method body -max-statements=50 - -# Minimum number of public methods for a class (see R0903). -min-public-methods=2 - - -[EXCEPTIONS] - -# Exceptions that will emit a warning when being caught. Defaults to -# "Exception" -overgeneral-exceptions=Exception diff --git a/pyinfraformat/__init__.py b/pyinfraformat/__init__.py index 96e5416..5b0a95c 100644 --- a/pyinfraformat/__init__.py +++ b/pyinfraformat/__init__.py @@ -1,10 +1,18 @@ -# pylint: disable=wildcard-import """pyinfraformat, Python library for Finnish infraformat files.""" + import logging -from .core import * -from .exceptions import * -from .plots import * +from .core import ( + Holes, + from_gtk_wfs, + from_infraformat, + identifiers, + print_info, + project_holes, + to_infraformat, +) +from .exceptions import FileExtensionMissingError, PathNotFoundError +from .plots import plot_hole, plot_map logger = logging.getLogger("pyinfraformat") @@ -29,4 +37,16 @@ def log_to_file(filename): __version__ = "23.1.2" -__all__ = ["Holes", "from_infraformat", "to_infraformat", "print_info"] +__all__ = [ + "FileExtensionMissingError", + "Holes", + "PathNotFoundError", + "from_gtk_wfs", + "from_infraformat", + "identifiers", + "plot_hole", + "plot_map", + "print_info", + "project_holes", + "to_infraformat", +] diff --git a/pyinfraformat/core/__init__.py b/pyinfraformat/core/__init__.py index 1340b40..2f3a98c 100644 --- a/pyinfraformat/core/__init__.py +++ b/pyinfraformat/core/__init__.py @@ -1,6 +1,16 @@ -# pylint: disable=wildcard-import """Core pyinfraformat functionality.""" -from .coord_utils import * -from .core import * -from .io import * -from .utils import * + +from .coord_utils import project_holes +from .core import Holes +from .io import from_gtk_wfs, from_infraformat, to_infraformat +from .utils import identifiers, print_info + +__all__ = [ + "Holes", + "from_gtk_wfs", + "from_infraformat", + "identifiers", + "print_info", + "project_holes", + "to_infraformat", +] diff --git a/pyinfraformat/core/coord_utils.py b/pyinfraformat/core/coord_utils.py index 35ee74e..c44843c 100644 --- a/pyinfraformat/core/coord_utils.py +++ b/pyinfraformat/core/coord_utils.py @@ -47,6 +47,10 @@ def coord_str_recognize(input_string): except pyproj.exceptions.CRSError as error: error_string = error.args[0] if "several objects matching this name" not in error_string: + if ("unrecognized format / unknown name" in error_string) and ( + "ETRS" not in input_string + ): + return coord_str_recognize("ETRS " + input_string) raise error_string = error_string[error_string.rfind(": ") + 2 : -1] projections = error_string.split(",") @@ -89,15 +93,16 @@ def flip_xy(holes): hole_copy.header.XY["X"], ) return hole_copy - raise ValueError("Inappropriate argument.") + msg = "Inappropriate argument." + raise ValueError(msg) def proj_espoo(x, y): - """Project Espoo vvj coordinates into ETRS-GK24 (EPSG:3878). + """ + Project Espoo vvj coordinates into ETRS-GK24 (EPSG:3878). - https://www.espoo.fi/fi-FI/Asuminen_ja_ymparisto/Rakentaminen/Kiinteiston_muodostus/Koordinaattiuudistus(19923) # pylint: disable=line-too-long + https://www.espoo.fi/fi-FI/Asuminen_ja_ymparisto/Rakentaminen/Kiinteiston_muodostus/Koordinaattiuudistus(19923) """ - # pylint: disable=invalid-name output_epsg = "EPSG:3878" a = 6599858.007479810200000 b = 24499824.978235636000000 @@ -110,11 +115,11 @@ def proj_espoo(x, y): def proj_helsinki(x, y): - """Project Helsinki coordinates into ETRS-GK25 (EPSG:3879). + """ + Project Helsinki coordinates into ETRS-GK25 (EPSG:3879). - https://www.hel.fi/helsinki/fi/kartat-ja-liikenne/kartat-ja-paikkatieto/paikkatiedot+ja+-aineistot/koordinaatistot_ja+_korkeudet/koordinaatti_ja_korkeusjarjestelmat # pylint: disable=line-too-long + https://www.hel.fi/helsinki/fi/kartat-ja-liikenne/kartat-ja-paikkatieto/paikkatiedot+ja+-aineistot/koordinaatistot_ja+_korkeudet/koordinaatti_ja_korkeusjarjestelmat """ - # pylint: disable=invalid-name output_epsg = "EPSG:3879" a = 6654650.14636 b = 25447166.49457 @@ -127,11 +132,11 @@ def proj_helsinki(x, y): def proj_porvoo(x, y): - """Project Porvoo coordinates into KKJ3 (EPSG:2393). + """ + Project Porvoo coordinates into KKJ3 (EPSG:2393). https://www.porvoo.fi/koordinaatti-ja-korkeusjarjestelma """ - # pylint: disable=invalid-name output_epsg = "EPSG:2393" P = x - 6699461.017 I = y - 427129.490 @@ -145,7 +150,8 @@ def proj_porvoo(x, y): def project_points(x, y, input_system="EPSG:3067", output_system="EPSG:4326"): - """Transform coordinate points from input to output, default output WGS84. + """ + Transform coordinate points from input to output, default output WGS84. Parameters ---------- @@ -160,6 +166,7 @@ def project_points(x, y, input_system="EPSG:3067", output_system="EPSG:4326"): ------- x : list or float y : list or float + """ input_system = str(input_system).upper() if "EPSG" in input_system: @@ -167,32 +174,27 @@ def project_points(x, y, input_system="EPSG:3067", output_system="EPSG:4326"): else: input_epsg = coord_str_recognize(input_system) if "unrecognized format" in input_epsg.lower(): - raise ValueError( - "Unrecognized format / unknown name for input_system parameter {}".format( - input_epsg - ) - ) + msg = f"Unrecognized format / unknown name for input_system parameter {input_epsg}" + raise ValueError(msg) output_system = str(output_system).upper() if "EPSG" in output_system: output_epsg = output_system else: output_epsg = coord_str_recognize(output_system) if "unrecognized format" in output_epsg.lower(): - raise ValueError( - "Unrecognized format / unknown name for output_system parameter {}".format( - input_epsg - ) - ) + msg = f"Unrecognized format / unknown name for output_system parameter {input_epsg}" + raise ValueError(msg) if input_epsg == output_epsg: return x, y transf = get_transformer(input_epsg, output_epsg) - y, x = transf.transform(y, x) # pylint: disable=unpacking-non-sequence + y, x = transf.transform(y, x) return x, y def check_hole_inside_bbox(hole, bbox): - """Check if hole point is inside bbox. + """ + Check if hole point is inside bbox. Returns boolean. """ @@ -203,7 +205,8 @@ def check_hole_inside_bbox(hole, bbox): def check_hole_in_country(holes, country="FI"): - """Check if holes or hole points are inside country bbox. Has to be in some system with EPSG. + """ + Check if holes or hole points are inside country bbox. Has to be in some system with EPSG. Returns boolean. """ @@ -212,32 +215,34 @@ def check_hole_in_country(holes, country="FI"): return True input_str = holes[0].fileheader.KJ["Coordinate system"] if not all(hole.fileheader.KJ["Coordinate system"] == input_str for hole in holes): - raise ValueError("Input not in uniform coordinate system") + msg = "Input not in uniform coordinate system" + raise ValueError(msg) elif isinstance(holes, Hole): input_str = holes.fileheader.KJ["Coordinate system"] else: - raise ValueError("holes -parameter is unknown input type") + msg = "holes -parameter is unknown input type" + raise ValueError(msg) input_str = coord_str_recognize(input_str) bbox = COUNTRY_BBOX[country][1].copy() if input_str != "EPSG:4326": transf = get_transformer("EPSG:4326", input_str) - # pylint: disable=unpacking-non-sequence + bbox[0], bbox[1] = transf.transform(bbox[0], bbox[1]) - # pylint: disable=unpacking-non-sequence + bbox[2], bbox[3] = transf.transform(bbox[2], bbox[3]) else: - raise ValueError("Input has to be in known epsg system.", input_str) + msg = "Input has to be in known epsg system." + raise ValueError(msg, input_str) if isinstance(holes, Holes): return all(check_hole_inside_bbox(hole, bbox) for hole in holes) - else: - return check_hole_inside_bbox(holes, bbox) + return check_hole_inside_bbox(holes, bbox) def get_n43_n60_points(): """Get MML reference points for height systems n43-n60.""" - url = r"https://www.maanmittauslaitos.fi/sites/maanmittauslaitos.fi/files/attachments/2019/02/n43n60triangulationVertices.txt" # pylint: disable=line-too-long + url = r"https://www.maanmittauslaitos.fi/sites/maanmittauslaitos.fi/files/attachments/2019/02/n43n60triangulationVertices.txt" df = pd.read_csv(url, delim_whitespace=True, header=None) df.columns = ["Point", "X", "Y", "diff", "Karttalehden numero"] df.index = df.iloc[:, 0] @@ -248,9 +253,8 @@ def get_n43_n60_points(): def get_n43_n60_triangulation(): """Get MML triangles for height systems n43-n60.""" - url = r"https://www.maanmittauslaitos.fi/sites/maanmittauslaitos.fi/files/attachments/2019/02/n43n60triangulationNetwork.txt" # pylint: disable=line-too-long - df = pd.read_csv(url, delim_whitespace=True, header=None) - return df + url = r"https://www.maanmittauslaitos.fi/sites/maanmittauslaitos.fi/files/attachments/2019/02/n43n60triangulationNetwork.txt" + return pd.read_csv(url, delim_whitespace=True, header=None) def get_n43_n60_interpolator(): @@ -261,26 +265,23 @@ def get_n43_n60_interpolator(): df_points.index.values, np.arange(df_points.shape[0], dtype=int) ).values interpolator_ = Triangulation(df_points["X"], df_points["Y"], triangles) - interpolator = LinearTriInterpolator(interpolator_, df_points["diff"]) - return interpolator + return LinearTriInterpolator(interpolator_, df_points["diff"]) def get_n60_n2000_points(): """Get MML reference points for height systems n60-n2000.""" - url = r"https://www.maanmittauslaitos.fi/sites/maanmittauslaitos.fi/files/attachments/2019/02/n60n2000triangulationVertices.txt" # pylint: disable=line-too-long + url = r"https://www.maanmittauslaitos.fi/sites/maanmittauslaitos.fi/files/attachments/2019/02/n60n2000triangulationVertices.txt" df = pd.read_csv(url, encoding="latin-1", delim_whitespace=True, header=None) df.columns = ["Point", "X", "Y", "N60", "N2000"] df.index = df.iloc[:, 0] df["diff"] = df["N2000"] - df["N60"] - df = df.loc[:, ["X", "Y", "diff"]] - return df + return df.loc[:, ["X", "Y", "diff"]] def get_n60_n2000_triangulation(): """Get MML triangles for height systems n60-n2000.""" - url = r"https://www.maanmittauslaitos.fi/sites/maanmittauslaitos.fi/files/attachments/2019/02/n60n2000triangulationNetwork.txt" # pylint: disable=line-too-long - df = pd.read_csv(url, encoding="latin-1", delim_whitespace=True, header=None) - return df + url = r"https://www.maanmittauslaitos.fi/sites/maanmittauslaitos.fi/files/attachments/2019/02/n60n2000triangulationNetwork.txt" + return pd.read_csv(url, encoding="latin-1", delim_whitespace=True, header=None) def get_n60_n2000_interpolator(): @@ -291,12 +292,12 @@ def get_n60_n2000_interpolator(): df_points.index.values, np.arange(df_points.shape[0], dtype=int) ).values interpolator_ = Triangulation(df_points["X"], df_points["Y"], triangles) - interpolator = LinearTriInterpolator(interpolator_, df_points["diff"]) - return interpolator + return LinearTriInterpolator(interpolator_, df_points["diff"]) def height_systems_diff(points, input_system, output_system): - """Get difference between systems at certain point. + """ + Get difference between systems at certain point. Calculates the difference for height systems at points based on MML reference points and triangulation. Coordinate system is KKJ3 (EPSG:2393). @@ -314,12 +315,14 @@ def height_systems_diff(points, input_system, output_system): ------- ndarray list of height differences (mm). Nan for points outside triangulation. + """ points = np.asarray(points) if len(points.shape) == 1 and len(points) == 2: points = points[None, :] if points.shape[1] != 2: - raise ValueError("Points has to be 2D -array with shape=(n, 2)") + msg = "Points has to be 2D -array with shape=(n, 2)" + raise ValueError(msg) input_system = input_system.upper() output_system = output_system.upper() if input_system == output_system: @@ -328,7 +331,7 @@ def height_systems_diff(points, input_system, output_system): diff = height_systems_diff(points, "N43", "N60") diff += height_systems_diff(points, "N60", "N2000") return diff - elif output_system == "N43" and input_system == "N2000": + if output_system == "N43" and input_system == "N2000": diff = -height_systems_diff(points, "N43", "N60") diff -= height_systems_diff(points, "N60", "N2000") return diff @@ -354,17 +357,17 @@ def height_systems_diff(points, input_system, output_system): INTERPOLATORS[key] = get_n60_n2000_interpolator() diff = -INTERPOLATORS[key](*points.T).data else: - raise ValueError( - ( - "input_system ({}) or output_system ({}) invalid." - " Possible values are N43, N60, N2000." - ).format(input_system, output_system) + msg = ( + f"input_system ({input_system}) or output_system ({output_system}) invalid." + " Possible values are N43, N60, N2000." ) + raise ValueError(msg) return -diff def project_hole(hole, output_epsg="EPSG:4326", output_height=False): - """Transform hole -objects coordinates. + """ + Transform hole -objects coordinates. Parameters ---------- @@ -379,14 +382,17 @@ def project_hole(hole, output_epsg="EPSG:4326", output_height=False): ------- hole : Hole -object Copy of hole with coordinates transformed + """ hole_copy = deepcopy(hole) if not isinstance(hole, Hole): - raise ValueError("hole -parameter invalid") + msg = "hole -parameter invalid" + raise ValueError(msg) output_epsg = coord_str_recognize(output_epsg) if "unrecognized format" in output_epsg.lower(): - raise ValueError("Unrecognized format / unknown name as output_epsg") + msg = "Unrecognized format / unknown name as output_epsg" + raise ValueError(msg) if ( hasattr(hole_copy, "fileheader") and hasattr(hole_copy.fileheader, "KJ") @@ -397,7 +403,8 @@ def project_hole(hole, output_epsg="EPSG:4326", output_height=False): input_str = coord_str_recognize(input_str) else: - raise ValueError("Hole has no coordinate system") + msg = "Hole has no coordinate system" + raise ValueError(msg) if ( hasattr(hole_copy.header, "XY") @@ -406,18 +413,21 @@ def project_hole(hole, output_epsg="EPSG:4326", output_height=False): ): x, y = hole_copy.header.XY["X"], hole_copy.header.XY["Y"] if not np.isfinite(x) or not np.isfinite(y): - raise ValueError("Coordinates are not finite") + msg = "Coordinates are not finite" + raise ValueError(msg) else: - raise ValueError("Hole has no coordinates") + msg = "Hole has no coordinates" + raise ValueError(msg) if input_str in LOCAL_SYSTEMS: func = LOCAL_SYSTEMS[input_str] x, y, input_str = func(x, y) elif "unrecognized format" in input_str.lower(): - raise ValueError("Unrecognized format / unknown name EPSG in holes {}".format(input_str)) + msg = f"Unrecognized format / unknown name EPSG in holes {input_str}" + raise ValueError(msg) transf = get_transformer(input_str, output_epsg) - y, x = transf.transform(y, x) # pylint: disable=unpacking-non-sequence + y, x = transf.transform(y, x) hole_copy.header.XY["X"], hole_copy.header.XY["Y"] = x, y hole_copy.fileheader.KJ["Coordinate system"] = output_epsg @@ -431,10 +441,12 @@ def project_hole(hole, output_epsg="EPSG:4326", output_height=False): try: input_system = hole.fileheader["KJ"]["Height reference"] except KeyError as error: - raise ValueError("Hole has no height system") from error + msg = "Hole has no height system" + raise ValueError(msg) from error - if input_system not in ["N43", "N60", "N2000"]: - raise ValueError("Hole has unknown heigth system:", input_system) + if input_system not in {"N43", "N60", "N2000"}: + msg = "Hole has unknown heigth system:" + raise ValueError(msg, input_system) diff = height_systems_diff(point, input_system, output_height) hole_copy.header.XY["Z-start"] += round(float(diff), 3) @@ -443,7 +455,8 @@ def project_hole(hole, output_epsg="EPSG:4326", output_height=False): def project_holes(holes, output="EPSG:4326", check="Finland", output_height=False): - """Transform holes -objects coordinates. + """ + Transform holes -objects coordinates. Transform holes coordinates, drops invalid holes. @@ -468,13 +481,13 @@ def project_holes(holes, output="EPSG:4326", check="Finland", output_height=Fals -------- holes_gk25 = project_holes(holes, output_epsg="EPSG:3879", check="Finland") one_hole_gk24 = project_holes(one_hole, output_epsg="EPSG:3878", check="Estonia") + """ output = str(output).upper() output_epsg = coord_str_recognize(output) if "unknown" in output_epsg.lower(): - raise ValueError( - "Unrecognized format / unknown name for output parameter {}".format(output) - ) + msg = f"Unrecognized format / unknown name for output parameter {output}" + raise ValueError(msg) if isinstance(holes, Holes): proj_holes = [] for hole in holes: @@ -498,7 +511,8 @@ def project_holes(holes, output="EPSG:4326", check="Finland", output_height=Fals hole = deepcopy(holes) return_value = project_hole(hole, output_epsg, output_height) else: - raise ValueError("holes -parameter is unknown input type") + msg = "holes -parameter is unknown input type" + raise ValueError(msg) if check and check.upper() == "FINLAND": if not check_hole_in_country(return_value, "FI"): diff --git a/pyinfraformat/core/core.py b/pyinfraformat/core/core.py index af0cd0f..d340885 100644 --- a/pyinfraformat/core/core.py +++ b/pyinfraformat/core/core.py @@ -7,11 +7,12 @@ from datetime import datetime from functools import cached_property from numbers import Integral +from typing import Never import numpy as np import pandas as pd -from ..exceptions import FileExtensionMissingError +from pyinfraformat.exceptions import FileExtensionMissingError logger = logging.getLogger("pyinfraformat") @@ -22,7 +23,8 @@ class Holes: """Container for multiple infraformat hole information.""" def __init__(self, holes=None, lowmemory=False): - """Container for multiple infraformat hole information. + """ + Container for multiple infraformat hole information. Parameters ---------- @@ -30,6 +32,7 @@ def __init__(self, holes=None, lowmemory=False): list of infraformat hole information lowmemory : bool, optional Create Pandas DataFrame one by one minimizing memory use. + """ if holes is None: holes = [] @@ -38,18 +41,18 @@ def __init__(self, holes=None, lowmemory=False): self.n = None def __str__(self): - msg = "Infraformat Holes -object:\n Total of {n} holes".format(n=len(self.holes)) + msg = f"Infraformat Holes -object:\n Total of {len(self.holes)} holes" value_counts = self.value_counts() if self.holes: max_length = max(len(str(values)) for values in value_counts.values()) + 1 counts = "\n".join( " - {key:.<10}{value:.>6}".format( - key="{} ".format(key), - value=("{:>" + "{}".format(max_length) + "}").format(value), + key=f"{key} ", + value=("{:>" + f"{max_length}" + "}").format(value), ) for key, value in value_counts.items() ) - msg = "\n".join((msg, counts)) + msg = f"{msg}\n{counts}" return msg def __repr__(self): @@ -69,35 +72,38 @@ def __next__(self): result = self.holes[self.n] self.n += 1 return result - else: - raise StopIteration + raise StopIteration def __add__(self, other): if isinstance(other, Holes): return Holes(self.holes + other.holes) if isinstance(other, Hole): - return Holes(self.holes + [other]) - raise ValueError("Only Holes or Hole -objects can be added.") + return Holes([*self.holes, other]) + msg = "Only Holes or Hole -objects can be added." + raise ValueError(msg) def __len__(self): return len(self.holes) - def append(self, hole): + def append(self, hole) -> None: """Append Hole object to holes.""" if isinstance(hole, Hole): self.holes += [hole] else: - raise ValueError("Only Hole -object can be appended.") + msg = "Only Hole -object can be appended." + raise ValueError(msg) - def extend(self, holes): + def extend(self, holes) -> None: """Extend with Holes -object.""" if isinstance(holes, Holes): self.holes += holes else: - raise ValueError("Only Holes -object can be extended.") + msg = "Only Holes -object can be extended." + raise ValueError(msg) def filter_holes(self, *, bbox=None, hole_type=None, start=None, end=None, fmt=None, **kwargs): - """Filter holes. + """ + Filter holes. Parameters ---------- @@ -142,6 +148,7 @@ def filter_holes(self, *, bbox=None, hole_type=None, start=None, end=None, fmt=N _filter_coordinates(bbox, **kwargs) _filter_type(hole_type, **kwargs) _filter_date(start=None, end=None, fmt=None, **kwargs) + """ filtered_holes = self.holes if bbox is not None: @@ -157,11 +164,7 @@ def _filter_coordinates(self, holes, bbox): xmin, xmax, ymin, ymax = bbox filtered_holes = [] for hole in holes: - if not ( - hasattr(hole.header, "XY") - and "X" in hole.header.XY.keys() - and "Y" in hole.header.XY.keys() - ): + if not (hasattr(hole.header, "XY") and "X" in hole.header.XY and "Y" in hole.header.XY): continue if ( hole.header.XY["X"] >= xmin @@ -174,16 +177,15 @@ def _filter_coordinates(self, holes, bbox): def _filter_type(self, holes, hole_type): """Filter object by survey abbreviation (type).""" - filtered_holes = [] if isinstance(hole_type, str): hole_type = [hole_type] - for hole in holes: - if ( - hasattr(hole.header, "TT") - and ("Survey abbreviation" in hole.header.TT) - and any(item == hole.header.TT["Survey abbreviation"] for item in hole_type) - ): - filtered_holes.append(hole) + filtered_holes = [ + hole + for hole in holes + if hasattr(hole.header, "TT") + and ("Survey abbreviation" in hole.header.TT) + and any(item == hole.header.TT["Survey abbreviation"] for item in hole_type) + ] return Holes(filtered_holes) def _filter_date(self, holes, start=None, end=None, fmt=None): @@ -226,7 +228,7 @@ def value_counts(self): counts["Missing survey abbreviation"] += 1 return counts - def drop_duplicates(self): + def drop_duplicates(self) -> Never: """Check if hole headers/datas are unique; drop duplicates.""" raise NotImplementedError @@ -260,8 +262,9 @@ def bounds(self): """Get bounding box of holes object as array.""" return self._get_bounds() - def to_csv(self, path, **kwargs): - """Save data in table format to CSV. + def to_csv(self, path, **kwargs) -> None: + """ + Save data in table format to CSV. Paramaters ---------- @@ -270,15 +273,16 @@ def to_csv(self, path, **kwargs): Passed to `pandas.DataFrame.to_csv` function. """ _, ext = os.path.splitext(path) - if ext not in (".txt", ".csv"): - msg = "Found extension {}, use '.csv' or '.txt'.".format(path) + if ext not in {".txt", ".csv"}: + msg = f"Found extension {path}, use '.csv' or '.txt'." logger.critical(msg) raise FileExtensionMissingError(msg) - with open(path, "w") as f: + with open(path, "w", encoding="utf-8") as f: self.get_dataframe().to_csv(f, **kwargs) - def to_excel(self, path, **kwargs): - """Save data in table format to Excel. + def to_excel(self, path, **kwargs) -> None: + """ + Save data in table format to Excel. Paramaters ---------- @@ -287,15 +291,16 @@ def to_excel(self, path, **kwargs): Passed to `pandas.DataFrame.to_csv` function. """ _, ext = os.path.splitext(path) - if ext not in (".xlsx", ".xls"): - msg = "ext not in ('.xlsx', '.xls'): {}".format(path) + if ext not in {".xlsx", ".xls"}: + msg = f"ext not in ('.xlsx', '.xls'): {path}" logger.critical(msg) raise FileExtensionMissingError(msg) - with pd.ExcelWriter(path) as writer: # pylint: disable=abstract-class-instantiated + with pd.ExcelWriter(path) as writer: self.get_dataframe().to_excel(writer, **kwargs) - def to_infraformat(self, path, split=False, namelist=None): - """Save data in infraformat. + def to_infraformat(self, path, split=False, namelist=None) -> None: + """ + Save data in infraformat. Parameters ---------- @@ -306,13 +311,14 @@ def to_infraformat(self, path, split=False, namelist=None): namelist : list, optional filenames for each file valid only if split is True + """ - from .io import to_infraformat # pylint: disable=cyclic-import + from .io import to_infraformat if split: use_format = False if namelist is None: - if not "{}" in path: + if "{}" not in path: msg = "Use either a {} or a namelist for filenames" logger.critical(msg) raise ValueError(msg) @@ -320,16 +326,14 @@ def to_infraformat(self, path, split=False, namelist=None): else: assert len(namelist) == len(self.holes) for i, hole in self.holes: - if use_format: - path_ = path.format(i) - else: - path_ = namelist[i] + path_ = path.format(i) if use_format else namelist[i] to_infraformat([hole], path_) else: to_infraformat(self.holes, path) def plot_map(self, render_holes=True, progress_bar=True, popup_size=(3, 3)): - """Plot a leaflet map from holes with popup hole plots. + """ + Plot a leaflet map from holes with popup hole plots. Parameters ---------- @@ -344,13 +348,15 @@ def plot_map(self, render_holes=True, progress_bar=True, popup_size=(3, 3)): Returns ------- map_fig : folium map object + """ - from ..plots.maps import plot_map as _plot_map + from pyinfraformat.plots.maps import plot_map as _plot_map return _plot_map(self, render_holes, progress_bar, popup_size) def project(self, output="EPSG:4326", check="Finland", output_height=False): - """Transform holes -objects coordinates. + """ + Transform holes -objects coordinates. Creates deepcopy of holes and drops invalid holes. Warns into logger. @@ -374,8 +380,9 @@ def project(self, output="EPSG:4326", check="Finland", output_height=False): Examples -------- holes_gk25 = holes.project(output="GK25", check="Finland", output_height="N2000") + """ - from ..core.coord_utils import project_holes + from pyinfraformat.core.coord_utils import project_holes return project_holes(self, output, check, output_height) @@ -383,9 +390,11 @@ def get_endings(self, check_height=True): """Get dataframe with every holes last data row with soil observation.""" if check_height: if len({hole.fileheader.KJ["Height reference"] for hole in self}) != 1: - raise ValueError("Holes must have uniform height reference.") - if any((hole.fileheader.KJ["Height reference"] in {"?", "Unknown"} for hole in self)): - raise ValueError("Unknown height reference system.") + msg = "Holes must have uniform height reference." + raise ValueError(msg) + if any(hole.fileheader.KJ["Height reference"] in {"?", "Unknown"} for hole in self): + msg = "Unknown height reference system." + raise ValueError(msg) soil_observations = [] for hole in self: if not ( @@ -419,6 +428,7 @@ def get_endings(self, check_height=True): logger.warning( "Hole ending 'KA' without any 'KA' soil observations, omitting." ) + continue else: slicer = df["data_Soil type"].str.upper() == "KA" slicer = slicer.shift(-1, fill_value=False) @@ -426,7 +436,7 @@ def get_endings(self, check_height=True): soil_observations.append( (point_x, point_y, z_start, z_start - soil_depth, abbrev, ending) ) - elif abbrev in ["NO", "NE"]: + elif abbrev in {"NO", "NE"}: if "Depth info 1 (m)" not in hole.survey.data[-1]: continue soil_depth = max( @@ -439,7 +449,7 @@ def get_endings(self, check_height=True): (point_x, point_y, z_start, z_start - soil_depth, abbrev, ending) ) - elif abbrev in ["KE", "KR"]: + elif abbrev in {"KE", "KR"}: # Bedrock analysis drillings pass else: @@ -454,10 +464,7 @@ def get_endings(self, check_height=True): def get_list(self): """Get survey data, hole header, fileheader, comments and illegals as list of dicts.""" - return_list = [] - for hole in self: - return_list.append(hole.get_dict()) - return return_list + return [hole.get_dict() for hole in self] def _get_columns(self): """Get survey data, header and fileheader columns.""" @@ -472,7 +479,8 @@ def columns(self): return self._get_columns() def get_dataframe(self, include_columns="all", skip_columns=False): - """Get survey data, hole header and fileheader as DataFrame. + """ + Get survey data, hole header and fileheader as DataFrame. Paramaters ---------- @@ -489,6 +497,7 @@ def get_dataframe(self, include_columns="all", skip_columns=False): -------- >>> holes.get_dataframe(include_columns="*laboratory*") >>> holes.get_dataframe(skip_columns=["*lab*", "*sieve*", "*header*"]) + """ df_list = [hole.get_dataframe(include_columns, skip_columns) for hole in self.holes] return pd.concat(df_list, axis=0, sort=False) @@ -532,41 +541,38 @@ def print_raw(self, linenumbers=True, illegals=True): print(f"{linenumber}:\t", illegals_dict[linenumber]) else: print(illegals_dict[linenumber]) + elif linenumbers: + print(f"{linenumber}:\t", line) else: - if linenumbers: - print(f"{linenumber}:\t", line) - else: - print(line) + print(line) if hasattr(self.header, "-1"): ending = self.header["-1"].get("Ending", None) if ending: if linenumbers: - print(f"{linenumber+1}:\t", f"-1 {ending}") + print(f"{linenumber + 1}:\t", f"-1 {ending}") else: print(f"-1 {ending}") def __str__(self): - items = {i: j for i, j in self.header.__dict__.items() if i not in {"keys"}} + items = {i: j for i, j in self.header.__dict__.items() if i != "keys"} msg = pprint.pformat(items).replace("\n", "\n ") - return "Infraformat Hole -object, hole header:\n {}".format(msg) + return f"Infraformat Hole -object, hole header:\n {msg}" def __repr__(self): return self.__str__() def __add__(self, other): if isinstance(other, Holes): - return Holes([self] + other.holes) + return Holes([self, *other.holes]) if isinstance(other, Hole): - return Holes([self] + [other]) - raise ValueError("Only Holes or Hole -objects can be added.") + return Holes([self, other]) + msg = "Only Holes or Hole -objects can be added." + raise ValueError(msg) def __getitem__(self, input_key): keys = input_key.split("_") - if keys[0] == "data": - item = self.survey - else: - item = self + item = self.survey if keys[0] == "data" else self for key in keys[:-1]: item = getattr(item, key) if isinstance(item, list): @@ -583,9 +589,8 @@ def get(self, key, default=None): def __setitem__(self, input_key, input_item): keys = input_key.split("_") if keys[0] == "data": - raise TypeError( - "Hole object does not support item assignment into survey, see .survey." - ) + msg = "Hole object does not support item assignment into survey, see .survey." + raise TypeError(msg) item = self for key in keys[:-1]: item = getattr(item, key) @@ -612,7 +617,8 @@ def _add_illegal(self, illegal): self._illegal.add(illegal) def plot(self, output="figure", figsize=(4, 4)): - """Plot a diagram of a sounding with matplotlib. + """ + Plot a diagram of a sounding with matplotlib. Parameters ---------- @@ -624,8 +630,9 @@ def plot(self, output="figure", figsize=(4, 4)): Returns ------- figure : matplotlib figure or svg + """ - from ..plots.holes import plot_hole + from pyinfraformat.plots.holes import plot_hole return plot_hole(self, output, figsize) @@ -634,31 +641,28 @@ def get_header_dict(self): d_header = {} for key in self.header.keys: for key_, item in getattr(self.header, key).items(): - d_header["{}_{}".format(key, key_)] = item + d_header[f"{key}_{key_}"] = item return d_header def get_data_list(self): """Get survey data as a list of dict.""" - dict_list = self.survey.data - return dict_list + return self.survey.data def get_fileheader_dict(self): """Get fileheader as a dict.""" d_fileheader = {} for key in self.fileheader.keys: for key_, item in getattr(self.fileheader, key).items(): - d_fileheader["{}_{}".format(key, key_)] = item + d_fileheader[f"{key}_{key_}"] = item return d_fileheader def get_comments_list(self): """Get fileheader as a dict.""" - dict_list = self.inline_comment.data - return dict_list + return self.inline_comment.data def get_illegals_list(self): """Get fileheader as a dict.""" - dict_list = self._illegal.data - return dict_list + return self._illegal.data def get_dict(self): """Get survey data, hole header, fileheader, comments and illegals as list of dicts.""" @@ -677,16 +681,14 @@ def get_dict(self): def _get_columns(self): """Get survey data, header and fileheader columns.""" - data = set("data_" + item for row in self.survey.data for item in row) + data = {"data_" + item for row in self.survey.data for item in row} fileheader = { f"fileheader_{key}_{item}" for key in self.fileheader.keys - for item in getattr(self.fileheader, key).keys() + for item in getattr(self.fileheader, key) } header = { - f"header_{key}_{item}" - for key in self.header.keys - for item in getattr(self.header, key).keys() + f"header_{key}_{item}" for key in self.header.keys for item in getattr(self.header, key) } return data.union(fileheader).union(header) @@ -696,7 +698,8 @@ def columns(self): return self._get_columns() def get_dataframe(self, include_columns="all", skip_columns=False): - """Get survey data, hole header and fileheader as DataFrame. + """ + Get survey data, hole header and fileheader as DataFrame. Paramaters ---------- @@ -713,6 +716,7 @@ def get_dataframe(self, include_columns="all", skip_columns=False): -------- >>> hole.get_dataframe(include_columns="*laboratory*") >>> hole.get_dataframe(skip_columns=["*lab*", "*sieve*", "*header*"]) + """ if skip_columns: skip_columns = ( @@ -729,7 +733,8 @@ def get_dataframe(self, include_columns="all", skip_columns=False): elif isinstance(include_columns, str): include_columns = [include_columns] else: - raise ValueError("Parameter include_columns must be list or str 'all'.") + msg = "Parameter include_columns must be list or str 'all'." + raise ValueError(msg) if include_columns != "all": columns = self.columns include_slicer = [False] * len(columns) @@ -738,7 +743,7 @@ def get_dataframe(self, include_columns="all", skip_columns=False): if fnmatch.fnmatch(col.lower(), include_item.lower()): include_slicer[index] = True break - for boolen, col in zip(include_slicer, columns): + for boolen, col in zip(include_slicer, columns, strict=False): if boolen is False: skip_columns.append(col) @@ -749,9 +754,9 @@ def get_dataframe(self, include_columns="all", skip_columns=False): for row in skip_columns: for data in dict_list: keys = data.keys() - for key in keys: - if fnmatch.fnmatch(key.lower(), row.lower()): - skip_data.append(key) + skip_data.extend( + key for key in keys if fnmatch.fnmatch(key.lower(), row.lower()) + ) dict_list = [ {item: row[item] for item in row if item not in skip_data} for row in dict_list ] @@ -762,7 +767,7 @@ def get_dataframe(self, include_columns="all", skip_columns=False): d_header = {"header_" + key: item for key, item in d_header.items()} for key in d_header: if skip_columns and any( - (fnmatch.fnmatch(key.lower(), item.lower()) for item in skip_columns) + fnmatch.fnmatch(key.lower(), item.lower()) for item in skip_columns ): continue df[key] = d_header[key] @@ -771,7 +776,7 @@ def get_dataframe(self, include_columns="all", skip_columns=False): d_fileheader = {"fileheader_" + key: item for key, item in d_fileheader.items()} for key in d_fileheader: if skip_columns and any( - (fnmatch.fnmatch(key.lower(), item.lower()) for item in skip_columns) + fnmatch.fnmatch(key.lower(), item.lower()) for item in skip_columns ): continue df[key] = d_fileheader[key] @@ -794,10 +799,10 @@ def add(self, key, values): def __str__(self): items = {i: j for i, j in self.__dict__.items() if i not in {"keys", "raw_str", "illegals"}} - msg = "FileHeader object - Fileheader contains {} items\n {}".format( - len(self.keys), pprint.pformat(items) + return ( + f"FileHeader object - Fileheader contains {len(self.keys)} items" + f"\n {pprint.pformat(items)}" ) - return msg def __repr__(self): return self.__str__() @@ -805,8 +810,8 @@ def __repr__(self): def __getitem__(self, attr): if attr in self.keys: return getattr(self, attr) - else: - raise TypeError("Attribute not found: {}".format(attr)) + msg = f"Attribute not found: {attr}" + raise TypeError(msg) class Header: @@ -835,9 +840,9 @@ def add(self, key, values): self.keys.add(key) def __str__(self): - items = {i: j for i, j in self.__dict__.items() if i not in {"keys"}} + items = {i: j for i, j in self.__dict__.items() if i != "keys"} msg = pprint.pformat(items) - return "Hole Header -object:\n {}".format(msg) + return f"Hole Header -object:\n {msg}" def __repr__(self): return self.__str__() @@ -845,24 +850,24 @@ def __repr__(self): def __getitem__(self, attr): if attr in self.keys: return getattr(self, attr) - else: - raise TypeError("Attribute not found: {}".format(attr)) + msg = f"Attribute not found: {attr}" + raise TypeError(msg) class InlineComment: """Class to inline comments.""" def __init__(self): - self.data = list() + self.data = [] def add(self, key, values): """Add inline comments to object.""" self.data.append((key, values)) def __str__(self): - items = {i: j for i, j in self.__dict__.items() if i not in {"keys"}} + items = {i: j for i, j in self.__dict__.items() if i != "keys"} msg = pprint.pformat(items) - return "Hole InlineComment -object:\n {}".format(msg) + return f"Hole InlineComment -object:\n {msg}" def __repr__(self): return self.__str__() @@ -875,16 +880,15 @@ class Survey: """Class to survey information.""" def __init__(self): - self.data = list() + self.data = [] def add(self, values): """Add survey information to object.""" self.data.append(values) def __str__(self): - msg = pprint.pformat(self.__dict__) - return "Hole Survey -object:\n {}".format(msg) + return f"Hole Survey -object:\n {msg}" def __repr__(self): return self.__str__() diff --git a/pyinfraformat/core/io.py b/pyinfraformat/core/io.py index 2c4b320..9f0f300 100644 --- a/pyinfraformat/core/io.py +++ b/pyinfraformat/core/io.py @@ -1,6 +1,5 @@ -# pylint: disable=try-except-raise """Input and output methods.""" -import io + import json import logging import os @@ -15,7 +14,8 @@ from owslib.wfs import WebFeatureService from tqdm.auto import tqdm -from ..exceptions import PathNotFoundError +from pyinfraformat.exceptions import PathNotFoundError + from .coord_utils import project_points from .core import Hole, Holes from .utils import identifiers, is_nan, is_number @@ -23,16 +23,16 @@ logger = logging.getLogger("pyinfraformat") logger.propagate = False -__all__ = ["from_infraformat", "from_gtk_wfs"] +__all__ = ["from_gtk_wfs", "from_infraformat", "to_infraformat"] TIMEOUT = 36_000 -# pylint: disable=redefined-argument-from-local def from_infraformat( path=None, encoding="auto", extension=None, errors="ignore_lines", save_ignored=False ): - """Read inframodel file(s). + """ + Read inframodel file(s). Paramaters ---------- @@ -54,6 +54,7 @@ def from_infraformat( Returns ------- Holes -object + """ if path is None or not path: return Holes() @@ -67,12 +68,13 @@ def from_infraformat( filelist = glob(os.path.join(path, "*")) else: if not extension.startswith("."): - extension = ".{}".format(extension) - filelist = glob(os.path.join(path, "*{}".format(extension))) + extension = f".{extension}" + filelist = glob(os.path.join(path, f"*{extension}")) else: filelist = glob(path) if not filelist: - raise PathNotFoundError("{}".format(path)) + msg = f"{path}" + raise PathNotFoundError(msg) else: filelist = [path] @@ -87,7 +89,8 @@ def from_infraformat( def from_gtk_wfs( bbox, coord_system, errors="ignore_lines", save_ignored=False, maxholes=1000, progress_bar=False ): - """Get holes from GTK WFS. + """ + Get holes from GTK WFS. Paramaters ---------- @@ -115,14 +118,14 @@ def from_gtk_wfs( -------- bbox = (60, 24, 61, 25) holes = from_gtk_wfs(bbox, coord_system="EPSG:4326") + """ - # pylint: disable=invalid-name if errors not in {"ignore_lines", "ignore_holes", "raise", "force"}: msg = "Argument errors must be 'ignore_lines', 'ignore_holes', 'raise' or 'force'." raise ValueError(msg) collected_illegals = [] - url = "http://gtkdata.gtk.fi/arcgis/services/Rajapinnat/GTK_Pohjatutkimukset_WFS/MapServer/WFSServer?" # pylint: disable=line-too-long + url = "https://gtkdata.gtk.fi/arcgis/services/Rajapinnat/GTK_Pohjatutkimukset_WFS/MapServer/WFSServer?" wfs = WebFeatureService(url, version="2.0.0") x1, y1 = project_points(bbox[0], bbox[1], coord_system, "EPSG:4326") @@ -150,7 +153,6 @@ def from_gtk_wfs( pbar = tqdm(total=min([holes_found, maxholes])) def parse_line(line): - if ("properties" in line) and ("ALKUPERAINEN_DATA" in line["properties"]): hole_str = line["properties"]["ALKUPERAINEN_DATA"].replace("\\r", "\n").splitlines() if errors == "ignore_holes": @@ -180,17 +182,18 @@ def parse_line(line): msg = f"Line {linenumber}: {line_highlighted} # {error_txt}" logger.warning(msg) if errors == "raise" and illegal_rows: - raise ValueError("Illegal/Il-defined hole detected!") + msg = "Illegal/Il-defined hole detected!" + raise ValueError(msg) else: hole = Hole() - hole.add_header("OM", {"Owner": line.get("properties", dict()).get("OMISTAJA", "-")}) + hole.add_header("OM", {"Owner": line.get("properties", {}).get("OMISTAJA", "-")}) x, y = line["geometry"]["coordinates"] x, y = round(float(y), 10), round(float(x), 10) if not hasattr(hole.header, "XY"): file_xy = {"X": None, "Y": None} hole.add_header("XY", file_xy) - hole.header.XY["X"], hole.header.XY["Y"] = x, y # pylint: disable=E1101 + hole.header.XY["X"], hole.header.XY["Y"] = x, y file_fo = {"Format version": "?", "Software": "GTK_WFS"} hole.add_fileheader("FO", file_fo) file_kj = { @@ -240,7 +243,7 @@ def parse_line(line): try: hole = parse_line(line) except KeyError as error: - msg = "Wfs hole parse failed, line {}. Missing {}".format(i, error) + msg = f"Wfs hole parse failed, line {i}. Missing {error}" logger.warning(msg) if hole: holes.append(hole) @@ -250,7 +253,7 @@ def parse_line(line): if len(holes) >= maxholes: break else: - msg = f"No features returned at page {startindex//page_size}." + msg = f"No features returned at page {startindex // page_size}." logger.warning(msg) break startindex += page_size @@ -258,7 +261,7 @@ def parse_line(line): pbar.close() if collected_illegals and save_ignored: if isinstance(save_ignored, str): - with open(save_ignored, "a") as f: + with open(save_ignored, "a", encoding="utf-8") as f: if errors == "ignore_holes": write_fileheader(holes, f, fo=None, kj=None) f.write("\n".join(collected_illegals)) @@ -270,7 +273,8 @@ def parse_line(line): def to_infraformat(data, path, comments=True, fo=None, kj=None, write_mode="w"): - """Export data to infraformat. + """ + Export data to infraformat. Parameters ---------- @@ -293,6 +297,7 @@ def to_infraformat(data, path, comments=True, fo=None, kj=None, write_mode="w"): write_mode : str By default create a new file. If "wa" appends to current file and it is recommended to set fo and kj to False. + """ with _open(path, mode=write_mode) as f: write_fileheader(data, f, fo=fo, kj=kj) @@ -302,7 +307,8 @@ def to_infraformat(data, path, comments=True, fo=None, kj=None, write_mode="w"): def write_fileheader(data, f, fo=None, kj=None): - """Write fileheader format out. + """ + Write fileheader format out. Parameters ---------- @@ -312,9 +318,10 @@ def write_fileheader(data, f, fo=None, kj=None): Dictionary for fileformat, software and software version. kj : dict, optional Dictionary for Coordinate system and Height reference. + """ if fo is None: - from ..__init__ import __version__ + from pyinfraformat.__init__ import __version__ fo = { "Format version": "2.5", @@ -338,7 +345,7 @@ def write_fileheader(data, f, fo=None, kj=None): for key, subdict in {"FO": fo, "KJ": kj}.items(): line_string = [key] - for _, value in subdict.items(): + for value in subdict.values(): if value is False: continue line_string.append(str(value)) @@ -348,12 +355,14 @@ def write_fileheader(data, f, fo=None, kj=None): def write_header(header, f): - """Write header format out. + """ + Write header format out. Parameters ---------- header : Header object f : fileobject + """ header_keys = list(identifiers()[1].keys()) for key in header_keys: @@ -371,9 +380,9 @@ def write_header(header, f): f.write(line) -# pylint: disable=protected-access def write_body(hole, f, comments=True, illegal=False, body_spacer=None, body_spacer_start=None): - """Write data out. + """ + Write data out. Parameters ---------- @@ -385,6 +394,7 @@ def write_body(hole, f, comments=True, illegal=False, body_spacer=None, body_spa str used as a spacer in the body part. Defaults to 4 spaces. body_spacer_start : str str used as a spacer in the start of the body part. Defaults to 4 spaces. + """ if body_spacer is None: body_spacer = " " * 4 @@ -407,7 +417,7 @@ def write_body(hole, f, comments=True, illegal=False, body_spacer=None, body_spa labs.append(lab_line) elif key != "linenumber": items.append(str(value)) - line_string = body_spacer_start + "{}".format(body_spacer).join(items) + line_string = body_spacer_start + f"{body_spacer}".join(items) for lab_line in sorted(labs): line_string += "\n" + lab_line if int(line_dict["linenumber"]) not in body_text: @@ -481,12 +491,13 @@ def _open(path, *args, **kwargs): finally: pass else: - with io.open(path, *args, **kwargs) as f: + with open(path, *args, **kwargs) as f: yield f def read(path, encoding="auto", errors="ignore_lines", save_ignored=False): - """Read input data. + """ + Read input data. Paramaters ---------- @@ -574,7 +585,8 @@ def read(path, encoding="auto", errors="ignore_lines", save_ignored=False): msg = f"Line {linenumber}: {line_highlighted} # {error_txt}" logger.warning(msg) if illegal_rows and errors == "raise": - raise ValueError("Illegal/Il-defined hole detected!") + msg = "Illegal/Il-defined hole detected!" + raise ValueError(msg) # Add fileheaders to hole objects if fileheaders: @@ -618,7 +630,8 @@ def read(path, encoding="auto", errors="ignore_lines", save_ignored=False): logger.warning(msg) if errors == "raise": - raise ValueError("File has to end on -1 row.") + msg = "File has to end on -1 row." + raise ValueError(msg) if fileheaders and hole: for key, value in fileheaders.items(): hole.add_fileheader(key, value) @@ -630,7 +643,7 @@ def read(path, encoding="auto", errors="ignore_lines", save_ignored=False): if collected_illegals and save_ignored: if isinstance(save_ignored, str): - with open(save_ignored, "a") as f: + with open(save_ignored, "a", encoding="utf-8") as f: if errors == "ignore_holes": write_fileheader(holes, f, fo=None, kj=None) f.write("\n".join(collected_illegals)) @@ -643,7 +656,8 @@ def read(path, encoding="auto", errors="ignore_lines", save_ignored=False): def split_with_whitespace(string, maxsplit=None): - """Split string with whitespaces, maxsplit defines maximum non white-space items. + """ + Split string with whitespaces, maxsplit defines maximum non white-space items. >>> split_with_whitespace(" This is an example", maxsplit=2) ['', ' ', 'This', ' ', 'is an example'] @@ -663,16 +677,18 @@ def split_with_whitespace(string, maxsplit=None): last_item.append(item) else: return_string.append(item) - return return_string + ["".join(last_item)] + return [*return_string, "".join(last_item)] def highlight_item(string, indexes=None, marker="**", maxsplit=None): - """Hightlight string items with by split_with_whitespace indexes. + """ + Hightlight string items with by split_with_whitespace indexes. Examples -------- >>> highlight_item(" Yeah this is an example", [2,6], marker="**") ' **Yeah** this **is** an example' + """ if isinstance(indexes, int): indexes = [indexes] @@ -690,7 +706,8 @@ def highlight_item(string, indexes=None, marker="**", maxsplit=None): def dictify_line(line, head=None, restrict_fields=True, force=False): - """Parse line into dict as infraformat line. + """ + Parse line into dict as infraformat line. Returns ------- @@ -698,6 +715,7 @@ def dictify_line(line, head=None, restrict_fields=True, force=False): line values parsed to dict line_errors : list (error_string, 'split_with_whitespace' index) + """ if head is None: head = line.split()[0].strip() @@ -724,7 +742,8 @@ def dictify_line(line, head=None, restrict_fields=True, force=False): else: names, dtypes, strict = survey_dict["P"] else: - raise ValueError(f"Head '{head}' not recognized") + msg = f"Head '{head}' not recognized" + raise ValueError(msg) maxsplit = len(dtypes) if head.upper() == line.split()[0].strip().upper(): @@ -771,7 +790,8 @@ def dictify_line(line, head=None, restrict_fields=True, force=False): def strip_header(line, head=None, restrict_fields=True, force=False): - """Strip header line, returns (hole, error_dict). + """ + Strip header line, returns (hole, error_dict). Examples -------- @@ -779,6 +799,7 @@ def strip_header(line, head=None, restrict_fields=True, force=False): >>> errors {'error': "1. Could not convert 'Y' value 'NOT_COORD' to 'custom_float'.", 'line_highlighted': 'XY 6674772.0 **NOT_COORD** 15.0 01011999 1'} + """ if head is None: head = line.split()[0].strip() @@ -799,12 +820,14 @@ def strip_header(line, head=None, restrict_fields=True, force=False): def strip_inline(line, head=None, restrict_fields=True, force=False): - """Strip inline line, returns (hole, error_dict). + """ + Strip inline line, returns (hole, error_dict). Examples -------- >>> strip_inline("LB w 12 kg/m3") ({'Laboratory w': '12'}, {}) + """ if head is None: head = line.split()[0].strip() @@ -833,12 +856,14 @@ def strip_inline(line, head=None, restrict_fields=True, force=False): def strip_survey(line, survey_type, restrict_fields=True, force=False): - """Strip survey line as survey_type, returns (hole, error_dict). + """ + Strip survey line as survey_type, returns (hole, error_dict). Examples -------- >>> strip_survey(" 1.50 42 Ka", "PO") ({'Depth (m)': 1.5, 'Time (s)': 42, 'Soil type': 'Ka'}, {}) + """ *_, survey_identifiers = identifiers() if survey_type not in survey_identifiers: @@ -863,7 +888,8 @@ def strip_survey(line, survey_type, restrict_fields=True, force=False): def parse_hole(str_list, force=False): - """Parse inframodel lines to hole objects. + """ + Parse inframodel lines to hole objects. Paramaters ---------- @@ -873,7 +899,7 @@ def parse_hole(str_list, force=False): Whetere to force illegal values as str. Ex. illegal dates, floats, int will be represented as str. Else omitted. Both cases gathered as errors. - Return + Return: ------ Hole -object errors : list @@ -882,6 +908,7 @@ def parse_hole(str_list, force=False): "line_highlighted" : "**rivit tietoa ja silee jossa ei järkeä**" }, {"linenumber" : 13, "error" : "Value must be float!, Date must be format ddmmyyy!", "line_highlighted" : "XY **x_koordinaatti** 999 **12345678** piste22" } + """ str_list = list(str_list) errors = [] @@ -898,7 +925,7 @@ def parse_hole(str_list, force=False): head = head.upper() if survey_type is None and hasattr(hole.header, "TT"): - survey_type = getattr(hole.header, "TT").get("Survey abbreviation", None) + survey_type = hole.header.TT.get("Survey abbreviation", None) if survey_type: survey_type = survey_type.upper() @@ -930,7 +957,7 @@ def parse_hole(str_list, force=False): errors.append(error_dict) else: hole.add_inline(head, inline) - elif (is_number(head) and survey_type) or survey_type in ("LB",): + elif (is_number(head) and survey_type) or survey_type == "LB": survey, error_dict = strip_survey(line, survey_type, force=force) if error_dict: error_dict["linenumber"] = linenumber diff --git a/pyinfraformat/core/utils.py b/pyinfraformat/core/utils.py index 9ab08ca..16aa08d 100644 --- a/pyinfraformat/core/utils.py +++ b/pyinfraformat/core/utils.py @@ -114,9 +114,7 @@ def is_number(number_str): try: complex(number_str) except ValueError: - if number_str in NANS: - return True - return False + return number_str in NANS return True @@ -124,9 +122,7 @@ def is_nan(number_str): """Test if number_str is nan according to infraformat logic.""" if not number_str: # Empty string or None return True - if number_str in NANS: - return True - return False + return number_str in NANS def custom_int(number): @@ -135,10 +131,9 @@ def custom_int(number): integer_number = int(floating_number) if integer_number == floating_number: return integer_number - else: - msg = "Non-integer value detected, a floating point number is returned" - logger.warning(msg) - return floating_number + msg = "Non-integer value detected, a floating point number is returned" + logger.warning(msg) + return floating_number def custom_float(number): @@ -147,16 +142,19 @@ def custom_float(number): def identifiers(): - """Return header identifiers. + """ + Return header identifiers. Identifier key: (names, dtype, mandatory) mandatory Falsy or + Returns ------- file_header_identifiers: dict header_identifiers: dict inline_identifiers: dict survey_identifiers: dict + """ file_header_identifiers = { "FO": ( @@ -535,23 +533,23 @@ def identifiers(): # common_survey_mistakes = {'KK' : ['KE', 'KR'], # 'DP' : ['HE', 'HK']} - result_tuple = ( + return ( file_header_identifiers, header_identifiers, inline_identifiers, survey_identifiers, # common_survey_mistakes ) - return result_tuple def info_fi(): - """Print class info in Finnish, infraformaatti 2.3. + """ + Print class info in Finnish, infraformaatti 2.3. Main site: http://www.citygeomodel.fi/ Link to file: http://www.citygeomodel.fi/Infra_formaatti_v2.3_211215.pdf """ - helper_str = """ tiedot kerätty infraformaatti 2.3 + return """ tiedot kerätty infraformaatti 2.3 Pääsivu: http://www.citygeomodel.fi/ Linkki tiedostoon: http://www.citygeomodel.fi/Infra_formaatti_v2.3_211215.pdf @@ -874,11 +872,11 @@ def info_fi(): F Näytteen syvyystieto2 (m) t Maalaji """ - return helper_str def print_info(language="fi"): - """Print out information about the finnish infraformat. + """ + Print out information about the finnish infraformat. Currently defined only in Finnish. @@ -886,8 +884,10 @@ def print_info(language="fi"): ---------- language : custom_str, {"fi"} short format for language. + """ if language.lower() != "fi": logger.critical("Only 'fi' info is implemented") - raise NotImplementedError("Only 'fi' info is implemented") + msg = "Only 'fi' info is implemented" + raise NotImplementedError(msg) print(info_fi()) diff --git a/pyinfraformat/exceptions.py b/pyinfraformat/exceptions.py index 0f3367a..632e750 100644 --- a/pyinfraformat/exceptions.py +++ b/pyinfraformat/exceptions.py @@ -7,6 +7,7 @@ class FileExtensionMissingError(Exception): """Exception class for missing file extension.""" def __init__(self, message, errors=None): + """Initialize exception class.""" self.message = message self.errors = errors super().__init__(message) @@ -16,6 +17,7 @@ class PathNotFoundError(Exception): """Exception class for invalid path.""" def __init__(self, message, errors=None): + """Initialize exception class.""" self.message = message self.errors = errors super().__init__(message) diff --git a/pyinfraformat/plots/__init__.py b/pyinfraformat/plots/__init__.py index 19c3de6..116f278 100644 --- a/pyinfraformat/plots/__init__.py +++ b/pyinfraformat/plots/__init__.py @@ -1,4 +1,6 @@ -# pylint: disable=wildcard-import """Plotting functionality.""" -from .holes import * -from .maps import * + +from .holes import plot_hole +from .maps import plot_map + +__all__ = ["plot_hole", "plot_map"] diff --git a/pyinfraformat/plots/holes.py b/pyinfraformat/plots/holes.py index ab16572..2b86240 100644 --- a/pyinfraformat/plots/holes.py +++ b/pyinfraformat/plots/holes.py @@ -6,14 +6,19 @@ from datetime import datetime import matplotlib.dates as mdates -import matplotlib.patches as patches import matplotlib.pyplot as plt import numpy as np import pandas as pd +from matplotlib import patches __all__ = ["plot_hole"] -BBOX = dict(facecolor="white", alpha=0.75, edgecolor="none", boxstyle="round,pad=0.1") # text boxes +BBOX = { + "facecolor": "white", + "alpha": 0.75, + "edgecolor": "none", + "boxstyle": "round,pad=0.1", +} # text boxes logger = logging.getLogger("pyinfraformat") @@ -34,7 +39,8 @@ def strip_date(x): def fig_to_hmtl(fig, clear_memory=True): - """Transform matplotlib figure to html with mpld3. + """ + Transform matplotlib figure to html with mpld3. Parameters ---------- @@ -44,6 +50,7 @@ def fig_to_hmtl(fig, clear_memory=True): Returns ------- html + """ str_io = io.StringIO() fig.savefig(str_io, format="svg") @@ -56,7 +63,8 @@ def fig_to_hmtl(fig, clear_memory=True): def plot_po(one_survey): - """Plot a diagram of PO (Porakonekairaus) with matplotlib. + """ + Plot a diagram of PO (Porakonekairaus) with matplotlib. Parameters ---------- @@ -65,6 +73,7 @@ def plot_po(one_survey): Returns ------- figure : matplotlib figure + """ df = pd.DataFrame(one_survey.survey.data) if "Soil type" in df.columns: # pylint: disable=unsupported-membership-test @@ -107,7 +116,8 @@ def plot_po(one_survey): def plot_pa(one_survey): - """Plot a diagram of PA (Painokairaus) with matplotlib. + """ + Plot a diagram of PA (Painokairaus) with matplotlib. Parameters ---------- @@ -116,6 +126,7 @@ def plot_pa(one_survey): Returns ------- figure : matplotlib figure + """ df = pd.DataFrame(one_survey.survey.data) df.loc[df["Load (kN)"] >= 100, "Load (kN)"] = 0 @@ -152,7 +163,8 @@ def plot_pa(one_survey): def plot_hp(one_survey): - """Plot a diagram of HP (Puristinheijarikairaus) with matplotlib. + """ + Plot a diagram of HP (Puristinheijarikairaus) with matplotlib. Parameters ---------- @@ -161,6 +173,7 @@ def plot_hp(one_survey): Returns ------- figure : matplotlib figure + """ df = pd.DataFrame(one_survey.survey.data) @@ -179,7 +192,7 @@ def plot_hp(one_survey): ax_left.set_xlim([200, 0]) ax_left.set_xticks([200, 100, 0]) ax_right.barh( - [0] + list(df["Depth (m)"])[:-1], + [0, *list(df["Depth (m)"])[:-1]], df["Blows"], align="edge", fill=False, @@ -201,7 +214,7 @@ def plot_hp(one_survey): y_min, y_max = ax_right.get_ylim() y = y_min + (y_max - y_min) * 0.005 for x in locs[1:]: - ax_right.text(x, y, s="{:.0f}".format(x / 5), ha="center", va="bottom") + ax_right.text(x, y, s=f"{x / 5:.0f}", ha="center", va="bottom") ax_right.spines["top"].set_visible(False) ax_right.spines["right"].set_visible(False) @@ -217,7 +230,8 @@ def plot_hp(one_survey): def plot_si(one_survey): - """Plot a diagram of SI (Siipikairaus) with matplotlib. + """ + Plot a diagram of SI (Siipikairaus) with matplotlib. Parameters ---------- @@ -226,6 +240,7 @@ def plot_si(one_survey): Returns ------- figure : matplotlib figure + """ df = pd.DataFrame(one_survey.survey.data) @@ -272,7 +287,8 @@ def plot_si(one_survey): def plot_tr(one_survey): - """Plot a diagram of TR (Tärykairaus) with matplotlib. + """ + Plot a diagram of TR (Tärykairaus) with matplotlib. Parameters ---------- @@ -281,6 +297,7 @@ def plot_tr(one_survey): Returns ------- figure : matplotlib figure + """ df = pd.DataFrame(one_survey.survey.data) if "Soil type" in df.columns: # pylint: disable=unsupported-membership-test @@ -325,7 +342,8 @@ def plot_tr(one_survey): def plot_he(one_survey): - """Plot a diagram of HE (Heijarikairaus) with matplotlib. + """ + Plot a diagram of HE (Heijarikairaus) with matplotlib. Parameters ---------- @@ -334,6 +352,7 @@ def plot_he(one_survey): Returns ------- figure : matplotlib figure + """ df = pd.DataFrame(one_survey.survey.data) if "Soil type" in df.columns: # pylint: disable=unsupported-membership-test @@ -351,7 +370,7 @@ def plot_he(one_survey): plt.setp(ax_left.get_yticklabels(), visible=False) ax_left.set_xticks([]) ax_right.barh( - [0] + list(df["Depth (m)"])[:-1], + [0, *list(df["Depth (m)"])[:-1]], df["Blows"], align="edge", fill=False, @@ -384,7 +403,8 @@ def plot_he(one_survey): def plot_vp(one_survey): - """Plot a diagram of VP (Pohjavesiputki) or VO (Orsivesiptki) with matplotlib. + """ + Plot a diagram of VP (Pohjavesiputki) or VO (Orsivesiptki) with matplotlib. Parameters ---------- @@ -393,6 +413,7 @@ def plot_vp(one_survey): Returns ------- figure : matplotlib figure + """ df = pd.DataFrame(one_survey.survey.data) @@ -422,16 +443,15 @@ def plot_vp(one_survey): level_min = 1 if bottom_level > level_min: ax_left.set_ylim(level_min, top_level) + elif bottom_level < top_level: + ax_left.set_ylim(bottom_level, top_level) else: - if bottom_level < top_level: - ax_left.set_ylim(bottom_level, top_level) - else: - ax_left.set_ylim(top_level - 2, top_level) + ax_left.set_ylim(top_level - 2, top_level) ax_left.set_xlim(0, 1.5) ax_left.add_patch(rect_sieve) ax_left.add_patch(rect_rest) - ax_left.set_title("{:+.2f}".format(ground_level)) + ax_left.set_title(f"{ground_level:+.2f}") ax_left.plot(ax_left.get_xlim(), [ground_level, ground_level], c="k") ax_left.set_xticks([]) plt.setp(ax_left.get_yticklabels(), visible=False) @@ -443,7 +463,7 @@ def plot_vp(one_survey): ax_right.text( 0.02, 0.01, - "Dates missing, n: {}".format(len(dates)), + f"Dates missing, n: {len(dates)}", transform=ax_right.transAxes, bbox=BBOX, ) @@ -460,7 +480,8 @@ def plot_vp(one_survey): def plot_hole(one_survey, output="figure", figsize=(4, 4)): - """Plot a diagram of a sounding with matplotlib. + """ + Plot a diagram of a sounding with matplotlib. Parameters ---------- @@ -473,6 +494,7 @@ def plot_hole(one_survey, output="figure", figsize=(4, 4)): Returns ------- figure : figure or svg + """ def _plot_hole(one_survey): @@ -492,12 +514,11 @@ def _plot_hole(one_survey): fig = plot_tr(one_survey) elif hole_type == "HE": fig = plot_he(one_survey) - elif hole_type == "VP": - fig = plot_vp(one_survey) - elif hole_type == "VO": + elif hole_type in {"VP", "VO"}: fig = plot_vp(one_survey) else: - raise NotImplementedError('Hole object "{}" not supported'.format(hole_type)) + msg = f'Hole object "{hole_type}" not supported' + raise NotImplementedError(msg) fig.tight_layout() fig.set_size_inches(figsize) return fig @@ -511,7 +532,7 @@ def _plot_hole(one_survey): if output == "figure": return fig - elif output == "svg": + if output == "svg": return fig_to_hmtl(fig) - else: - raise NotImplementedError("Plotting backend {} not implemented".format(output)) + msg = f"Plotting backend {output} not implemented" + raise NotImplementedError(msg) diff --git a/pyinfraformat/plots/icons/hole_icons.py b/pyinfraformat/plots/icons/hole_icons.py index 8b5e630..f9d7c5e 100644 --- a/pyinfraformat/plots/icons/hole_icons.py +++ b/pyinfraformat/plots/icons/hole_icons.py @@ -1,4 +1,5 @@ -"""Create hole icons {abbreviation}.svg. +""" +Create hole icons {abbreviation}.svg. Creates to current path svg files with svgwrite. Change Globals for options like icon size and stroke width. @@ -92,13 +93,12 @@ def get_arc_command(point1, point2, radius, sweep=0): def get_arc(point1, point2, radius, width=3, stroke="black", fill="black", sweep=0): """Return an path -object that bulges to the right (sweep=0) or left (sweep=1).""" command = get_arc_command(point1, point2, radius, sweep=sweep) - path = svgwrite.path.Path( + return svgwrite.path.Path( d=command, fill=fill, stroke=stroke, stroke_width=width, ) - return path def po_icon(): @@ -232,7 +232,7 @@ def hp_icon(): point2 = (DRAWING_SIZE[0] / 2, DRAWING_SIZE[1] / 2 - radius) arc = get_arc(point2, point1, radius, width=1e-5, fill="black") point3 = (DRAWING_SIZE / 2).tolist() - arc.push("L {} {}".format(point3[0], point3[1])) + arc.push(f"L {point3[0]} {point3[1]}") dwg.add(arc) dwg.attribs["height"] += STROKE_WIDTH return dwg @@ -269,7 +269,7 @@ def he_icon(): point2 = (DRAWING_SIZE[0] / 2, DRAWING_SIZE[1] / 2 - radius) arc = get_arc(point2, point1, radius, width=1e-5, fill="black") point3 = (DRAWING_SIZE / 2).tolist() - arc.push("L {} {}".format(point3[0], point3[1])) + arc.push(f"L {point3[0]} {point3[1]}") dwg.add(arc) return dwg @@ -429,7 +429,7 @@ def ne_icon(): arc = get_arc(point1, point2, radius, width=1e-9, stroke="none", fill="black") point3 = (width / 2 + radius, width / 2) - arc.push("L {} {}".format(point3[0], point3[1])) + arc.push(f"L {point3[0]} {point3[1]}") point4 = (width / 2 - radius, width / 2) arc_arg = get_arc_command(point3, point4, radius, sweep=1) diff --git a/pyinfraformat/plots/maps.py b/pyinfraformat/plots/maps.py index b20076c..fe33c77 100644 --- a/pyinfraformat/plots/maps.py +++ b/pyinfraformat/plots/maps.py @@ -8,16 +8,18 @@ from folium.plugins import MarkerCluster, MeasureControl, MousePosition from tqdm.auto import tqdm -from ..core import Holes -from ..core.coord_utils import coord_str_recognize, project_points -from ..core.utils import ABBREVIATIONS +from pyinfraformat.core import Holes +from pyinfraformat.core.coord_utils import coord_str_recognize, project_points +from pyinfraformat.core.utils import ABBREVIATIONS + from .holes import plot_hole __all__ = ["plot_map"] def plot_map(holes, render_holes=True, progress_bar=True, popup_size=(3, 3)): - """Plot a leaflet map from holes with popup hole plots. + """ + Plot a leaflet map from holes with popup hole plots. Parameters ---------- @@ -32,11 +34,13 @@ def plot_map(holes, render_holes=True, progress_bar=True, popup_size=(3, 3)): Returns ------- map_fig : folium map object + """ holes_filtered = [] first_system = False if len(holes) == 0: - raise ValueError("Can't plot empty holes -object.") + msg = "Can't plot empty holes -object." + raise ValueError(msg) for hole in holes: if hasattr(hole, "header") and hasattr(hole.header, "XY"): if "X" in hole.header.XY and "Y" in hole.header.XY: @@ -49,9 +53,9 @@ def plot_map(holes, render_holes=True, progress_bar=True, popup_size=(3, 3)): raise ValueError(msg) if not first_system: first_system = coord_system - else: - if not first_system == coord_system: - raise ValueError("Coordinate system is not uniform in holes -object") + elif first_system != coord_system: + msg = "Coordinate system is not uniform in holes -object" + raise ValueError(msg) holes_filtered = Holes(holes_filtered) x_all, y_all = [], [] @@ -71,11 +75,10 @@ def plot_map(holes, render_holes=True, progress_bar=True, popup_size=(3, 3)): tiles=None, ) folium.TileLayer("OpenStreetMap", maxNativeZoom=19, maxZoom=max_zoom).add_to(map_fig) - folium.TileLayer("Stamen Terrain", maxNativeZoom=18, maxZoom=max_zoom).add_to(map_fig) folium.TileLayer("CartoDB positron", maxNativeZoom=18, maxZoom=max_zoom).add_to(map_fig) esri_url = ( "https://server.arcgisonline.com/ArcGIS/rest/services/" - + "World_Imagery/MapServer/tile/{z}/{y}/{x}" + "World_Imagery/MapServer/tile/{z}/{y}/{x}" ) folium.TileLayer( tiles=esri_url, @@ -137,9 +140,12 @@ def plot_map(holes, render_holes=True, progress_bar=True, popup_size=(3, 3)): cluster = MarkerCluster( control=False, - options=dict( - animate=True, maxClusterRadius=15, showCoverageOnHover=False, disableClusteringAtZoom=20 - ), + options={ + "animate": True, + "maxClusterRadius": 15, + "showCoverageOnHover": False, + "disableClusteringAtZoom": 20, + }, ).add_to(map_fig) map_fig.add_child(cluster) hole_clusters = {} @@ -161,11 +167,11 @@ def plot_map(holes, render_holes=True, progress_bar=True, popup_size=(3, 3)): ] colors = cycle(colors) clust_icon_kwargs = {} - for color, key in zip(colors, holes_filtered.value_counts().keys()): + for color, key in zip(colors, holes_filtered.value_counts().keys(), strict=False): hole_clusters[key] = folium.plugins.FeatureGroupSubGroup( cluster, name=ABBREVIATIONS.get(key, "Unrecognize abbreviation"), show=True ) - clust_icon_kwargs[key] = dict(color=color, icon="") + clust_icon_kwargs[key] = {"color": color, "icon": ""} map_fig.add_child(hole_clusters[key]) if progress_bar: @@ -234,19 +240,18 @@ def get_icon(abbreviation, clust_icon_kwargs, default=False): # print(path/icon_path, path) # print(abbreviation) try: - with open(path / icon_path, "r") as f: + with open(path / icon_path, encoding="utf-8") as f: svg_str = f.read() except FileNotFoundError: return folium.Icon(**clust_icon_kwargs[abbreviation]) height = float( - [line for line in svg_str.split() if line.startswith("height")][0] + next(line for line in svg_str.split() if line.startswith("height")) .split("=")[-1] .replace('"', "") ) width = float( - [line for line in svg_str.split() if line.startswith("width")][0] + next(line for line in svg_str.split() if line.startswith("width")) .split("=")[-1] .replace('"', "") ) - icon = folium.DivIcon(html=svg_str, icon_anchor=(width / 2, height / 2)) - return icon + return folium.DivIcon(html=svg_str, icon_anchor=(width / 2, height / 2)) diff --git a/pyproject.toml b/pyproject.toml index 1de3c8b..abc1fdb 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,5 +1,122 @@ -[tool.black] +[build-system] +requires = ["setuptools >= 40.9.0", "wheel"] +build-backend = "setuptools.build_meta" + +[tool.ruff] +# Exclude a variety of commonly ignored directories. +exclude = [ + ".bzr", + ".direnv", + ".eggs", + ".git", + ".git-rewrite", + ".hg", + ".ipynb_checkpoints", + ".mypy_cache", + ".nox", + ".pants.d", + ".pyenv", + ".pytest_cache", + ".pytype", + ".ruff_cache", + ".svn", + ".tox", + ".venv", + ".vscode", + "__pypackages__", + "_build", + "buck-out", + "build", + "dist", + "node_modules", + "site-packages", + "venv", +] +extend-exclude = ["tests"] +# Same as Black. line-length = 100 +indent-width = 4 +# Assume Python 3.12 +target-version = "py312" + +[tool.ruff.lint] +# Enable Pyflakes (`F`) and a subset of the pycodestyle (`E`) codes by default. +select = ["ALL"] +ignore = [ + "CPY001", + "D203", + "D212", + "ANN003", + "PLR0913", + "PLW2901", + "S101", + "COM812", + "ISC001", + "FBT001", + "FBT002", + "PLR0912", + "C901", + "D205", + "N813", + "SLF001", + "T201", + "PLR0914", + "PLR0915", + "PLR0917", + "PLC2701", + "BLE001", + "PLR6104", + "PD011", + "ANN401", + "ANN202", + "ANN001", + "ANN204", + "PLR2004", + "D105", + "DOC501", + "TRY004", + "DTZ007", + "PD901", + "DOC201", + "PLC0415", + "PLR0904", + "ANN201", + "PTH122", + "PTH123", + "SIM102", + "PD003", + "PLR6301", + "D417", + "N806", + "E741", + "ERA001", + "DOC402", + "ANN002", + "E722", + "PTH118", + "PTH207", + "PTH112", + "RUF001", + "FURB101", + "PLC0206", + "UP031", + "INP001", + "SIM108" +] +extend-select = [] +preview = true +# Allow fix for all enabled rules (when `--fix`) is provided. +fixable = ["ALL"] +unfixable = [] +# Allow unused variables when underscore-prefixed. +dummy-variable-rgx = "^(_+|(_+[a-zA-Z0-9_]*[a-zA-Z0-9]+?))$" -[tool.isort] -profile = "black" +[tool.ruff.format] +# Like Black, use double quotes for strings. +quote-style = "double" +# Like Black, indent with spaces, rather than tabs. +indent-style = "space" +# Like Black, respect magic trailing commas. +skip-magic-trailing-comma = false +# Like Black, automatically detect the appropriate line ending. +line-ending = "auto" diff --git a/requirements-dev.txt b/requirements-dev.txt index 865bba8..3ab0f5b 100644 --- a/requirements-dev.txt +++ b/requirements-dev.txt @@ -1,8 +1,5 @@ -black -numpydoc +ruff openpyxl xlrd -pydocstyle -pylint pytest -pytest-cov +pytest-cov \ No newline at end of file diff --git a/setup.py b/setup.py index 2fe8e78..c3b563d 100644 --- a/setup.py +++ b/setup.py @@ -26,10 +26,10 @@ def get_long_description(): def get_version(): """Read version info from __init__.py.""" - lines = open(VERSION_FILE, "rt").readlines() + lines = open(VERSION_FILE).readlines() version_regex = r"^__version__ = ['\"]([^'\"]*)['\"]" for line in lines: - version_search = re.search(version_regex, line, re.M) + version_search = re.search(version_regex, line, re.MULTILINE) if version_search: return version_search.group(1) raise RuntimeError("Unable to find version in %s." % (VERSION_FILE,)) diff --git a/tests/helpers.py b/tests/helpers.py index 92bd784..1c2955a 100644 --- a/tests/helpers.py +++ b/tests/helpers.py @@ -1,9 +1,13 @@ """Helper functions for tests.""" from pyinfraformat import Holes, from_gtk_wfs +from requests.exceptions import HTTPError def ping_gtk(): - bbox = (60.2, 24.8, 60.215, 24.83) - holes = from_gtk_wfs(bbox, "EPSG:4326") - return not Holes(holes[:3]).get_dataframe().empty + try: + bbox = (60.2, 24.8, 60.215, 24.83) + holes = from_gtk_wfs(bbox, "EPSG:4326") + result = not Holes(holes[:3]).get_dataframe().empty + except HTTPError: + return True