├── tests ├── __init__.py ├── conftest.py ├── test_write.py ├── test_read.py └── test_export.py ├── requirements.txt ├── requirements-dev.txt ├── .gitattributes ├── zmapio ├── __version__.py ├── __init__.py ├── writer.py ├── reader.py └── zmap.py ├── _static ├── output_28_1.png ├── output_30_1.png ├── output_33_1.png └── output_9_1.png ├── setup.cfg ├── .github └── workflows │ ├── ci-pre-commit.yml │ └── ci.yml ├── .pre-commit-config.yaml ├── LICENSE ├── .gitignore ├── setup.py └── README.rst /tests/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | numpy>=1.22.0 2 | -------------------------------------------------------------------------------- /requirements-dev.txt: -------------------------------------------------------------------------------- 1 | pytest 2 | pandas 3 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | *.ipynb linguist-vendored 2 | -------------------------------------------------------------------------------- /zmapio/__version__.py: -------------------------------------------------------------------------------- 1 | __version__ = "0.8.1" 2 | -------------------------------------------------------------------------------- /_static/output_28_1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/abduhbm/zmapio/HEAD/_static/output_28_1.png -------------------------------------------------------------------------------- /_static/output_30_1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/abduhbm/zmapio/HEAD/_static/output_30_1.png -------------------------------------------------------------------------------- /_static/output_33_1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/abduhbm/zmapio/HEAD/_static/output_33_1.png -------------------------------------------------------------------------------- /_static/output_9_1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/abduhbm/zmapio/HEAD/_static/output_9_1.png -------------------------------------------------------------------------------- /setup.cfg: -------------------------------------------------------------------------------- 1 | [flake8] 2 | ignore = E203 3 | max-line-length = 119 4 | 5 | [pep8] 6 | ignore = E265,E501,W504 7 | -------------------------------------------------------------------------------- /zmapio/__init__.py: -------------------------------------------------------------------------------- 1 | from .__version__ import __version__ # noqa: F401 2 | from .zmap import ZMAPGrid # noqa: F401 3 | -------------------------------------------------------------------------------- /.github/workflows/ci-pre-commit.yml: -------------------------------------------------------------------------------- 1 | name: Linting 2 | 3 | on: [push, pull_request] 4 | 5 | jobs: 6 | checks: 7 | name: "pre-commit hooks" 8 | runs-on: ubuntu-latest 9 | steps: 10 | - uses: actions/checkout@v2 11 | - uses: actions/setup-python@v2 12 | - uses: pre-commit/action@v2.0.0 13 | -------------------------------------------------------------------------------- /.pre-commit-config.yaml: -------------------------------------------------------------------------------- 1 | # See https://pre-commit.com for more information 2 | # See https://pre-commit.com/hooks.html for more hooks 3 | repos: 4 | - repo: https://github.com/pre-commit/pre-commit-hooks 5 | rev: v2.4.0 6 | hooks: 7 | - id: trailing-whitespace 8 | - id: end-of-file-fixer 9 | - id: check-yaml 10 | - id: check-added-large-files 11 | 12 | - repo: https://github.com/asottile/reorder_python_imports 13 | rev: v2.3.0 14 | hooks: 15 | - id: reorder-python-imports 16 | 17 | - repo: https://gitlab.com/pycqa/flake8 18 | rev: 3.8.3 19 | hooks: 20 | - id: flake8 21 | 22 | - repo: https://github.com/psf/black 23 | rev: 22.3.0 24 | hooks: 25 | - id: black 26 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 Abdulelah Bin Mahfoodh 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /tests/conftest.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | 3 | from zmapio import ZMAPGrid 4 | 5 | z_text = """! File created by DMBTools2.GridFileFormats.ZmapPlusFile 6 | ! 7 | @GRID FILE, GRID, 4 8 | 20, -9999.0000000, , 7, 1 9 | 6, 4, 0, 200, 0, 300 10 | 0.0, 0.0, 0.0 11 | @ 12 | -9999.0000000 -9999.0000000 3.0000000 32.0000000 13 | 88.0000000 13.0000000 14 | -9999.0000000 20.0000000 8.0000000 42.0000000 15 | 75.0000000 5.0000000 16 | 5.0000000 100.0000000 35.0000000 50.0000000 17 | 27.0000000 1.0000000 18 | 2.0000000 36.0000000 10.0000000 6.0000000 19 | 9.0000000 -9999.0000000 20 | """ 21 | 22 | 23 | @pytest.fixture 24 | def zmap_object(tmpdir): 25 | x = tmpdir.join("test.dat") 26 | x.write(z_text) 27 | z_obj = ZMAPGrid(x.strpath) 28 | yield z_obj 29 | 30 | 31 | @pytest.fixture 32 | def zmap_object_pixel_is_point(tmpdir): 33 | x = tmpdir.join("test.dat") 34 | x.write(z_text) 35 | z_obj = ZMAPGrid(x.strpath, pixel_is_point=True) 36 | yield z_obj 37 | -------------------------------------------------------------------------------- /tests/test_write.py: -------------------------------------------------------------------------------- 1 | from zmapio import ZMAPGrid 2 | 3 | z_text = """!this is 4 | !a test 5 | @test, GRID, 4 6 | 20, -9999.0, , 7, 1 7 | 6, 4, 0.0, 200.0, 0.0, 300.0 8 | 0.0, 0.0, 0.0 9 | @ 10 | -9999.0000000 -9999.0000000 3.0000000 32.0000000 11 | 88.0000000 13.0000000 12 | -9999.0000000 20.0000000 8.0000000 42.0000000 13 | 75.0000000 5.0000000 14 | 5.0000000 100.0000000 35.0000000 50.0000000 15 | 27.0000000 1.0000000 16 | 2.0000000 36.0000000 10.0000000 6.0000000 17 | 9.0000000 -9999.0000000 18 | """ 19 | 20 | 21 | def test_write_zmap_file(zmap_object, tmpdir): 22 | x = tmpdir.join("output.dat") 23 | z = ZMAPGrid( 24 | z_values=zmap_object.z_values, min_x=0.0, max_x=200.0, min_y=0.0, max_y=300.0 25 | ) 26 | z.comments = ["this is", "a test"] 27 | z.nodes_per_line = 4 28 | z.field_width = 20 29 | z.decimal_places = 7 30 | z.name = "test" 31 | z.null_value = -9999.0 32 | z.write(x.strpath) 33 | 34 | with open(x.strpath) as f: 35 | data = f.read() 36 | assert data == z_text 37 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | # This workflow will install Python dependencies, run tests and lint with a variety of Python versions 2 | # For more information see: https://help.github.com/actions/language-and-framework-guides/using-python-with-github-actions 3 | 4 | name: CI 5 | 6 | on: [push, pull_request] 7 | 8 | jobs: 9 | test: 10 | runs-on: ${{ matrix.os }} 11 | 12 | strategy: 13 | fail-fast: false 14 | matrix: 15 | os: ["ubuntu-latest", "windows-latest"] 16 | python-version: [3.7, 3.8, 3.9] 17 | 18 | env: 19 | PYTHON_VERSION: ${{ matrix.python-version }} 20 | PARALLEL: "true" 21 | COVERAGE: "false" 22 | 23 | steps: 24 | - uses: actions/checkout@v2 25 | - name: Set up Python ${{ matrix.python-version }} 26 | uses: actions/setup-python@v2 27 | with: 28 | python-version: ${{ matrix.python-version }} 29 | - name: Install dependencies 30 | shell: bash -l {0} 31 | run: | 32 | python -m pip install --upgrade pip 33 | if [ -f requirements.txt ]; then pip install -r requirements.txt; fi 34 | if [ -f requirements-dev.txt ]; then pip install -r requirements-dev.txt; fi 35 | - name: Test with pytest 36 | shell: bash -l {0} 37 | run: | 38 | pytest 39 | -------------------------------------------------------------------------------- /tests/test_read.py: -------------------------------------------------------------------------------- 1 | def test_read_comments_section(zmap_object): 2 | assert len(zmap_object.comments) == 2 3 | assert zmap_object.comments[0].startswith( 4 | " File created by DMBTools2.GridFileFormats" 5 | ) 6 | 7 | 8 | def test_columns_rows_count(zmap_object): 9 | assert zmap_object.no_cols == 4 10 | assert zmap_object.no_rows == 6 11 | 12 | 13 | def test_max_min_values(zmap_object): 14 | assert zmap_object.max_x == 200.0 15 | assert zmap_object.min_x == 0.0 16 | assert zmap_object.max_y == 300.0 17 | assert zmap_object.min_y == 0.0 18 | 19 | 20 | def test_decimal_places(zmap_object): 21 | assert zmap_object.decimal_places == 7 22 | 23 | 24 | def test_nodes_per_line(zmap_object): 25 | assert zmap_object.nodes_per_line == 4 26 | 27 | 28 | def test_null_values(zmap_object): 29 | assert zmap_object.null_value == -9999 30 | assert zmap_object.null_value_2 == "" 31 | 32 | 33 | def test_start_column(zmap_object): 34 | assert zmap_object.start_column == 1 35 | 36 | 37 | def test_x_values(zmap_object): 38 | assert zmap_object.x_values.shape == (6, 4) 39 | 40 | 41 | def test_y_values(zmap_object): 42 | assert zmap_object.y_values.shape == (6, 4) 43 | 44 | 45 | def test_z_values(zmap_object): 46 | assert zmap_object.z_values.shape == (4, 6) 47 | 48 | 49 | def test_z_type(zmap_object): 50 | assert zmap_object.z_type == "GRID" 51 | -------------------------------------------------------------------------------- /zmapio/writer.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | 3 | 4 | def chunks(x, n): 5 | for i in range(0, len(x), n): 6 | yield x[i : i + n] 7 | 8 | 9 | def write(zmap, file_object, nodes_per_line): 10 | lines = [] 11 | 12 | if not zmap.null_value_2: 13 | zmap.null_value_2 = "" 14 | 15 | for c in zmap.comments: 16 | lines.append("!" + c) 17 | 18 | lines.append("@{}, {}, {}".format(zmap.name, zmap.z_type, nodes_per_line)) 19 | lines.append( 20 | "{}, {}, {}, {}, {}".format( 21 | zmap.field_width, 22 | zmap.null_value, 23 | zmap.null_value_2, 24 | zmap.decimal_places, 25 | zmap.start_column, 26 | ) 27 | ) 28 | lines.append( 29 | "{}, {}, {}, {}, {}, {}".format( 30 | zmap.no_rows, zmap.no_cols, zmap.min_x, zmap.max_x, zmap.min_y, zmap.max_y 31 | ) 32 | ) 33 | lines.append("0.0, 0.0, 0.0") 34 | lines.append("@") 35 | 36 | file_object.write("\n".join(lines)) 37 | file_object.write("\n") 38 | 39 | fmt = "{0:>{1}.{2}f}".format 40 | width = zmap.field_width 41 | precision = zmap.decimal_places 42 | 43 | zmap.z_values = np.nan_to_num(zmap.z_values, nan=zmap.null_value) 44 | 45 | def write_lines(r): 46 | line_gen = ( 47 | "".join(line) + "\n" 48 | for line in chunks( 49 | [fmt(val, width, precision) for val in r], nodes_per_line 50 | ) 51 | ) 52 | file_object.writelines(line_gen) 53 | 54 | [write_lines(row) for row in zmap.z_values] 55 | 56 | file_object.writelines([""]) 57 | -------------------------------------------------------------------------------- /zmapio/reader.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | 3 | try: 4 | import cStringIO as StringIO 5 | except ImportError: 6 | try: 7 | import StringIO 8 | except ImportError: 9 | from io import StringIO 10 | else: 11 | from StringIO import StringIO 12 | else: 13 | from StringIO import StringIO 14 | 15 | 16 | def open_file(file_ref): 17 | if isinstance(file_ref, str): 18 | lines = file_ref.splitlines() 19 | 20 | if len(lines) > 1: 21 | file_ref = StringIO(file_ref) 22 | 23 | else: 24 | file_ref = open(lines[0], "r") 25 | 26 | return file_ref 27 | 28 | 29 | def read_file_contents(file_obj, dtype): 30 | comments = [] 31 | headers = None 32 | data = None 33 | data_flag = False 34 | lines = file_obj.readlines() 35 | lines = [line for line in lines if not line.startswith("+")] 36 | 37 | for i in range(len(lines)): 38 | line = lines[i].strip() 39 | if not line: 40 | continue 41 | 42 | if line.startswith("!"): 43 | comments.append(line[1:]) 44 | 45 | elif line.startswith("@") and not data_flag: 46 | data_flag = True 47 | headers = read_headers(i, lines) 48 | 49 | elif line.startswith("@") and data_flag: 50 | data = read_data(i, lines, dtype) 51 | break 52 | 53 | else: 54 | continue 55 | 56 | return comments, headers, data 57 | 58 | 59 | def read_headers(index, lines): 60 | rows = [] 61 | for line in lines[index : index + 4]: 62 | line = line.strip() 63 | rows.append(line.split(",")) 64 | 65 | header = { 66 | "name": rows[0][0].strip()[1:], 67 | "z_type": rows[0][1].strip(), 68 | "nodes_per_line": int(rows[0][2]), 69 | "field_width": int(rows[1][0]), 70 | "null_value": rows[1][1].strip(), 71 | "null_value_2": rows[1][2].strip(), 72 | "decimal_places": int(rows[1][3]), 73 | "start_column": int(rows[1][4]), 74 | "no_rows": int(rows[2][0]), 75 | "no_cols": int(rows[2][1]), 76 | "min_x": np.float64(rows[2][2]), 77 | "max_x": np.float64(rows[2][3]), 78 | "min_y": np.float64(rows[2][4]), 79 | "max_y": np.float64(rows[2][5]), 80 | } 81 | 82 | return header 83 | 84 | 85 | def read_data(index, lines, dtype): 86 | return np.asarray( 87 | [dtype(word) for line in lines[index + 1 :] for word in line.strip().split()] 88 | ) 89 | -------------------------------------------------------------------------------- /tests/test_export.py: -------------------------------------------------------------------------------- 1 | import json 2 | import warnings 3 | 4 | import pandas as pd 5 | import pytest 6 | 7 | 8 | def test_export_to_csv(zmap_object, tmpdir): 9 | x = tmpdir.join("output.csv") 10 | zmap_object.to_csv(x.strpath) 11 | lines = x.readlines() 12 | assert len(lines) == 25 13 | assert lines[0] == "# X,Y,Z\n" 14 | assert lines[1] == "0.0,300.0,nan\n" 15 | 16 | 17 | def test_export_to_csv_pixel_is_point(zmap_object_pixel_is_point, tmpdir): 18 | x = tmpdir.join("output.csv") 19 | zmap_object_pixel_is_point.to_csv(x.strpath) 20 | lines = x.readlines() 21 | assert len(lines) == 25 22 | assert lines[0] == "# X,Y,Z\n" 23 | assert lines[1] == "25.0,275.0,nan\n" 24 | 25 | 26 | def test_export_to_geojson(zmap_object, tmpdir): 27 | x = tmpdir.join("output.json") 28 | zmap_object.to_geojson(x.strpath) 29 | d = json.load(x) 30 | assert sorted(list(d.keys())) == ["coordinates", "type"] 31 | assert d.get("type") == "MultiPoint" 32 | assert len(d.get("coordinates")) == 24 33 | assert [0.0, 60.0, 88.0] in d.get("coordinates") 34 | 35 | 36 | def test_export_to_geojson_pixel_is_point(zmap_object_pixel_is_point, tmpdir): 37 | x = tmpdir.join("output.json") 38 | zmap_object_pixel_is_point.to_geojson(x.strpath) 39 | d = json.load(x) 40 | assert sorted(list(d.keys())) == ["coordinates", "type"] 41 | assert d.get("type") == "MultiPoint" 42 | assert len(d.get("coordinates")) == 24 43 | assert [25.0, 175.0, 3.0] in d.get("coordinates") 44 | 45 | 46 | def test_export_to_wkt(zmap_object, tmpdir): 47 | x = tmpdir.join("output.wkt") 48 | zmap_object.to_wkt(x.strpath) 49 | with open(x.strpath) as f: 50 | line = f.readline() 51 | assert line.startswith("MULTIPOINT ((0.0000000 300.0000000 nan),") 52 | 53 | 54 | def test_export_to_wkt_pixel_is_point(zmap_object_pixel_is_point, tmpdir): 55 | x = tmpdir.join("output.wkt") 56 | zmap_object_pixel_is_point.to_wkt(x.strpath) 57 | with open(x.strpath) as f: 58 | line = f.readline() 59 | assert line.startswith("MULTIPOINT ((25.0000000 275.0000000 nan),") 60 | 61 | 62 | def test_export_to_wkt_with_precision(zmap_object, tmpdir): 63 | x = tmpdir.join("output.wkt") 64 | zmap_object.to_wkt(x.strpath, precision=2) 65 | with open(x.strpath) as f: 66 | line = f.readline() 67 | assert line.startswith("MULTIPOINT ((0.00 300.00 nan),") 68 | 69 | 70 | def test_export_to_dataframe(zmap_object): 71 | with pytest.warns(UserWarning, match="to_dataframe was renamed to to_pandas"): 72 | zmap_object.to_dataframe() 73 | 74 | 75 | def test_export_to_pandas(zmap_object): 76 | df = zmap_object.to_pandas() 77 | assert type(df) == pd.DataFrame 78 | assert df.describe().loc["mean"]["X"] == 100.0 79 | 80 | 81 | def test_export_to_dataframe_capture_warning(zmap_object): 82 | with pytest.warns(UserWarning): 83 | warnings.warn("renamed to to_pandas", UserWarning) 84 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Created by .ignore support plugin (hsz.mobi) 2 | ### JetBrains template 3 | # Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio and WebStorm 4 | # Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839 5 | 6 | # User-specific stuff 7 | .idea/ 8 | 9 | # CMake 10 | cmake-build-*/ 11 | 12 | # Mongo Explorer plugin 13 | .idea/**/mongoSettings.xml 14 | 15 | # File-based project format 16 | *.iws 17 | 18 | # IntelliJ 19 | out/ 20 | 21 | # mpeltonen/sbt-idea plugin 22 | .idea_modules/ 23 | 24 | # JIRA plugin 25 | atlassian-ide-plugin.xml 26 | 27 | # Cursive Clojure plugin 28 | .idea/replstate.xml 29 | 30 | # Crashlytics plugin (for Android Studio and IntelliJ) 31 | com_crashlytics_export_strings.xml 32 | crashlytics.properties 33 | crashlytics-build.properties 34 | fabric.properties 35 | 36 | # Editor-based Rest Client 37 | .idea/httpRequests 38 | ### Python template 39 | # Byte-compiled / optimized / DLL files 40 | __pycache__/ 41 | *.py[cod] 42 | *$py.class 43 | 44 | # C extensions 45 | *.so 46 | 47 | # Distribution / packaging 48 | .Python 49 | build/ 50 | develop-eggs/ 51 | dist/ 52 | downloads/ 53 | eggs/ 54 | .eggs/ 55 | lib/ 56 | lib64/ 57 | parts/ 58 | sdist/ 59 | var/ 60 | wheels/ 61 | *.egg-info/ 62 | .installed.cfg 63 | *.egg 64 | MANIFEST 65 | 66 | # PyInstaller 67 | # Usually these files are written by a python script from a template 68 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 69 | *.manifest 70 | *.spec 71 | 72 | # Installer logs 73 | pip-log.txt 74 | pip-delete-this-directory.txt 75 | 76 | # Unit test / coverage reports 77 | htmlcov/ 78 | .tox/ 79 | .coverage 80 | .coverage.* 81 | .cache 82 | nosetests.xml 83 | coverage.xml 84 | *.cover 85 | .hypothesis/ 86 | .pytest_cache/ 87 | 88 | # Translations 89 | *.mo 90 | *.pot 91 | 92 | # Django stuff: 93 | *.log 94 | local_settings.py 95 | db.sqlite3 96 | 97 | # Flask stuff: 98 | instance/ 99 | .webassets-cache 100 | 101 | # Scrapy stuff: 102 | .scrapy 103 | 104 | # Sphinx documentation 105 | docs/_build/ 106 | 107 | # PyBuilder 108 | target/ 109 | 110 | # Jupyter Notebook 111 | .ipynb_checkpoints 112 | 113 | # pyenv 114 | .python-version 115 | 116 | # celery beat schedule file 117 | celerybeat-schedule 118 | 119 | # SageMath parsed files 120 | *.sage.py 121 | 122 | # Environments 123 | .env 124 | .venv 125 | env/ 126 | venv/ 127 | ENV/ 128 | env.bak/ 129 | venv.bak/ 130 | 131 | # Spyder project settings 132 | .spyderproject 133 | .spyproject 134 | 135 | # Rope project settings 136 | .ropeproject 137 | 138 | # mkdocs documentation 139 | /site 140 | 141 | # mypy 142 | .mypy_cache/ 143 | 144 | .DS_Store 145 | .idea/encodings.xml 146 | .idea/inspectionProfiles/ 147 | .idea/misc.xml 148 | .idea/modules.xml 149 | .idea/zmapio.iml 150 | _static/.DS_Store 151 | examples/.DS_Store 152 | output/ 153 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | import io 4 | import os 5 | import sys 6 | from shutil import rmtree 7 | 8 | from setuptools import Command 9 | from setuptools import find_packages 10 | from setuptools import setup 11 | 12 | NAME = "zmapio" 13 | DESCRIPTION = "Python library for reading and writing map gridded data using ZMAP Plus ASCII Grid format" 14 | URL = "https://github.com/abduhbm/zmapio" 15 | AUTHOR = "Abdulelah Bin Mahfoodh" 16 | 17 | REQUIRED = ["numpy>=1.22"] 18 | EXTRA_REQS = ["pandas", "matplotlib"] 19 | TEST_REQS = ["pytest"] 20 | 21 | here = os.path.abspath(os.path.dirname(__file__)) 22 | 23 | with io.open(os.path.join(here, "README.rst"), encoding="utf-8") as f: 24 | long_description = "\n" + f.read() 25 | 26 | about = {} 27 | with open(os.path.join(here, NAME, "__version__.py")) as f: 28 | exec(f.read(), about) 29 | 30 | 31 | class UploadCommand(Command): 32 | """Support setup.py upload.""" 33 | 34 | description = "Build and publish the package." 35 | user_options = [] 36 | 37 | @staticmethod 38 | def status(s): 39 | """Prints things in bold.""" 40 | print("\033[1m{0}\033[0m".format(s)) 41 | 42 | def initialize_options(self): 43 | pass 44 | 45 | def finalize_options(self): 46 | pass 47 | 48 | def run(self): 49 | try: 50 | self.status("Removing previous builds…") 51 | rmtree(os.path.join(here, "dist")) 52 | except OSError: 53 | pass 54 | 55 | self.status("Building Source and Wheel (universal) distribution…") 56 | os.system("{0} setup.py sdist bdist_wheel --universal".format(sys.executable)) 57 | 58 | self.status("Uploading the package to PyPi via Twine…") 59 | os.system("twine upload dist/*") 60 | 61 | sys.exit() 62 | 63 | 64 | setup( 65 | name=NAME, 66 | version=about["__version__"], 67 | description=DESCRIPTION, 68 | long_description=long_description, 69 | author=AUTHOR, 70 | url=URL, 71 | packages=find_packages(exclude=["tests"]), 72 | include_package_data=True, 73 | install_requires=REQUIRED, 74 | extras_require={"all": EXTRA_REQS, "test": (EXTRA_REQS, TEST_REQS)}, 75 | tests_require=TEST_REQS, 76 | license="MIT", 77 | keywords="ZMAP Plus Grid format", 78 | classifiers=[ 79 | "Development Status :: 3 - Alpha", 80 | "License :: OSI Approved :: MIT License", 81 | "Programming Language :: Python", 82 | "Programming Language :: Python :: 3", 83 | "Programming Language :: Python :: 3.6", 84 | "Programming Language :: Python :: 3.7", 85 | "Programming Language :: Python :: 3.8", 86 | "Programming Language :: Python :: 3.9", 87 | "Programming Language :: Python :: 3.10", 88 | "Topic :: Utilities", 89 | "Topic :: Scientific/Engineering :: GIS", 90 | "Topic :: Scientific/Engineering :: Visualization", 91 | ], 92 | # $ setup.py publish support. 93 | cmdclass={"upload": UploadCommand}, 94 | ) 95 | -------------------------------------------------------------------------------- /README.rst: -------------------------------------------------------------------------------- 1 | 2 | zmapio: reading and writing ZMAP Plus Grid files 3 | ------------------------------------------------ 4 | 5 | |CI Status| |PyPI version| 6 | 7 | .. |CI Status| image:: https://github.com/abduhbm/zmapio/workflows/CI/badge.svg?branch=master 8 | :target: https://github.com/abduhbm/zmapio/actions?query=workflow%3A%22CI%22 9 | .. |PyPI version| image:: https://img.shields.io/pypi/v/zmapio.svg 10 | :target: https://pypi.python.org/pypi/zmapio 11 | 12 | 13 | To install: 14 | =========== 15 | 16 | .. code:: bash 17 | 18 | $ pip install zmapio 19 | 20 | 21 | Basic usage of zmapio 22 | ===================== 23 | 24 | 25 | .. code:: python 26 | 27 | import matplotlib.pyplot as plt 28 | import numpy as np 29 | from zmapio import ZMAPGrid 30 | 31 | .. code:: python 32 | 33 | %matplotlib inline 34 | 35 | Reading a ZMAP file: 36 | 37 | .. code:: python 38 | 39 | z_file = ZMAPGrid('./examples/NSLCU.dat') 40 | 41 | Accessing the comments header: 42 | 43 | .. code:: python 44 | 45 | for c in z_file.comments: 46 | print(c) 47 | 48 | 49 | .. parsed-literal:: 50 | 51 | Landmark Zmap grid file name: .\DATA\NSLCU.dat 52 | Created/converted by Oasis Montaj, Geosoft Inc. 53 | 54 | 55 | Plotting the grid data: 56 | 57 | .. code:: python 58 | 59 | z_file.plot() 60 | 61 | 62 | 63 | 64 | .. image:: https://raw.githubusercontent.com/abduhbm/zmapio/master/_static/output_9_1.png 65 | 66 | 67 | Counts for rows and columns: 68 | 69 | .. code:: python 70 | 71 | z_file.no_cols, z_file.no_rows 72 | 73 | 74 | 75 | 76 | .. parsed-literal:: 77 | 78 | (435, 208) 79 | 80 | 81 | 82 | Shape for z-values: 83 | 84 | .. code:: python 85 | 86 | z_file.z_values.shape 87 | 88 | 89 | 90 | 91 | .. parsed-literal:: 92 | 93 | (208, 435) 94 | 95 | 96 | 97 | Exporting to CSV file: 98 | 99 | .. code:: python 100 | 101 | z_file.to_csv('./output/output.csv') 102 | 103 | .. code:: bash 104 | 105 | head ./output/output.csv 106 | 107 | 108 | .. parsed-literal:: 109 | 110 | -630000.0,2621000.0,-16481.9570313 111 | -630000.0,2618000.0,-16283.9033203 112 | -630000.0,2615000.0,-16081.5751953 113 | -630000.0,2612000.0,-15856.7861328 114 | -630000.0,2609000.0,-15583.7167969 115 | -630000.0,2606000.0,-15255.734375 116 | -630000.0,2603000.0,-14869.3769531 117 | -630000.0,2600000.0,-14426.1513672 118 | -630000.0,2597000.0,-13915.8769531 119 | -630000.0,2594000.0,-13340.4677734 120 | 121 | 122 | Exporting to WKT file: 123 | 124 | .. code:: python 125 | 126 | z_file.to_wkt('./output/output.wkt', precision=2) 127 | 128 | Exporting to GeoJSON file: 129 | 130 | .. code:: python 131 | 132 | z_file.to_geojson('./output/output.json') 133 | 134 | Exporting to Pandas Dataframe: 135 | 136 | .. code:: python 137 | 138 | df = z_file.to_dataframe() 139 | 140 | 141 | .. code:: python 142 | 143 | df.Z.describe() 144 | 145 | 146 | 147 | 148 | .. parsed-literal:: 149 | 150 | count 90480.000000 151 | mean -5244.434235 152 | std 4692.845490 153 | min -16691.371094 154 | 25% -10250.590088 155 | 50% -4003.433105 156 | 75% -1320.896881 157 | max 2084.417969 158 | Name: Z, dtype: float64 159 | 160 | 161 | 162 | Write a new ZMAP file as 3 nodes per line format: 163 | 164 | .. code:: python 165 | 166 | z_file.write('./output/test.zmap', nodes_per_line=3) 167 | 168 | .. code:: bash 169 | 170 | head ./output/test.zmap 171 | 172 | 173 | .. parsed-literal:: 174 | 175 | ! Landmark Zmap grid file name: .\DATA\NSLCU.dat 176 | ! Created/converted by Oasis Montaj, Geosoft Inc. 177 | @.\DATA\NSLCU.dat, GRID, 3 178 | 20, 1e+30, , 7, 1 179 | 208, 435, -630000.0, 672000.0, 2000000.0, 2621000.0 180 | 0.0, 0.0, 0.0 181 | @ 182 | -16481.9570313 -16283.9033203 -16081.5751953 183 | -15856.7861328 -15583.7167969 -15255.7343750 184 | -14869.3769531 -14426.1513672 -13915.8769531 185 | 186 | 187 | Creating a ZMAP object from string: 188 | 189 | .. code:: python 190 | 191 | z_text = """ 192 | ! 193 | ! File created by DMBTools2.GridFileFormats.ZmapPlusFile 194 | ! 195 | @GRID FILE, GRID, 4 196 | 20, -9999.0000000, , 7, 1 197 | 6, 4, 0, 200, 0, 300 198 | 0.0, 0.0, 0.0 199 | @ 200 | -9999.0000000 -9999.0000000 3.0000000 32.0000000 201 | 88.0000000 13.0000000 202 | -9999.0000000 20.0000000 8.0000000 42.0000000 203 | 75.0000000 5.0000000 204 | 5.0000000 100.0000000 35.0000000 50.0000000 205 | 27.0000000 1.0000000 206 | 2.0000000 36.0000000 10.0000000 6.0000000 207 | 9.0000000 -9999.0000000 208 | """ 209 | z_t = ZMAPGrid(z_text) 210 | z_t.plot() 211 | 212 | 213 | 214 | 215 | .. image:: https://raw.githubusercontent.com/abduhbm/zmapio/master/_static/output_28_1.png 216 | 217 | 218 | Adding colorbar and colormap using matplotlib: 219 | 220 | .. code:: python 221 | 222 | z_obj = ZMAPGrid('./examples/NStopo.dat') 223 | fig=plt.figure(figsize=(12, 6)) 224 | z_obj.plot(cmap='jet') 225 | plt.colorbar() 226 | 227 | 228 | 229 | 230 | .. image:: https://raw.githubusercontent.com/abduhbm/zmapio/master/_static/output_30_1.png 231 | 232 | 233 | Creating a new ZMAP object from 2D-Numpy array with shape (no_cols, 234 | no_rows): 235 | 236 | .. code:: python 237 | 238 | z_val = z_obj.z_values 239 | print('Z-values shape: ', z_val.shape) 240 | new_zgrid = ZMAPGrid(z_values=z_val, min_x=-630000.0000, max_x=672000.0000, 241 | min_y=2000000.0000, max_y=2621000.0000) 242 | 243 | 244 | .. parsed-literal:: 245 | 246 | Z-values shape: (435, 208) 247 | 248 | 249 | .. code:: python 250 | 251 | new_zgrid.plot(cmap='gist_earth') 252 | 253 | 254 | 255 | 256 | .. image:: https://raw.githubusercontent.com/abduhbm/zmapio/master/_static/output_33_1.png 257 | 258 | 259 | Customize writing a ZMAP file: 260 | 261 | .. code:: python 262 | 263 | new_zgrid.comments = ['this is', 'a test'] 264 | new_zgrid.nodes_per_line = 4 265 | new_zgrid.field_width = 15 266 | new_zgrid.decimal_places = 3 267 | new_zgrid.name = 'test' 268 | new_zgrid.write('./output/new_z.dat') 269 | 270 | .. code:: bash 271 | 272 | head ./output/new_z.dat 273 | 274 | 275 | .. parsed-literal:: 276 | 277 | !this is 278 | !a test 279 | @test, GRID, 4 280 | 15, 1e+30, , 3, 1 281 | 208, 435, -630000.0, 672000.0, 2000000.0, 2621000.0 282 | 0.0, 0.0, 0.0 283 | @ 284 | -67.214 -67.570 -67.147 -69.081 285 | -73.181 -74.308 -72.766 -72.034 286 | -70.514 -68.555 -66.195 -62.776 287 | 288 | 289 | References 290 | ========== 291 | * https://lists.osgeo.org/pipermail/gdal-dev/2011-June/029173.html 292 | * https://gist.github.com/wassname/526d5fde3f3cbeb67da8 293 | * Saltus, R.W. and Bird, K.J., 2003. Digital depth horizon compilations of the Alaskan North Slope and adjacent arctic regions. U.S. Geological Survey data release: https://doi.org/10.3133/ofr03230 294 | -------------------------------------------------------------------------------- /zmapio/zmap.py: -------------------------------------------------------------------------------- 1 | import json 2 | 3 | import numpy as np 4 | 5 | from . import reader 6 | from . import writer 7 | 8 | 9 | class ZMAPGrid(object): 10 | def __init__( 11 | self, 12 | file_ref=None, 13 | comments=None, 14 | name=None, 15 | z_type="GRID", 16 | nodes_per_line=None, 17 | field_width=None, 18 | null_value=1e30, 19 | decimal_places=None, 20 | start_column=1, 21 | min_x=None, 22 | max_x=None, 23 | min_y=None, 24 | max_y=None, 25 | z_values=None, 26 | pixel_is_point=False, 27 | **kwargs 28 | ): 29 | self.comments = comments 30 | self.name = name 31 | self.z_type = z_type 32 | self.nodes_per_line = nodes_per_line 33 | self.field_width = field_width 34 | self.null_value = null_value 35 | self.null_value_2 = "" 36 | self.decimal_places = decimal_places 37 | self.start_column = start_column 38 | self.z_values = z_values 39 | self.min_x = min_x 40 | self.max_x = max_x 41 | self.min_y = min_y 42 | self.max_y = max_y 43 | self.pixel_is_point = pixel_is_point 44 | 45 | if file_ref: 46 | x, y, z = self.read(file_ref, **kwargs) 47 | self.x_values = x 48 | self.y_values = y 49 | self.z_values = z 50 | 51 | elif all( 52 | v is not None 53 | for v in [self.z_values, self.min_x, self.max_x, self.min_y, self.max_y] 54 | ): 55 | self.no_cols, self.no_rows = self.z_values.shape 56 | x = np.linspace(self.min_x, self.max_x, self.no_cols) 57 | y = np.linspace(self.max_y, self.min_y, self.no_rows) 58 | self.x_values, self.y_values = np.meshgrid(x, y) 59 | 60 | def read(self, file_ref, dtype=np.float64): 61 | file_obj = reader.open_file(file_ref) 62 | comments, headers, z = reader.read_file_contents(file_obj, dtype) 63 | 64 | if not headers: 65 | raise ValueError("Header section is not defined") 66 | 67 | for key in headers: 68 | setattr(self, key, headers[key]) 69 | 70 | self.comments = comments 71 | 72 | if hasattr(file_obj, "close"): 73 | file_obj.close() 74 | 75 | try: 76 | self.null_value = np.float64(self.null_value) 77 | except TypeError: 78 | try: 79 | self.null_value = np.float64(self.null_value_2) 80 | except TypeError: 81 | raise ValueError("Null value is not defined in header") 82 | 83 | z[z == self.null_value] = np.nan 84 | z = z.reshape((self.no_cols, self.no_rows)) 85 | if self.pixel_is_point: 86 | # get cell size 87 | dx = (self.max_x - self.min_x) / self.no_cols 88 | dy = (self.max_y - self.min_y) / self.no_rows 89 | x = np.linspace(self.min_x + (dx / 2), self.max_x - (dx / 2), self.no_cols) 90 | y = np.linspace(self.max_y - (dy / 2), self.min_y + (dy / 2), self.no_rows) 91 | 92 | else: 93 | x = np.linspace(self.min_x, self.max_x, self.no_cols) 94 | y = np.linspace(self.max_y, self.min_y, self.no_rows) 95 | 96 | x, y = np.meshgrid(x, y) 97 | 98 | return x, y, z 99 | 100 | def plot(self, **kwargs): 101 | try: 102 | import matplotlib.pyplot as plt 103 | except ImportError: 104 | raise ImportError("matplotlib needs to be installed for plotting.") 105 | 106 | ax = plt.pcolormesh( 107 | self.x_values, self.y_values, self.z_values.swapaxes(0, 1), **kwargs 108 | ) 109 | 110 | return ax 111 | 112 | def to_csv(self, file_ref, swap_null=False, delimiter=",", **kwargs): 113 | dat = np.column_stack( 114 | [ 115 | self.x_values.flatten(), 116 | self.y_values.flatten(), 117 | self.z_values.T.flatten(), 118 | ] 119 | ) 120 | if swap_null: 121 | dat = np.nan_to_num(dat, nan=self.null_value) 122 | np.savetxt( 123 | file_ref, dat, header="X,Y,Z", delimiter=delimiter, fmt="%s", **kwargs 124 | ) 125 | 126 | def to_wkt(self, file_ref, precision=None): 127 | opened_file = False 128 | if isinstance(file_ref, str) and not hasattr(file_ref, "write"): 129 | opened_file = True 130 | file_ref = open(file_ref, "w") 131 | 132 | if not precision: 133 | if self.decimal_places: 134 | precision = self.decimal_places 135 | else: 136 | precision = 4 137 | 138 | nodes = [] 139 | for j in range(self.no_cols): 140 | for i in range(self.no_rows): 141 | x = self.x_values[i, j] 142 | y = self.y_values[i, j] 143 | z = self.z_values[j, i] 144 | nodes.append( 145 | "({} {} {})".format( 146 | np.format_float_positional( 147 | x, precision=precision, unique=False 148 | ), 149 | np.format_float_positional( 150 | y, precision=precision, unique=False 151 | ), 152 | np.format_float_positional( 153 | z, precision=precision, unique=False 154 | ), 155 | ) 156 | ) 157 | file_ref.write("MULTIPOINT (" + ", ".join(nodes) + ")") 158 | 159 | if opened_file: 160 | file_ref.close() 161 | 162 | def to_geojson(self, file_ref): 163 | opened_file = False 164 | if isinstance(file_ref, str) and not hasattr(file_ref, "write"): 165 | opened_file = True 166 | file_ref = open(file_ref, "w") 167 | 168 | nodes = [] 169 | for j in range(self.no_cols): 170 | for i in range(self.no_rows): 171 | x = self.x_values[i, j] 172 | y = self.y_values[i, j] 173 | z = self.z_values[j, i] 174 | nodes.append([x, y, z]) 175 | 176 | json.dump({"type": "MultiPoint", "coordinates": nodes}, file_ref) 177 | 178 | if opened_file: 179 | file_ref.close() 180 | 181 | def to_dataframe(self): 182 | import warnings 183 | 184 | warnings.warn("In version 0.7, to_dataframe was renamed to to_pandas") 185 | return self.to_pandas() 186 | 187 | def to_pandas(self): 188 | try: 189 | import pandas as pd 190 | except ImportError: 191 | raise ImportError( 192 | "pandas package needs to be installed for dataframe conversion." 193 | ) 194 | 195 | dat = np.column_stack( 196 | [ 197 | self.x_values.flatten(), 198 | self.y_values.flatten(), 199 | self.z_values.T.flatten(), 200 | ] 201 | ) 202 | return pd.DataFrame(dat, columns=["X", "Y", "Z"]).sort_values(by=["X", "Y"]) 203 | 204 | def write(self, file_ref, nodes_per_line=None): 205 | opened_file = False 206 | if isinstance(file_ref, str) and not hasattr(file_ref, "write"): 207 | opened_file = True 208 | file_ref = open(file_ref, "w") 209 | 210 | if not nodes_per_line: 211 | nodes_per_line = self.nodes_per_line 212 | 213 | writer.write(self, file_ref, nodes_per_line) 214 | if opened_file: 215 | file_ref.close() 216 | --------------------------------------------------------------------------------