Joshua Sundance Bailey commited on
Commit
4b5e485
0 Parent(s):

initial commit

Browse files
.dockerignore ADDED
@@ -0,0 +1,14 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ .env
2
+ .env-example
3
+ .git/
4
+ .github
5
+ .gitignore
6
+ .idea
7
+ .mypy_cache
8
+ .pre-commit-config.yaml
9
+ .ruff_cache
10
+ Dockerfile
11
+ kubernetes
12
+ docker-compose.yml
13
+ junk/
14
+ kubernetes/
.github/ISSUE_TEMPLATE/bug_report.md ADDED
@@ -0,0 +1,38 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ ---
2
+ name: Bug report
3
+ about: Create a report to help us improve
4
+ title: ''
5
+ labels: bug
6
+ assignees: ''
7
+
8
+ ---
9
+
10
+ **Describe the bug**
11
+ A clear and concise description of what the bug is.
12
+
13
+ **To Reproduce**
14
+ Steps to reproduce the behavior:
15
+ 1. Go to '...'
16
+ 2. Click on '....'
17
+ 3. Scroll down to '....'
18
+ 4. See error
19
+
20
+ **Expected behavior**
21
+ A clear and concise description of what you expected to happen.
22
+
23
+ **Screenshots**
24
+ If applicable, add screenshots to help explain your problem.
25
+
26
+ **Desktop (please complete the following information):**
27
+ - OS: [e.g. iOS]
28
+ - Browser [e.g. chrome, safari]
29
+ - Version [e.g. 22]
30
+
31
+ **Smartphone (please complete the following information):**
32
+ - Device: [e.g. iPhone6]
33
+ - OS: [e.g. iOS8.1]
34
+ - Browser [e.g. stock browser, safari]
35
+ - Version [e.g. 22]
36
+
37
+ **Additional context**
38
+ Add any other context about the problem here.
.github/ISSUE_TEMPLATE/feature_request.md ADDED
@@ -0,0 +1,17 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ ---
2
+ name: Feature request
3
+ about: Suggest an idea for this project
4
+ title: ''
5
+ labels: enhancement
6
+ assignees: ''
7
+
8
+ ---
9
+
10
+ **Describe the solution you'd like**
11
+ A clear and concise description of what you want to happen.
12
+
13
+ **Describe alternatives you've considered**
14
+ A clear and concise description of any alternative solutions or features you've considered.
15
+
16
+ **Additional context**
17
+ Add any other context or screenshots about the feature request here.
.github/dependabot.yml ADDED
@@ -0,0 +1,11 @@
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # To get started with Dependabot version updates, you'll need to specify which
2
+ # package ecosystems to update and where the package manifests are located.
3
+ # Please see the documentation for all configuration options:
4
+ # https://docs.github.com/github/administering-a-repository/configuration-options-for-dependency-updates
5
+
6
+ version: 2
7
+ updates:
8
+ - package-ecosystem: "pip" # See documentation for possible values
9
+ directory: "/" # Location of package manifests
10
+ schedule:
11
+ interval: "weekly"
.github/pull_request_template.md ADDED
@@ -0,0 +1,12 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ Thank you for contributing!
2
+ Before submitting this PR, please make sure:
3
+
4
+ - [ ] Your code builds clean without any errors or warnings
5
+ - [ ] Your code doesn't break anything we can't fix
6
+ - [ ] You have added appropriate tests
7
+
8
+ Please check one or more of the following to describe the nature of this PR:
9
+ - [ ] New feature
10
+ - [ ] Bug fix
11
+ - [ ] Documentation
12
+ - [ ] Other
.gitignore ADDED
@@ -0,0 +1,91 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ *$py.class
2
+ *.chainlit
3
+ *.chroma
4
+ *.cover
5
+ *.egg
6
+ *.egg-info/
7
+ *.env
8
+ *.langchain.db
9
+ *.log
10
+ *.manifest
11
+ *.mo
12
+ *.pot
13
+ *.py,cover
14
+ *.py[cod]
15
+ *.sage.py
16
+ *.so
17
+ *.spec
18
+ .DS_STORE
19
+ .Python
20
+ .cache
21
+ .coverage
22
+ .coverage.*
23
+ .dmypy.json
24
+ .eggs/
25
+ .env
26
+ .hypothesis/
27
+ .idea/
28
+ .installed.cfg
29
+ .ipynb_checkpoints
30
+ .mypy_cache/
31
+ .nox/
32
+ .pyre/
33
+ .pytest_cache/
34
+ .python-version
35
+ .ropeproject
36
+ .ruff_cache/
37
+ .scrapy
38
+ .spyderproject
39
+ .spyproject
40
+ .tox/
41
+ .venv
42
+ .vscode
43
+ .webassets-cache
44
+ /site
45
+ ENV/
46
+ MANIFEST
47
+ __pycache__
48
+ __pycache__/
49
+ __pypackages__/
50
+ build/
51
+ celerybeat-schedule
52
+ celerybeat.pid
53
+ coverage.xml
54
+ credentials.json
55
+ data/
56
+ db.sqlite3
57
+ db.sqlite3-journal
58
+ develop-eggs/
59
+ dist/
60
+ dmypy.json
61
+ docs/_build/
62
+ downloads/
63
+ eggs/
64
+ env.bak/
65
+ env/
66
+ fly.toml
67
+ htmlcov/
68
+ instance/
69
+ ipython_config.py
70
+ junk/
71
+ lib/
72
+ lib64/
73
+ local_settings.py
74
+ models/*.bin
75
+ nosetests.xml
76
+ notebooks/scratch/
77
+ parts/
78
+ pip-delete-this-directory.txt
79
+ pip-log.txt
80
+ pip-wheel-metadata/
81
+ profile_default/
82
+ sdist/
83
+ share/python-wheels/
84
+ storage
85
+ target/
86
+ token.json
87
+ var/
88
+ venv
89
+ venv.bak/
90
+ venv/
91
+ wheels/
.pre-commit-config.yaml ADDED
@@ -0,0 +1,63 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Don't know what this file is? See https://pre-commit.com/
2
+ # pip install pre-commit
3
+ # pre-commit install
4
+ # pre-commit autoupdate
5
+ # Apply to all files without commiting:
6
+ # pre-commit run --all-files
7
+ # I recommend running this until you pass all checks, and then commit.
8
+ # Fix what you need to and then let the pre-commit hooks resolve their conflicts.
9
+ # You may need to git add -u between runs.
10
+ exclude: "AI_CHANGELOG.md"
11
+ repos:
12
+ - repo: https://github.com/charliermarsh/ruff-pre-commit
13
+ rev: "v0.0.292"
14
+ hooks:
15
+ - id: ruff
16
+ args: [--fix, --exit-non-zero-on-fix, --ignore, E501]
17
+ - repo: https://github.com/koalaman/shellcheck-precommit
18
+ rev: v0.9.0
19
+ hooks:
20
+ - id: shellcheck
21
+ - repo: https://github.com/pre-commit/pre-commit-hooks
22
+ rev: v4.5.0
23
+ hooks:
24
+ - id: check-ast
25
+ - id: check-builtin-literals
26
+ - id: check-merge-conflict
27
+ - id: check-symlinks
28
+ - id: check-toml
29
+ - id: check-xml
30
+ - id: debug-statements
31
+ - id: check-case-conflict
32
+ - id: check-docstring-first
33
+ - id: check-executables-have-shebangs
34
+ - id: check-json
35
+ # - id: check-yaml
36
+ - id: debug-statements
37
+ - id: fix-byte-order-marker
38
+ - id: detect-private-key
39
+ - id: end-of-file-fixer
40
+ - id: trailing-whitespace
41
+ - id: mixed-line-ending
42
+ - id: requirements-txt-fixer
43
+ - repo: https://github.com/pre-commit/mirrors-mypy
44
+ rev: v1.6.0
45
+ hooks:
46
+ - id: mypy
47
+ - repo: https://github.com/asottile/add-trailing-comma
48
+ rev: v3.1.0
49
+ hooks:
50
+ - id: add-trailing-comma
51
+ - repo: https://github.com/dannysepler/rm_unneeded_f_str
52
+ rev: v0.2.0
53
+ hooks:
54
+ - id: rm-unneeded-f-str
55
+ - repo: https://github.com/psf/black
56
+ rev: 23.9.1
57
+ hooks:
58
+ - id: black
59
+ - repo: https://github.com/PyCQA/bandit
60
+ rev: 1.7.5
61
+ hooks:
62
+ - id: bandit
63
+ args: ["-x", "tests/*.py"]
Dockerfile ADDED
@@ -0,0 +1,20 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ FROM python:3.11-slim-bookworm
2
+
3
+ RUN adduser --uid 1000 --disabled-password --gecos '' appuser
4
+ USER 1000
5
+
6
+ ENV PYTHONDONTWRITEBYTECODE=1 \
7
+ PYTHONUNBUFFERED=1 \
8
+ PATH="/home/appuser/.local/bin:$PATH"
9
+
10
+ RUN pip install --user --no-cache-dir --upgrade pip
11
+ COPY ./requirements.txt /home/appuser/requirements.txt
12
+ RUN pip install --user --no-cache-dir --upgrade -r /home/appuser/requirements.txt
13
+
14
+ COPY geospatial-data-converter/ /home/appuser/geospatial-data-converter/
15
+
16
+ WORKDIR /workspace
17
+ EXPOSE 7860
18
+
19
+ CMD ["streamlit", "run", "/home/appuser/geospatial-data-converter/app.py", "--server.port", "7860", "--server.address", "0.0.0.0"]
20
+ #CMD ["/bin/bash"]
LICENSE ADDED
@@ -0,0 +1,9 @@
 
 
 
 
 
 
 
 
 
 
1
+ MIT License
2
+
3
+ Copyright (c) 2023 Joshua Sundance Bailey
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
6
+
7
+ The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
8
+
9
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
README.md ADDED
File without changes
bumpver.toml ADDED
@@ -0,0 +1,18 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ [bumpver]
2
+ current_version = "0.0.1"
3
+ version_pattern = "MAJOR.MINOR.PATCH"
4
+ commit_message = "bump version {old_version} -> {new_version}"
5
+ tag_message = "{new_version}"
6
+ tag_scope = "default"
7
+ pre_commit_hook = ""
8
+ post_commit_hook = ""
9
+ commit = true
10
+ tag = true
11
+ push = true
12
+
13
+ [bumpver.file_patterns]
14
+ "bumpver.toml" = [
15
+ 'current_version = "{version}"',
16
+ ]
17
+ "geospatial-data-converter/app.py" = ['__version__ = "{version}"']
18
+ "kubernetes/resources.yaml" = [' image: joshuasundance/geospatial-data-converter:{version}']
docker-compose.yml ADDED
@@ -0,0 +1,15 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ version: '3.8'
2
+
3
+ services:
4
+ geospatial-data-converter:
5
+ image: geospatial-data-converter:latest
6
+ build: .
7
+ ports:
8
+ - "${APP_PORT:-7860}:${APP_PORT:-7860}"
9
+ working_dir: /workspace
10
+ command: [
11
+ "streamlit", "run",
12
+ "/home/appuser/geospatial-data-converter/app.py",
13
+ "--server.port", "${APP_PORT:-7860}",
14
+ "--server.address", "0.0.0.0"
15
+ ]
geospatial-data-converter/.streamlit/config.toml ADDED
@@ -0,0 +1,6 @@
 
 
 
 
 
 
 
1
+ [theme]
2
+ primaryColor="#F63366"
3
+ backgroundColor="#FFFFFF"
4
+ secondaryBackgroundColor="#F0F2F6"
5
+ textColor="#262730"
6
+ font="sans serif"
geospatial-data-converter/app.py ADDED
@@ -0,0 +1,66 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import os
2
+
3
+ import streamlit as st
4
+
5
+ from utils import read_file, convert, output_format_dict
6
+
7
+ __version__ = "0.0.1"
8
+
9
+ # --- Initialization ---
10
+ st.set_page_config(
11
+ page_title=f"geospatial-data-converter v{__version__}",
12
+ page_icon="🌎",
13
+ )
14
+
15
+
16
+ # Upload the file
17
+ st.file_uploader(
18
+ "Choose a geospatial file",
19
+ key="uploaded_file",
20
+ type=["kml", "kmz", "geojson", "zip"],
21
+ )
22
+
23
+
24
+ if st.session_state.uploaded_file is not None:
25
+ fn_without_extension, _ = os.path.splitext(
26
+ os.path.basename(st.session_state.uploaded_file.name),
27
+ )
28
+
29
+ st.session_state.gdf = read_file(st.session_state.uploaded_file)
30
+
31
+ st.selectbox(
32
+ "Select output format",
33
+ output_format_dict.keys(),
34
+ key="output_format",
35
+ index=0,
36
+ )
37
+
38
+ if st.button("Convert"):
39
+ file_ext, dl_ext, mimetype = output_format_dict[st.session_state.output_format]
40
+ output_fn = f"{fn_without_extension}.{file_ext}"
41
+ dl_fn = f"{fn_without_extension}.{dl_ext}"
42
+
43
+ st.session_state.converted_data = convert(
44
+ gdf=st.session_state.gdf,
45
+ output_name=output_fn,
46
+ output_format=st.session_state.output_format,
47
+ )
48
+
49
+ st.download_button(
50
+ label="Download",
51
+ data=st.session_state.converted_data,
52
+ file_name=dl_fn,
53
+ mime=mimetype,
54
+ )
55
+
56
+ st.markdown(
57
+ "---\n"
58
+ f"## {fn_without_extension}\n"
59
+ f"### CRS: *{st.session_state.gdf.crs}*\n"
60
+ f"### Shape: *{st.session_state.gdf.shape}*\n"
61
+ "*(geometry omitted for display purposes)*",
62
+ )
63
+
64
+ display_df = st.session_state.gdf.drop(columns=["geometry"])
65
+
66
+ st.dataframe(display_df)
geospatial-data-converter/utils.py ADDED
@@ -0,0 +1,66 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import io
2
+ import os
3
+ import zipfile
4
+ from tempfile import TemporaryDirectory
5
+ from typing import BinaryIO
6
+
7
+ import geopandas as gpd
8
+
9
+ output_format_dict = {
10
+ "ESRI Shapefile": ("shp", "zip", "application/zip"), # must be zipped
11
+ "OpenFileGDB": ("gdb", "zip", "application/zip"), # must be zipped
12
+ "GeoJSON": ("geojson", "geojson", "application/geo+json"),
13
+ "CSV": ("csv", "csv", "text/csv"),
14
+ "KML": ("kml", "kml", "application/vnd.google-earth.kml+xml"),
15
+ }
16
+
17
+
18
+ def read_file(file: BinaryIO, *args, **kwargs) -> gpd.GeoDataFrame:
19
+ """Read a file and return a GeoDataFrame"""
20
+ if file.name.lower().endswith(".zip"):
21
+ with TemporaryDirectory() as tmp_dir:
22
+ tmp_file_path = os.path.join(tmp_dir, file.name)
23
+ with open(tmp_file_path, "wb") as tmp_file:
24
+ tmp_file.write(file.read())
25
+ return gpd.read_file(
26
+ f"zip://{tmp_file_path}",
27
+ *args,
28
+ engine="pyogrio",
29
+ **kwargs,
30
+ )
31
+ return gpd.read_file(file, *args, engine="pyogrio", **kwargs)
32
+
33
+
34
+ def zip_dir(directory: str) -> bytes:
35
+ """Zip a directory and return the bytes"""
36
+ zip_buffer = io.BytesIO()
37
+
38
+ with zipfile.ZipFile(zip_buffer, "w", zipfile.ZIP_DEFLATED) as zipf:
39
+ for root, dirs, files in os.walk(directory):
40
+ for file in files:
41
+ new_member = os.path.join(root, file)
42
+ zipf.write(
43
+ new_member,
44
+ os.path.relpath(new_member, directory),
45
+ )
46
+
47
+ return zip_buffer.getvalue()
48
+
49
+
50
+ def convert(gdf: gpd.GeoDataFrame, output_name: str, output_format: str) -> bytes:
51
+ """Convert a GeoDataFrame to the specified format"""
52
+ with TemporaryDirectory() as tmpdir:
53
+ out_path = os.path.join(tmpdir, output_name)
54
+
55
+ if output_format == "CSV":
56
+ gdf.to_csv(out_path)
57
+ else:
58
+ gdf.to_file(out_path, driver=output_format, engine="pyogrio")
59
+
60
+ if output_format in ("ESRI Shapefile", "OpenFileGDB"):
61
+ output_bytes = zip_dir(tmpdir)
62
+ else:
63
+ with open(out_path, "rb") as f:
64
+ output_bytes = f.read()
65
+
66
+ return output_bytes
kubernetes/deploy.sh ADDED
@@ -0,0 +1,6 @@
 
 
 
 
 
 
 
1
+ #!/bin/bash
2
+ set -euo pipefail
3
+ IFS=$'\n\t'
4
+
5
+ # Deploy to Kubernetes
6
+ kubectl apply -f kubernetes/resources.yaml
kubernetes/resources.yaml ADDED
@@ -0,0 +1,61 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ apiVersion: apps/v1
2
+ kind: Deployment
3
+ metadata:
4
+ name: geospatial-data-converter-deployment
5
+ spec:
6
+ replicas: 1
7
+ selector:
8
+ matchLabels:
9
+ app: geospatial-data-converter
10
+ template:
11
+ metadata:
12
+ labels:
13
+ app: geospatial-data-converter
14
+ spec:
15
+ containers:
16
+ - name: geospatial-data-converter
17
+ image: joshuasundance/geospatial-data-converter:0.0.1
18
+ imagePullPolicy: Always
19
+ resources:
20
+ requests:
21
+ cpu: "100m"
22
+ memory: "200Mi"
23
+ limits:
24
+ cpu: "500m"
25
+ memory: "500Mi"
26
+ securityContext:
27
+ runAsNonRoot: true
28
+ ---
29
+ apiVersion: v1
30
+ kind: Service
31
+ metadata:
32
+ name: geospatial-data-converter-service
33
+ # configure on Azure and uncomment below to use a vnet
34
+ # annotations:
35
+ # service.beta.kubernetes.io/azure-load-balancer-internal: "true"
36
+ # service.beta.kubernetes.io/azure-load-balancer-ipv4: vnet.ip.goes.here
37
+ # service.beta.kubernetes.io/azure-dns-label-name: "geospatial-data-converter"
38
+ spec:
39
+ selector:
40
+ app: geospatial-data-converter
41
+ ports:
42
+ - protocol: TCP
43
+ port: 80
44
+ targetPort: 7860
45
+ type: LoadBalancer
46
+ ---
47
+ apiVersion: networking.k8s.io/v1
48
+ kind: NetworkPolicy
49
+ metadata:
50
+ name: geospatial-data-converter-network-policy
51
+ spec:
52
+ podSelector:
53
+ matchLabels:
54
+ app: geospatial-data-converter
55
+ policyTypes:
56
+ - Ingress
57
+ ingress:
58
+ - from: [] # An empty array here means it will allow traffic from all sources.
59
+ ports:
60
+ - protocol: TCP
61
+ port: 7860
requirements.txt ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ geopandas==0.14.0
2
+ pyogrio==0.6.0
3
+ streamlit==1.27.2