├── .github
├── ISSUE_TEMPLATE
│ ├── bug_report.md
│ └── feature_request.md
└── workflows
│ ├── push_translations.yml
│ ├── release.yml
│ └── test-and-pre-release.yml
├── .gitignore
├── .gitmodules
├── .qgis-plugin-ci
├── CHANGELOG.md
├── GlobeBuilder
├── __init__.py
├── build.py
├── core
│ ├── __init__.py
│ ├── globe.py
│ ├── graticules.py
│ ├── halo.py
│ └── utils
│ │ ├── __init__.py
│ │ ├── geocoder.py
│ │ └── utils.py
├── definitions
│ ├── __init__.py
│ ├── projections.py
│ └── settings.py
├── logs
│ └── .gitignore
├── metadata.txt
├── plugin.py
├── pylintrc
├── resources
│ ├── data
│ │ ├── ne_110m_admin_0_countries.geojson
│ │ └── ne_50m_admin_0_countries.geojson
│ ├── icon.png
│ └── ui
│ │ └── globe_builder_dockwidget_base.ui
└── ui
│ ├── __init__.py
│ └── globe_builder_dockwidget.py
├── LICENSE
├── README.md
├── docs
└── development.md
├── images
├── icon.xcf
└── screenshots
│ ├── geocoding1.gif
│ ├── globe_view_gist.gif
│ └── layout1.gif
└── requirements-dev.txt
/.github/ISSUE_TEMPLATE/bug_report.md:
--------------------------------------------------------------------------------
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 | - Plugin: [e.g. 1.0]
28 | - QGIS [e.g. 3.14]
29 | - Python: [e.g. 3.8]
30 | - OS: [e.g. Windows 10, Fedora 32]
31 |
32 | **Additional context**
33 | Add any other context about the problem here.
34 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/feature_request.md:
--------------------------------------------------------------------------------
1 | ---
2 | name: Feature request
3 | about: Suggest an idea for this project
4 | title: ''
5 | labels: enhancement
6 | assignees: ''
7 |
8 | ---
9 |
10 | **Expected behaviour**
11 | A clear and concise description of what you'd like to happen if you do x.
12 |
13 | **Current behaviour**
14 | A clear and concise description of the current behaviour when you do x. If completely new feature, leave empty.
15 |
16 | **Describe alternatives you've considered**
17 | A clear and concise description of any alternative solutions or features you've considered.
18 |
19 | **Additional context**
20 | Add any other context or screenshots about the feature request here. If relevant please also provide version of the plugin and information on the system you are running it on.
21 |
--------------------------------------------------------------------------------
/.github/workflows/push_translations.yml:
--------------------------------------------------------------------------------
1 | name: Translations
2 |
3 | on:
4 | push:
5 | branches:
6 | - master
7 |
8 | jobs:
9 | push_translations:
10 | runs-on: ubuntu-latest
11 |
12 | steps:
13 | - uses: actions/checkout@v2
14 | with:
15 | submodules: true
16 |
17 | - name: Set up Python 3.8
18 | uses: actions/setup-python@v1
19 | with:
20 | python-version: 3.8
21 |
22 | - name: Install qgis-plugin-ci
23 | run: pip3 install qgis-plugin-ci
24 |
25 | - name: Push translations
26 | run: qgis-plugin-ci push-translation ${{ secrets.TRANSIFEX_TOKEN }}
27 |
--------------------------------------------------------------------------------
/.github/workflows/release.yml:
--------------------------------------------------------------------------------
1 | name: Release
2 |
3 | on:
4 | release:
5 | types: published
6 |
7 | jobs:
8 | plugin_dst:
9 | runs-on: ubuntu-latest
10 |
11 | steps:
12 | - uses: actions/checkout@v2
13 | with:
14 | submodules: true
15 |
16 | - name: Set up Python 3.8
17 | uses: actions/setup-python@v1
18 | with:
19 | python-version: 3.8
20 |
21 | - name: Install Qt lrelease
22 | run: sudo apt-get install qt5-default qttools5-dev-tools
23 |
24 | - name: Install qgis-plugin-ci
25 | run: pip3 install qgis-plugin-ci
26 |
27 | - name: Deploy plugin
28 | run: qgis-plugin-ci release ${GITHUB_REF/refs\/tags\//} --github-token ${{ secrets.GITHUB_TOKEN }} --osgeo-username Joonala --osgeo-password ${{ secrets.OSGEO_PASSWORD }} --transifex-token ${{ secrets.TRANSIFEX_TOKEN }} --disable-submodule-update
29 |
--------------------------------------------------------------------------------
/.github/workflows/test-and-pre-release.yml:
--------------------------------------------------------------------------------
1 | # workflow name
2 | name: Tests
3 |
4 | # Controls when the action will run. Triggers the workflow on push or pull request
5 | # events but only for the wanted branches
6 | on:
7 | pull_request:
8 | push:
9 | branches: [master, main]
10 |
11 | # A workflow run is made up of one or more jobs that can run sequentially or in parallel
12 | jobs:
13 | linux_tests:
14 | # The type of runner that the job will run on
15 | runs-on: ubuntu-latest
16 | strategy:
17 | matrix:
18 | # Remove unsupported versions and add more versions. Use LTR version in the cov_tests job
19 | docker_tags: [release-3_10, release-3_16, latest]
20 | fail-fast: false
21 |
22 | # Steps represent a sequence of tasks that will be executed as part of the job
23 | steps:
24 | # Checks-out your repository under $GITHUB_WORKSPACE, so your job can access it
25 | - uses: actions/checkout@v2
26 | with:
27 | submodules: true
28 |
29 | - name: Pull qgis
30 | run: docker pull qgis/qgis:${{ matrix.docker_tags }}
31 |
32 | # Runs all tests
33 | - name: Run tests
34 | run: >
35 | docker run --rm --net=host --volume `pwd`:/app -w=/app -e QGIS_PLUGIN_IN_CI=1 qgis/qgis:${{ matrix.docker_tags }} sh -c
36 | "pip3 install -qr requirements-dev.txt && xvfb-run -s '+extension GLX -screen 0 1024x768x24'
37 | pytest -v --cov=GlobeBuilder --cov-report=xml"
38 |
39 | # Upload coverage report. Will not work if the repo is private
40 | - name: Upload coverage to Codecov
41 | if: ${{ matrix.docker_tags == 'latest' && !github.event.repository.private }}
42 | uses: codecov/codecov-action@v1
43 | with:
44 | file: ./coverage.xml
45 | flags: unittests
46 | fail_ci_if_error: false # set to true when upload is working
47 | verbose: false
48 |
49 | windows_tests:
50 | runs-on: windows-latest
51 |
52 | steps:
53 | - uses: actions/checkout@v2
54 | with:
55 | submodules: true
56 |
57 | - name: Choco install qgis
58 | uses: crazy-max/ghaction-chocolatey@v1
59 | with:
60 | args: install qgis-ltr -y
61 |
62 | - name: Run tests
63 | shell: pwsh
64 | run: |
65 | $env:PATH="C:\Program Files\QGIS 3.16\bin;$env:PATH"
66 | $env:QGIS_PLUGIN_IN_CI=1
67 | python-qgis-ltr.bat -m pip install -qr requirements-dev.txt
68 | python-qgis-ltr.bat -m pytest -v
69 |
70 | pre-release:
71 | name: "Pre Release"
72 | runs-on: "ubuntu-latest"
73 | needs: [linux_tests, windows_tests]
74 |
75 | steps:
76 | - uses: hmarr/debug-action@v2
77 |
78 | - uses: "marvinpinto/action-automatic-releases@latest"
79 | if: ${{ github.event.pull_request }}
80 | with:
81 | repo_token: "${{ secrets.GITHUB_TOKEN }}"
82 | automatic_release_tag: "dev-pr"
83 | prerelease: true
84 | title: "Development Build made for PR #${{ github.event.number }}"
85 |
86 | - uses: "marvinpinto/action-automatic-releases@latest"
87 | if: ${{ github.event.after != github.event.before }}
88 | with:
89 | repo_token: "${{ secrets.GITHUB_TOKEN }}"
90 | automatic_release_tag: "dev"
91 | prerelease: true
92 | title: "Development Build made for master branch"
93 |
94 | - uses: actions/checkout@v2
95 | with:
96 | submodules: true
97 |
98 | - name: Set up Python 3.8
99 | uses: actions/setup-python@v1
100 | with:
101 | python-version: 3.8
102 |
103 | # Needed if the plugin is using Transifex, to have the lrelease command
104 | # - name: Install Qt lrelease
105 | # run: sudo apt-get update && sudo apt-get install qt5-default qttools5-dev-tools
106 |
107 | - name: Install qgis-plugin-ci
108 | run: pip3 install qgis-plugin-ci
109 |
110 | # When Transifex is wanted: --transifex-token ${{ secrets.TRANSIFEX_TOKEN }}
111 | - name: Deploy plugin
112 | if: ${{ github.event.pull_request }}
113 | run: qgis-plugin-ci release dev-pr --github-token ${{ secrets.GITHUB_TOKEN }} --disable-submodule-update
114 |
115 | # When Transifex is wanted: --transifex-token ${{ secrets.TRANSIFEX_TOKEN }}
116 | - name: Deploy plugin
117 | if: ${{ github.event.after != github.event.before }}
118 | run: qgis-plugin-ci release dev --github-token ${{ secrets.GITHUB_TOKEN }} --disable-submodule-update
119 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # Byte-compiled / optimized / DLL files
2 | __pycache__/
3 | *.py[cod]
4 | *$py.class
5 |
6 | # C extensions
7 | *.so
8 |
9 | # Distribution / packaging
10 | .Python
11 | env/
12 | build/
13 | develop-eggs/
14 | dist/
15 | downloads/
16 | eggs/
17 | .eggs/
18 | lib/
19 | lib64/
20 | parts/
21 | sdist/
22 | var/
23 | wheels/
24 | *.egg-info/
25 | .installed.cfg
26 | *.egg
27 |
28 | # PyInstaller
29 | # Usually these files are written by a python script from a template
30 | # before PyInstaller builds the exe, so as to inject date/other infos into it.
31 | *.manifest
32 | *.spec
33 |
34 | # Installer logs
35 | pip-log.txt
36 | pip-delete-this-directory.txt
37 |
38 | # Unit test / coverage reports
39 | htmlcov/
40 | .tox/
41 | GlobeBuilder/.coverage
42 | .coverage.*
43 | .cache
44 | nosetests.xml
45 | coverage.xml
46 | *.cover
47 | .hypothesis/
48 |
49 | # Translations
50 | *.mo
51 | *.pot
52 |
53 | # Django stuff:
54 | *.log
55 | local_settings.py
56 |
57 | # Flask stuff:
58 | instance/
59 | .webassets-cache
60 |
61 | # Scrapy stuff:
62 | .scrapy
63 |
64 | # Sphinx documentation
65 | docs/_build/
66 |
67 | # PyBuilder
68 | target/
69 |
70 | # Jupyter Notebook
71 | .ipynb_checkpoints
72 |
73 | # pyenv
74 | .python-version
75 |
76 | # celery beat schedule file
77 | celerybeat-schedule
78 |
79 | # SageMath parsed files
80 | *.sage.py
81 |
82 | # dotenv
83 | .env
84 |
85 | # virtualenv
86 | .venv
87 | venv/
88 | ENV/
89 |
90 | # Spyder project settings
91 | .spyderproject
92 | .spyproject
93 |
94 | # Rope project settings
95 | .ropeproject
96 |
97 | # mkdocs documentation
98 | /site
99 |
100 | # mypy
101 | .mypy_cache/
102 |
103 | *~
104 |
105 | # Projects specifig
106 | .idea
107 | GlobeBuilder.iml
108 | /icon_large.xcf
109 | /GlobeBuilder.zip
110 | /GlobeBuilder/i18n/
111 |
--------------------------------------------------------------------------------
/.gitmodules:
--------------------------------------------------------------------------------
1 | [submodule "GlobeBuilder/qgis_plugin_tools"]
2 | path = GlobeBuilder/qgis_plugin_tools
3 | url = https://github.com/GispoCoding/qgis_plugin_tools.git
4 |
--------------------------------------------------------------------------------
/.qgis-plugin-ci:
--------------------------------------------------------------------------------
1 | plugin_path: GlobeBuilder
2 | github_organization_slug: GispoCoding
3 | project_slug: GlobeBuilder
4 | transifex_coordinator: Joonalai
5 | transifex_organization: gispo
6 |
--------------------------------------------------------------------------------
/CHANGELOG.md:
--------------------------------------------------------------------------------
1 | # CHANGELOG
2 |
3 | ### 0.2.3 - 15/01/2020
4 |
5 | * Initial release
6 |
7 | ### 0.3.0 - 17/01/2020
8 |
9 | * Rounder halo with default of 64 segments.
10 | * New data sources: Sentinel-2 cloudless and Natural Earth 30 degree graticules
11 |
12 | ### 0.4.0 - 24/01/2020
13 |
14 | * Dockable window
15 | * Visualization customization
16 | * Option to center based on a layer
17 | * Bug fixes
18 |
19 | ### 0.5.0 - 11/02/2020
20 |
21 | * Possibility to add Globe to any layout
22 | * More accurate countries and graticules
23 | * Improved UI and customization
24 | * Simple tests
25 |
26 | ### 0.6.0 - 23/09/2020
27 |
28 | * More projections (might work depending on PROJ version)
29 | * Fixed bug with changing origin of the globe
30 | * Fixed rendering artefacts caused by graticules by generating those via processing algorithms
31 | * Various bug fixes
32 | * Use pytest and CI tests
33 | * Automatic release process with qgis-plugin-ci
34 |
35 | ##
36 |
--------------------------------------------------------------------------------
/GlobeBuilder/__init__.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 |
3 |
4 | # Copyright (C) 2020-2021 GlobeBuilder contributors.
5 | #
6 | #
7 | # This file is part of GlobeBuilder.
8 | #
9 | # GlobeBuilder is free software: you can redistribute it and/or modify
10 | # it under the terms of the GNU General Public License as published by
11 | # the Free Software Foundation, either version 2 of the License, or
12 | # (at your option) any later version.
13 | #
14 | # GlobeBuilder is distributed in the hope that it will be useful,
15 | # but WITHOUT ANY WARRANTY; without even the implied warranty of
16 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17 | # GNU General Public License for more details.
18 | #
19 | # You should have received a copy of the GNU General Public License
20 | # along with GlobeBuilder. If not, see .
21 |
22 | import os
23 |
24 | from .qgis_plugin_tools.infrastructure.debugging import setup_pydevd
25 |
26 | if os.environ.get('QGIS_PLUGIN_USE_DEBUGGER') == 'pydevd':
27 | if os.environ.get('IN_TESTS', "0") != "1" and os.environ.get('QGIS_PLUGIN_IN_CI', "0") != "1":
28 | setup_pydevd()
29 |
30 |
31 | # noinspection PyPep8Naming
32 | def classFactory(iface): # pylint: disable=invalid-name
33 | """Load GlobeBuilder class from file GlobeBuilder.
34 |
35 | :param iface: A QGIS interface instance.
36 | :type iface: QgsInterface
37 | """
38 | #
39 | from .plugin import GlobeBuilder
40 | return GlobeBuilder(iface)
41 |
--------------------------------------------------------------------------------
/GlobeBuilder/build.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python
2 | # -*- coding: utf-8 -*-
3 |
4 |
5 | # Copyright (C) 2020-2021 GlobeBuilder contributors.
6 | #
7 | #
8 | # This file is part of GlobeBuilder.
9 | #
10 | # GlobeBuilder is free software: you can redistribute it and/or modify
11 | # it under the terms of the GNU General Public License as published by
12 | # the Free Software Foundation, either version 2 of the License, or
13 | # (at your option) any later version.
14 | #
15 | # GlobeBuilder is distributed in the hope that it will be useful,
16 | # but WITHOUT ANY WARRANTY; without even the implied warranty of
17 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
18 | # GNU General Public License for more details.
19 | #
20 | # You should have received a copy of the GNU General Public License
21 | # along with GlobeBuilder. If not, see .
22 |
23 | import glob
24 | from typing import List
25 |
26 | from qgis_plugin_tools.infrastructure.plugin_maker import PluginMaker
27 |
28 | """
29 | #################################################
30 | # Edit the following to match the plugin
31 | #################################################
32 | """
33 |
34 | py_files = [
35 | fil
36 | for fil in glob.glob("**/*.py", recursive=True)
37 | if "test/" not in fil and "test\\" not in fil
38 | ]
39 | locales = ["fi"]
40 | profile = "dev"
41 | ui_files = list(glob.glob("**/*.ui", recursive=True))
42 | resources = list(glob.glob("**/*.qrc", recursive=True))
43 | extra_dirs = ["resources"]
44 | compiled_resources: List[str] = []
45 |
46 | PluginMaker(
47 | py_files=py_files,
48 | ui_files=ui_files,
49 | resources=resources,
50 | extra_dirs=extra_dirs,
51 | compiled_resources=compiled_resources,
52 | locales=locales,
53 | profile=profile,
54 | )
55 |
--------------------------------------------------------------------------------
/GlobeBuilder/core/__init__.py:
--------------------------------------------------------------------------------
1 | # Copyright (C) 2020-2021 GlobeBuilder contributors.
2 | #
3 | #
4 | # This file is part of GlobeBuilder.
5 | #
6 | # GlobeBuilder is free software: you can redistribute it and/or modify
7 | # it under the terms of the GNU General Public License as published by
8 | # the Free Software Foundation, either version 2 of the License, or
9 | # (at your option) any later version.
10 | #
11 | # GlobeBuilder is distributed in the hope that it will be useful,
12 | # but WITHOUT ANY WARRANTY; without even the implied warranty of
13 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 | # GNU General Public License for more details.
15 | #
16 | # You should have received a copy of the GNU General Public License
17 | # along with GlobeBuilder. If not, see .
18 |
--------------------------------------------------------------------------------
/GlobeBuilder/core/globe.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 |
3 |
4 | # Copyright (C) 2020-2021 GlobeBuilder contributors.
5 | #
6 | #
7 | # This file is part of GlobeBuilder.
8 | #
9 | # GlobeBuilder is free software: you can redistribute it and/or modify
10 | # it under the terms of the GNU General Public License as published by
11 | # the Free Software Foundation, either version 2 of the License, or
12 | # (at your option) any later version.
13 | #
14 | # GlobeBuilder is distributed in the hope that it will be useful,
15 | # but WITHOUT ANY WARRANTY; without even the implied warranty of
16 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17 | # GNU General Public License for more details.
18 | #
19 | # You should have received a copy of the GNU General Public License
20 | # along with GlobeBuilder. If not, see .
21 |
22 | import logging
23 | import os
24 |
25 | from PyQt5.QtGui import QColor
26 | from qgis.core import (QgsProject, QgsCoordinateReferenceSystem, Qgis, QgsRasterLayer, QgsVectorLayer,
27 | QgsMapThemeCollection, QgsLayoutItemMap,
28 | QgsMapSettings, QgsRectangle, QgsLayoutPoint, QgsUnitTypes, QgsLayoutSize)
29 |
30 | from .graticules import Graticules
31 | from .halo import Halo
32 | from .utils.utils import set_selection_based_style, get_feature_ids_that_intersect_bbox
33 | from ..definitions.projections import Projections
34 | from ..definitions.settings import (LayerConnectionType, S2CLOUDLESS_WMTS_URL, LOCAL_DATA_DIR,
35 | DEFAULT_LAYER_CONNECTION_TYPE, NATURAL_EARTH_BASE_URL,
36 | DEFAULT_ORIGIN,
37 | WGS84)
38 | from ..qgis_plugin_tools.tools.custom_logging import bar_msg
39 | from ..qgis_plugin_tools.tools.i18n import tr
40 | from ..qgis_plugin_tools.tools.resources import plugin_name
41 | from ..qgis_plugin_tools.tools.settings import get_setting
42 |
43 | LOGGER = logging.getLogger(plugin_name())
44 |
45 |
46 | class Globe:
47 | THEME_NAME = tr('Globe')
48 | GROUP_NAME = tr('Globe')
49 |
50 | def __init__(self, iface, origin=DEFAULT_ORIGIN, projection=Projections.AZIMUTHAL_ORTHOGRAPHIC):
51 | self.iface = iface
52 | self.origin = origin
53 | self.projection = projection
54 | # noinspection PyArgumentList
55 | self.qgis_instance = QgsProject.instance()
56 |
57 | @property
58 | def group(self):
59 | root = self.qgis_instance.layerTreeRoot()
60 | groups = tuple(filter(lambda g: g.name() == Globe.GROUP_NAME,
61 | filter(root.isGroup, root.children())))
62 | if len(groups) == 1:
63 | return groups[0]
64 | else:
65 | return root.addGroup(Globe.GROUP_NAME)
66 |
67 | def delete_group(self):
68 | group = self.group
69 | root = self.qgis_instance.layerTreeRoot()
70 | if group is not None:
71 | for child in group.children():
72 | dump = child.dump()
73 | self.qgis_instance.removeMapLayer(dump.split("=")[-1].strip())
74 | root.removeChildNode(group)
75 |
76 | def set_origin(self, coordinates):
77 | if coordinates is not None:
78 | self.origin = coordinates
79 |
80 | def set_projection(self, projection):
81 | self.projection = projection
82 |
83 | def set_group_visibility(self, is_visible):
84 | self.group.setItemVisibilityCheckedRecursive(is_visible)
85 |
86 | def load_data(self, load_s2, load_countries, load_graticules, countries_color, graticules_color,
87 | intersecting_countries_color, counties_res, graticules_res):
88 | existing_layer_names = self.get_existing_layer_names()
89 | s2_cloudless_layer_name = tr(u'S2 Cloudless 2018')
90 | if load_s2 and s2_cloudless_layer_name not in existing_layer_names:
91 | s2_layer = QgsRasterLayer(S2CLOUDLESS_WMTS_URL, s2_cloudless_layer_name, "wms")
92 | if s2_layer.isValid():
93 | self.insert_layer_to_group(s2_layer)
94 | else:
95 | LOGGER.warning(tr(u"Could not add Sentinel 2 Cloudless layer"), extra=bar_msg())
96 |
97 | ne_data = {}
98 | if load_countries:
99 | def style_countries(layer):
100 | if intersecting_countries_color is None:
101 | layer.renderer().symbol().setColor(countries_color)
102 | else:
103 | set_selection_based_style(layer, intersecting_countries_color, countries_color)
104 | ids = get_feature_ids_that_intersect_bbox(layer, self.iface.mapCanvas().extent(),
105 | self.qgis_instance.crs())
106 | layer.select(ids)
107 |
108 | ne_data[tr(u'Countries')] = (
109 | 'ne_{}_admin_0_countries.geojson'.format(counties_res), lambda l: style_countries(l))
110 |
111 | if len(ne_data):
112 | self.load_natural_earth_data(ne_data)
113 |
114 | gra = Graticules(graticules_res)
115 | if load_graticules and gra.LAYER_NAME not in existing_layer_names:
116 | graticule_layer = gra.create_graticules(graticules_color)
117 | if graticule_layer.isValid():
118 | self.insert_layer_to_group(graticule_layer)
119 | else:
120 | LOGGER.warning(tr('Could not add graticules'), extra=bar_msg())
121 |
122 | self.iface.mapCanvas().refresh()
123 |
124 | def load_natural_earth_data(self, ne_data):
125 | # TODO: resolution
126 | existing_layer_names = self.get_existing_layer_names()
127 |
128 | connection_type = LayerConnectionType(
129 | get_setting("layerConnectionType", DEFAULT_LAYER_CONNECTION_TYPE.value, int))
130 |
131 | if connection_type == LayerConnectionType.local:
132 | root = LOCAL_DATA_DIR
133 | else:
134 | root = NATURAL_EARTH_BASE_URL
135 |
136 | for name, data in ne_data.items():
137 | source, styling_method = data
138 | if name not in existing_layer_names:
139 | layer = QgsVectorLayer(os.path.join(root, source), name, "ogr")
140 | if layer is None:
141 | layer = QgsVectorLayer(os.path.join(LOCAL_DATA_DIR, source), name, "ogr")
142 | if layer is None:
143 | self.iface.messageBar().pushMessage(
144 | tr(u"Could not load Natural Earth layer '{}'", name),
145 | level=Qgis.Warning, duration=3)
146 | else:
147 | layer.setName(name)
148 | self.insert_layer_to_group(layer)
149 |
150 | else:
151 | layer = self.qgis_instance.mapLayersByName(name)[0]
152 |
153 | if styling_method is not None and layer is not None:
154 | styling_method(layer)
155 | layer.triggerRepaint()
156 |
157 | def insert_layer_to_group(self, layer, index=0):
158 | self.qgis_instance.addMapLayer(layer, False)
159 | self.group.insertLayer(index, layer)
160 |
161 | def change_project_projection(self):
162 | # Change to wgs84 to activate the changes in origin
163 | self.qgis_instance.setCrs(WGS84)
164 | proj_string = self.projection.value.proj_str(self.origin)
165 | crs = QgsCoordinateReferenceSystem()
166 | crs.createFromProj(proj_string)
167 | self.qgis_instance.setCrs(crs)
168 |
169 | def change_temporarily_to_globe_projection(self):
170 | crs = self.qgis_instance.crs()
171 | self.change_project_projection()
172 | self.qgis_instance.setCrs(crs)
173 |
174 | def change_background_color(self, new_background_color):
175 | # Write it to the project (will still need to be saved!)
176 | self.qgis_instance.writeEntry("Gui", "/CanvasColorRedPart", new_background_color.red())
177 | self.qgis_instance.writeEntry("Gui", "/CanvasColorGreenPart", new_background_color.green())
178 | self.qgis_instance.writeEntry("Gui", "/CanvasColorBluePart", new_background_color.blue())
179 |
180 | # And apply for the current session
181 | self.iface.mapCanvas().setCanvasColor(new_background_color)
182 | self.iface.mapCanvas().refresh()
183 |
184 | # noinspection PyArgumentList
185 | @staticmethod
186 | def get_existing_layer_names():
187 | return [layer.name() for layer in QgsProject.instance().mapLayers().values()]
188 |
189 | # noinspection PyArgumentList
190 | def add_halo(self, use_effects, stroke_color, fill_color=None, halo_with_fill=False):
191 | halo = Halo(self.iface, self.origin, self.projection)
192 |
193 | if halo_with_fill:
194 | self.add_halo(True, stroke_color)
195 | else:
196 | [self.qgis_instance.removeMapLayer(lyr.id()) for lyr in
197 | self.qgis_instance.mapLayersByName(halo.LAYER_NAME)]
198 |
199 | layer, index = halo.create_halo_layer(use_effects, stroke_color, fill_color)
200 | self.insert_layer_to_group(layer, index)
201 |
202 | def refresh_theme(self):
203 | theme_collection = self.qgis_instance.mapThemeCollection()
204 | layers = [layer.layer() for layer in self.group.findLayers()]
205 | if Globe.THEME_NAME in theme_collection.mapThemes():
206 | theme_collection.removeMapTheme(Globe.THEME_NAME)
207 | if len(layers):
208 | map_theme_record = QgsMapThemeCollection.MapThemeRecord()
209 | map_theme_record.setLayerRecords([QgsMapThemeCollection.MapThemeLayerRecord(layer) for layer in layers])
210 | theme_collection.insert(Globe.THEME_NAME, map_theme_record)
211 |
212 | def add_to_layout(self, layout, background_color=QColor(255, 255, 255, 0), size=80):
213 | """
214 | Inspired by https://opensourceoptions.com/blog/pyqgis-create-and-print-a-map-layout-with-python/
215 | """
216 | layers = [layer.layer() for layer in self.group.findLayers()]
217 | # create map item in the layout
218 | map = QgsLayoutItemMap(layout)
219 | map.setRect(20, 20, 20, 20)
220 | # set the map extent
221 | ms = QgsMapSettings()
222 | ms.setLayers(layers) # set layers to be mapped
223 | crs = QgsCoordinateReferenceSystem()
224 | crs.createFromProj(self.projection.value.proj_str(self.origin))
225 | map.setCrs(crs)
226 | map.setFollowVisibilityPreset(True)
227 | map.setFollowVisibilityPresetName(Globe.THEME_NAME)
228 | rect = QgsRectangle(ms.fullExtent())
229 |
230 | ms.setExtent(rect)
231 | map.setExtent(rect)
232 | map.setBackgroundColor(background_color)
233 | layout.addLayoutItem(map)
234 | map.attemptMove(QgsLayoutPoint(5, 20, QgsUnitTypes.LayoutMillimeters))
235 | map.attemptResize(QgsLayoutSize(size, size, QgsUnitTypes.LayoutMillimeters))
236 |
--------------------------------------------------------------------------------
/GlobeBuilder/core/graticules.py:
--------------------------------------------------------------------------------
1 | # Copyright (C) 2020-2021 GlobeBuilder contributors.
2 | #
3 | #
4 | # This file is part of GlobeBuilder.
5 | #
6 | # GlobeBuilder is free software: you can redistribute it and/or modify
7 | # it under the terms of the GNU General Public License as published by
8 | # the Free Software Foundation, either version 2 of the License, or
9 | # (at your option) any later version.
10 | #
11 | # GlobeBuilder is distributed in the hope that it will be useful,
12 | # but WITHOUT ANY WARRANTY; without even the implied warranty of
13 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 | # GNU General Public License for more details.
15 | #
16 | # You should have received a copy of the GNU General Public License
17 | # along with GlobeBuilder. If not, see .
18 |
19 | from PyQt5.QtGui import QColor
20 | from qgis.core import (QgsProcessingFeedback, QgsVectorLayer, QgsFillSymbol,
21 | QgsSymbolLayer, QgsFeatureRenderer)
22 |
23 | from ..definitions.settings import WGS84
24 | from ..qgis_plugin_tools.tools.i18n import tr
25 |
26 | '''
27 | alg:
28 | create grid!
29 | densify grid!
30 | '''
31 |
32 |
33 | class Graticules:
34 | LAYER_NAME = tr('Graticules')
35 |
36 | def __init__(self, spacing=30, num_vertices=100) -> None:
37 | self.spacing = spacing
38 | self.num_vertices = num_vertices
39 | self.feedback = QgsProcessingFeedback()
40 |
41 | def create_graticules(self, stroke_color):
42 | tmp_grid_layer = self._create_grid_layer()
43 | layer = self._create_graticule_layer(tmp_grid_layer)
44 | self._set_styles(layer, stroke_color)
45 | return layer
46 |
47 | @staticmethod
48 | def _set_styles(layer, stroke_color):
49 | # Set transparent fill
50 |
51 | renderer: QgsFeatureRenderer = layer.renderer()
52 | props = {'color': 'white'}
53 | # noinspection PyArgumentList
54 | fill_symbol = QgsFillSymbol.createSimple(props)
55 | fill_symbol_layer: QgsSymbolLayer = fill_symbol.symbolLayers()[0]
56 | fill_color: QColor = fill_symbol_layer.fillColor()
57 | fill_color.setAlpha(0)
58 | fill_symbol_layer.setFillColor(fill_color)
59 | fill_symbol_layer.setStrokeColor(stroke_color)
60 | # noinspection PyUnresolvedReferences
61 | renderer.setSymbol(fill_symbol)
62 | layer.triggerRepaint()
63 |
64 | def _create_graticule_layer(self, tmp_grid_layer) -> QgsVectorLayer:
65 | # noinspection PyUnresolvedReferences
66 | import processing
67 | params = {
68 | 'INPUT': tmp_grid_layer,
69 | 'OUTPUT': f'memory:{self.LAYER_NAME}', 'VERTICES': self.num_vertices}
70 | res = processing.run('native:densifygeometries', params, feedback=self.feedback)
71 | layer: QgsVectorLayer = res['OUTPUT']
72 |
73 | return layer
74 |
75 | def _create_grid_layer(self) -> QgsVectorLayer:
76 | # noinspection PyUnresolvedReferences
77 | import processing
78 | params = {'CRS': WGS84,
79 | 'EXTENT': '-180,180,-90,90 [EPSG:4326]', 'HOVERLAY': 0, 'HSPACING': self.spacing,
80 | 'OUTPUT': 'memory:tmp_grid', 'TYPE': 2, 'VOVERLAY': 0, 'VSPACING': self.spacing}
81 | res = processing.run('native:creategrid', params, feedback=self.feedback)
82 | tmp_grid_layer = res['OUTPUT']
83 | return tmp_grid_layer
84 |
--------------------------------------------------------------------------------
/GlobeBuilder/core/halo.py:
--------------------------------------------------------------------------------
1 | # Copyright (C) 2020-2021 GlobeBuilder contributors.
2 | #
3 | #
4 | # This file is part of GlobeBuilder.
5 | #
6 | # GlobeBuilder is free software: you can redistribute it and/or modify
7 | # it under the terms of the GNU General Public License as published by
8 | # the Free Software Foundation, either version 2 of the License, or
9 | # (at your option) any later version.
10 | #
11 | # GlobeBuilder is distributed in the hope that it will be useful,
12 | # but WITHOUT ANY WARRANTY; without even the implied warranty of
13 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 | # GNU General Public License for more details.
15 | #
16 | # You should have received a copy of the GNU General Public License
17 | # along with GlobeBuilder. If not, see .
18 |
19 | from qgis.core import (QgsFillSymbol, QgsEffectStack, QgsDropShadowEffect, QgsInnerShadowEffect,
20 | QgsGeometryGeneratorSymbolLayer, QgsVectorLayer, QgsFeature, QgsGeometry, QgsPointXY,
21 | QgsCoordinateTransform, QgsProject)
22 | from qgis.gui import QgisInterface
23 |
24 | from ..definitions.projections import Projections
25 | from ..definitions.settings import (TRANSPARENT_COLOR, HaloDrawMethod, EARTH_RADIUS, DEFAULT_HALO_DRAW_METHOD,
26 | DEFAULT_NUMBER_OF_SEGMENTS, WGS84)
27 | from ..qgis_plugin_tools.tools.i18n import tr
28 | from ..qgis_plugin_tools.tools.settings import get_setting
29 |
30 |
31 | class Halo:
32 | LAYER_NAME = tr('Halo')
33 |
34 | def __init__(self, iface: QgisInterface, origin, projection) -> None:
35 | self.iface = iface
36 | self.origin = origin
37 | self.projection = projection
38 |
39 | def create_halo_layer(self, use_effects, stroke_color, fill_color=None):
40 | # noinspection PyArgumentList
41 | qgis_instance: QgsProject = QgsProject.instance()
42 |
43 | draw_method = HaloDrawMethod(
44 | get_setting("haloDrawMethod", DEFAULT_HALO_DRAW_METHOD.value, str))
45 | proj_string = self.projection.value.proj_str(self.origin)
46 | # Block signals required to prevent the pop up asking about the crs change
47 | self.iface.mainWindow().blockSignals(True)
48 | layer = QgsVectorLayer(draw_method.value, self.LAYER_NAME, "memory")
49 | crs = layer.crs()
50 | crs.createFromProj(proj_string)
51 | layer.setCrs(crs)
52 | self.iface.mainWindow().blockSignals(False)
53 |
54 | feature = QgsFeature()
55 | if self.projection == Projections.AZIMUTHAL_ORTHOGRAPHIC:
56 | # noinspection PyArgumentList
57 | geom = QgsGeometry.fromPointXY(QgsPointXY(self.origin['lat'], self.origin['lon']))
58 | if draw_method == HaloDrawMethod.buffered_point:
59 | geom = geom.buffer(EARTH_RADIUS, DEFAULT_NUMBER_OF_SEGMENTS)
60 | else:
61 | geom = self.create_grid_halo(layer.crs())
62 |
63 | feature.setGeometry(geom)
64 | provider = layer.dataProvider()
65 | layer.startEditing()
66 | provider.addFeatures([feature])
67 | layer.commitChanges()
68 |
69 | # Assign styles and to map (but not toc yet)
70 | self.set_halo_styles(layer, draw_method, stroke_color, use_effects, fill_color)
71 | qgis_instance.addMapLayer(layer, False)
72 |
73 | index = 0 if use_effects else -1
74 | return layer, index
75 |
76 | # noinspection PyCallByClass
77 | @staticmethod
78 | def set_halo_styles(layer, draw_method, stroke_color, use_effects, fill_color=None):
79 | renderer = layer.renderer()
80 | sym = renderer.symbol()
81 |
82 | props = {'color': 'blue'}
83 | # noinspection PyArgumentList
84 | fill_symbol = QgsFillSymbol.createSimple(props)
85 | fill_symbol_layer = fill_symbol.symbolLayers()[0]
86 | fill_symbol_layer.setStrokeColor(stroke_color)
87 | if fill_color is not None:
88 | fill_symbol_layer.setColor(fill_color)
89 | elif not use_effects:
90 | fill_symbol_layer.setColor(TRANSPARENT_COLOR)
91 |
92 | if use_effects:
93 | # Assign effects
94 | effect_stack = QgsEffectStack()
95 | drop_shdw = QgsDropShadowEffect()
96 | drop_shdw.setColor(stroke_color)
97 | inner_shdw = QgsInnerShadowEffect()
98 | inner_shdw.setColor(stroke_color)
99 | effect_stack.appendEffect(drop_shdw)
100 | effect_stack.appendEffect(inner_shdw)
101 |
102 | fill_symbol_layer.setPaintEffect(effect_stack)
103 | # noinspection PyArgumentList
104 | if draw_method == HaloDrawMethod.buffered_point:
105 | renderer.setSymbol(fill_symbol)
106 | else:
107 | # noinspection PyCallByClass, PyArgumentList
108 | geom_generator_sl = QgsGeometryGeneratorSymbolLayer.create({
109 | 'SymbolType': 'Fill',
110 | 'geometryModifier': 'buffer($geometry, {:d})'.format(EARTH_RADIUS)
111 | })
112 | geom_generator_sl.setSubSymbol(fill_symbol)
113 | sym.changeSymbolLayer(0, geom_generator_sl)
114 |
115 | layer.triggerRepaint()
116 | return layer
117 |
118 | @staticmethod
119 | def create_grid_halo(crs):
120 | min_x = -180
121 | min_y = -90
122 | max_x = 180
123 | max_y = 90
124 | step = 2
125 | coords = []
126 | for y in range(min_y, max_y + step, step):
127 | coords.append((min_x, y))
128 | for x in range(min_x + step, max_x + step, step):
129 | coords.append((x, max_y))
130 | for y in reversed(range(min_y, max_y + step, step)):
131 | coords.append((max_x, y))
132 | for x in reversed(range(min_x + step, max_x + step, step)):
133 | coords.append((x, min_y))
134 | coords.append(coords[0])
135 | # noinspection PyCallByClass,PyArgumentList
136 | geom = QgsGeometry.fromPolygonXY([[QgsPointXY(pair[0], pair[1]) for pair in coords]]).asQPolygonF()
137 | # noinspection PyArgumentList
138 | transformer = QgsCoordinateTransform(WGS84, crs, QgsProject.instance())
139 | transformer.transformPolygon(geom)
140 | # noinspection PyArgumentList
141 | return QgsGeometry.fromQPolygonF(geom)
142 |
--------------------------------------------------------------------------------
/GlobeBuilder/core/utils/__init__.py:
--------------------------------------------------------------------------------
1 | # Copyright (C) 2020-2021 GlobeBuilder contributors.
2 | #
3 | #
4 | # This file is part of GlobeBuilder.
5 | #
6 | # GlobeBuilder is free software: you can redistribute it and/or modify
7 | # it under the terms of the GNU General Public License as published by
8 | # the Free Software Foundation, either version 2 of the License, or
9 | # (at your option) any later version.
10 | #
11 | # GlobeBuilder is distributed in the hope that it will be useful,
12 | # but WITHOUT ANY WARRANTY; without even the implied warranty of
13 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 | # GNU General Public License for more details.
15 | #
16 | # You should have received a copy of the GNU General Public License
17 | # along with GlobeBuilder. If not, see .
18 |
--------------------------------------------------------------------------------
/GlobeBuilder/core/utils/geocoder.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 |
3 |
4 | # Copyright (C) 2020-2021 GlobeBuilder contributors.
5 | #
6 | #
7 | # This file is part of GlobeBuilder.
8 | #
9 | # GlobeBuilder is free software: you can redistribute it and/or modify
10 | # it under the terms of the GNU General Public License as published by
11 | # the Free Software Foundation, either version 2 of the License, or
12 | # (at your option) any later version.
13 | #
14 | # GlobeBuilder is distributed in the hope that it will be useful,
15 | # but WITHOUT ANY WARRANTY; without even the implied warranty of
16 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17 | # GNU General Public License for more details.
18 | #
19 | # You should have received a copy of the GNU General Public License
20 | # along with GlobeBuilder. If not, see .
21 |
22 | import json
23 | import logging
24 |
25 | from PyQt5.QtCore import QSettings, QUrl
26 | from PyQt5.QtNetwork import QNetworkRequest, QNetworkReply, QNetworkAccessManager
27 | from qgis.core import Qgis, QgsMessageLog
28 |
29 | from ...definitions.settings import NOMINATIM_URL, MAX_NAME_PARTS
30 | from ...qgis_plugin_tools.tools.resources import plugin_name
31 |
32 | LOGGER = logging.getLogger(plugin_name())
33 |
34 |
35 | class Geocoder:
36 | # noinspection PyUnresolvedReferences
37 | def __init__(self, callback):
38 | self.network_access_manager = QNetworkAccessManager()
39 | self.network_access_manager.finished.connect(self.on_search_response)
40 | self.callback = callback
41 |
42 | def geocode(self, query, max_results):
43 | params = {
44 | 'query': query,
45 | 'limit': max_results
46 | }
47 | url = NOMINATIM_URL.format(**params)
48 |
49 | # http://osgeo-org.1560.x6.nabble.com/QGIS-Developer-Do-we-have-a-User-Agent-string-for-QGIS-td5360740.html
50 | user_agent = QSettings().value("/qgis/networkAndProxy/userAgent", "Mozilla/5.0")
51 | user_agent += " " if len(user_agent) else ""
52 | # noinspection PyUnresolvedReferences
53 | user_agent += "QGIS/{}".format(Qgis.QGIS_VERSION_INT)
54 | user_agent += " GlobeBuilder-plugin"
55 |
56 | QgsMessageLog.logMessage(url, "GlobeBuilder", Qgis.Info)
57 | geocoding_request = QNetworkRequest(QUrl(url))
58 | # https://www.riverbankcomputing.com/pipermail/pyqt/2016-May/037514.html
59 | geocoding_request.setRawHeader(b"User-Agent", bytes(user_agent, "utf-8"))
60 | self.network_access_manager.get(geocoding_request)
61 |
62 | def on_search_response(self, search_result):
63 | error = search_result.error()
64 | result_dict = {}
65 | if error == QNetworkReply.NoError:
66 | bytes_string = search_result.readAll()
67 | data_string = str(bytes_string, 'utf-8')
68 |
69 | result = json.loads(data_string)
70 |
71 | for f in result['features']:
72 | name_parts = f['properties']['display_name'].split(",")
73 | name = "{} ({})".format(",".join(
74 | name_parts[0:min(MAX_NAME_PARTS, len(name_parts) - 1)]),
75 | name_parts[-1].strip()) if len(name_parts) > 1 else name_parts[0]
76 | coordinates = f['geometry']['coordinates']
77 | result_dict[name] = coordinates
78 | else:
79 | LOGGER.warning(str(error))
80 | LOGGER.warning(search_result.errorString())
81 |
82 | self.callback(result_dict)
83 |
--------------------------------------------------------------------------------
/GlobeBuilder/core/utils/utils.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 |
3 |
4 | # Copyright (C) 2020-2021 GlobeBuilder contributors.
5 | #
6 | #
7 | # This file is part of GlobeBuilder.
8 | #
9 | # GlobeBuilder is free software: you can redistribute it and/or modify
10 | # it under the terms of the GNU General Public License as published by
11 | # the Free Software Foundation, either version 2 of the License, or
12 | # (at your option) any later version.
13 | #
14 | # GlobeBuilder is distributed in the hope that it will be useful,
15 | # but WITHOUT ANY WARRANTY; without even the implied warranty of
16 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17 | # GNU General Public License for more details.
18 | #
19 | # You should have received a copy of the GNU General Public License
20 | # along with GlobeBuilder. If not, see .
21 |
22 | from qgis.core import QgsPrintLayout, QgsFillSymbol, QgsRuleBasedRenderer, QgsFeatureRequest, \
23 | QgsCoordinateTransformContext, QgsCoordinateTransform
24 |
25 | from ...definitions.settings import WGS84
26 | from ...qgis_plugin_tools.tools.i18n import tr
27 |
28 |
29 | def create_layout(layout_name, qgis_instance):
30 | manager = qgis_instance.layoutManager()
31 | layouts_list = manager.printLayouts()
32 | # remove any duplicate layouts
33 | for layout in layouts_list:
34 | if layout.name() == layout_name:
35 | manager.removeLayout(layout)
36 | layout = QgsPrintLayout(qgis_instance)
37 | layout.initializeDefaults()
38 | layout.setName(layout_name)
39 | manager.addLayout(layout)
40 | return layout
41 |
42 |
43 | def set_selection_based_style(layer, s_color, else_color):
44 | # noinspection PyCallByClass,PyArgumentList
45 | fill_for_selected = QgsFillSymbol.createSimple({'color': 'blue'})
46 | fill_for_selected.setColor(s_color)
47 | rule_s = QgsRuleBasedRenderer.Rule(fill_for_selected, label=tr(u"Selected"),
48 | filterExp="is_selected()")
49 |
50 | fill_for_else = fill_for_selected.clone()
51 | fill_for_else.setColor(else_color)
52 | rule_else = QgsRuleBasedRenderer.Rule(fill_for_else, label=tr(u"Not Selected"),
53 | elseRule=True)
54 |
55 | renderer = QgsRuleBasedRenderer(QgsRuleBasedRenderer.Rule(None))
56 | root_rule = renderer.rootRule()
57 | root_rule.appendChild(rule_s)
58 | root_rule.appendChild(rule_else)
59 |
60 | layer.setRenderer(renderer)
61 | return layer
62 |
63 |
64 | def transform_to_wgs84(geom, crs, qgis_instance):
65 | transformer = QgsCoordinateTransform(crs, WGS84, qgis_instance)
66 | return transformer.transform(geom)
67 |
68 |
69 | def get_feature_ids_that_intersect_bbox(layer, rect, crs):
70 | request = (QgsFeatureRequest()
71 | .setFilterRect(rect)
72 | .setDestinationCrs(crs=crs, context=QgsCoordinateTransformContext())
73 | .setNoAttributes().setFlags(QgsFeatureRequest.NoGeometry))
74 | return [f.id() for f in layer.getFeatures(request)]
75 |
76 |
77 | def get_map_center_coordinates(iface, qgis_instance, frmt="{:.0f}"):
78 | center_point = iface.mapCanvas().extent().center()
79 | center_point = transform_to_wgs84(center_point, qgis_instance.crs(),
80 | qgis_instance)
81 | center = {'lon': float(frmt.format(center_point.x())), 'lat': float(frmt.format(center_point.y()))}
82 | return center
83 |
--------------------------------------------------------------------------------
/GlobeBuilder/definitions/__init__.py:
--------------------------------------------------------------------------------
1 | # Copyright (C) 2020-2021 GlobeBuilder contributors.
2 | #
3 | #
4 | # This file is part of GlobeBuilder.
5 | #
6 | # GlobeBuilder is free software: you can redistribute it and/or modify
7 | # it under the terms of the GNU General Public License as published by
8 | # the Free Software Foundation, either version 2 of the License, or
9 | # (at your option) any later version.
10 | #
11 | # GlobeBuilder is distributed in the hope that it will be useful,
12 | # but WITHOUT ANY WARRANTY; without even the implied warranty of
13 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 | # GNU General Public License for more details.
15 | #
16 | # You should have received a copy of the GNU General Public License
17 | # along with GlobeBuilder. If not, see .
18 |
--------------------------------------------------------------------------------
/GlobeBuilder/definitions/projections.py:
--------------------------------------------------------------------------------
1 | # Copyright (C) 2020-2021 GlobeBuilder contributors.
2 | #
3 | #
4 | # This file is part of GlobeBuilder.
5 | #
6 | # GlobeBuilder is free software: you can redistribute it and/or modify
7 | # it under the terms of the GNU General Public License as published by
8 | # the Free Software Foundation, either version 2 of the License, or
9 | # (at your option) any later version.
10 | #
11 | # GlobeBuilder is distributed in the hope that it will be useful,
12 | # but WITHOUT ANY WARRANTY; without even the implied warranty of
13 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 | # GNU General Public License for more details.
15 | #
16 | # You should have received a copy of the GNU General Public License
17 | # along with GlobeBuilder. If not, see .
18 |
19 | from enum import Enum
20 | from typing import Tuple
21 |
22 | from ..qgis_plugin_tools.tools.i18n import tr
23 |
24 |
25 | class Projection:
26 |
27 | def __init__(self, name: str, proj_str: str, min_proj_version: Tuple[int, int] = (0, 0)):
28 | self.name = name
29 | self.proj_str_raw = proj_str
30 | self.min_proj = min_proj_version
31 |
32 | def proj_str(self, origin: {str: float}) -> str:
33 | return self.proj_str_raw.format(**origin)
34 |
35 |
36 | class Projections(Enum):
37 | AZIMUTHAL_ORTHOGRAPHIC = Projection(tr(u'Azimuthal Orthographic'),
38 | '+proj=ortho +lat_0={lat} +lon_0={lon} +x_0=0 +y_0=0 +a=6370997 +b=6370997 +units=m +no_defs')
39 |
40 | # Unfortunately +lon_0={lon} causes nasty rendering artifacts for all projections below (tested with QGIS 3.14.0 and 3.20.3)
41 |
42 | # https://www.gislounge.com/how-to-use-the-equal-earth-projection-using-qgis-on-the-mac/
43 | EQUAL_EARTH = Projection(tr(u'Equal Earth'), '+proj=eqearth +datum=WGS84 +units=m +no_defs', (5, 2))
44 |
45 | # 'https://proj.org/operations/projections/hammer.html'
46 | HAMMER_ECKERT = Projection(tr(u'Hammer & Eckert-Greifendorff'), '+proj=hammer')
47 |
48 | # 'https://proj.org/operations/projections/aitoff.html'
49 | AITOFF = Projection(tr(u'Aitoff'), '+proj=aitoff')
50 |
51 | # 'https://proj.org/operations/projections/eck1.html'
52 | ECKERT_I = Projection(tr(u'Eckert I'), '+proj=eck1')
53 |
54 | @staticmethod
55 | def proj_from_id(tr_name: str):
56 | for projection in Projections:
57 | if projection.value.name == tr_name:
58 | return projection
59 |
--------------------------------------------------------------------------------
/GlobeBuilder/definitions/settings.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 |
3 |
4 | # Copyright (C) 2020-2021 GlobeBuilder contributors.
5 | #
6 | #
7 | # This file is part of GlobeBuilder.
8 | #
9 | # GlobeBuilder is free software: you can redistribute it and/or modify
10 | # it under the terms of the GNU General Public License as published by
11 | # the Free Software Foundation, either version 2 of the License, or
12 | # (at your option) any later version.
13 | #
14 | # GlobeBuilder is distributed in the hope that it will be useful,
15 | # but WITHOUT ANY WARRANTY; without even the implied warranty of
16 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17 | # GNU General Public License for more details.
18 | #
19 | # You should have received a copy of the GNU General Public License
20 | # along with GlobeBuilder. If not, see .
21 |
22 | import enum
23 |
24 | from PyQt5.QtCore import Qt
25 | from PyQt5.QtGui import QColor
26 | from qgis.core import QgsCoordinateReferenceSystem
27 |
28 | from ..qgis_plugin_tools.tools.resources import resources_path
29 |
30 |
31 | class LayerConnectionType(enum.Enum):
32 | local = 1
33 | url = 2
34 |
35 |
36 | class HaloDrawMethod(enum.Enum):
37 | geometry_generator = "Point"
38 | buffered_point = "Polygon"
39 |
40 |
41 | NATURAL_EARTH_BASE_URL = "https://raw.githubusercontent.com/nvkelso/natural-earth-vector/master/geojson"
42 | S2CLOUDLESS_WMTS_URL = "url=https://tiles.maps.eox.at/wmts?SERVICE%3DWMTS%26REQUEST%3DGetCapabilities&contextualWMSLegend=0&crs=EPSG:4326&dpiMode=7&featureCount=10&format=image/jpeg&layers=s2cloudless-2018&styles=default&tileMatrixSet=WGS84"
43 | LOCAL_DATA_DIR = resources_path("data")
44 | DEFAULT_ORIGIN = {'lat': 42.5, 'lon': 0.5}
45 |
46 | EARTH_RADIUS = 6370997
47 | DEFAULT_NUMBER_OF_SEGMENTS = 64
48 | DEFAULT_LAYER_CONNECTION_TYPE = LayerConnectionType.local
49 | DEFAULT_HALO_DRAW_METHOD = HaloDrawMethod.buffered_point
50 |
51 | _crs = QgsCoordinateReferenceSystem()
52 | _crs.createFromId(4326)
53 | WGS84 = _crs
54 |
55 | # Colors
56 | DEFAULT_BACKGROUND_COLOR = QColor(Qt.black)
57 | DEFAULT_LAYOUT_BACKGROUND_COLOR = QColor(Qt.white)
58 | DEFAULT_COUNTRIES_COLOR = QColor(Qt.darkGreen)
59 | DEFAULT_GRATICULES_COLOR = QColor(Qt.gray)
60 | DEFAULT_HALO_COLOR = QColor(Qt.lightGray)
61 | DEFAULT_HALO_LAYOUT_COLOR = QColor(Qt.black)
62 | DEFAULT_HALO_FILL_COLOR = QColor(Qt.blue)
63 | DEFAULT_INTERSECTING_COUNTRIES_COLOR = QColor(Qt.red)
64 | _transparent = QColor(Qt.black)
65 | _transparent.setAlpha(0)
66 | TRANSPARENT_COLOR = _transparent
67 |
68 | # UI
69 | NOMINATIM_URL = "https://nominatim.openstreetmap.org/search/{query}?limit={limit}&format=geojson"
70 | DEFAULT_MAX_NUMBER_OF_RESULTS = 5
71 | MAX_NAME_PARTS = 3
72 | DEFAULT_USE_NE_COUNTRIES = True
73 | DEFAULT_USE_NE_GRATICULES = False
74 | DEFAULT_USE_S2_CLOUDLESS = False
75 |
--------------------------------------------------------------------------------
/GlobeBuilder/logs/.gitignore:
--------------------------------------------------------------------------------
1 | *.log*
2 |
--------------------------------------------------------------------------------
/GlobeBuilder/metadata.txt:
--------------------------------------------------------------------------------
1 | # This file contains metadata for your plugin.
2 |
3 | # This file should be included when you package your plugin.
4 | # Mandatory items:
5 |
6 | [general]
7 | name=Globe Builder
8 | qgisMinimumVersion=3.2
9 | description=This plugin is meant for adding globe visualization to the current map or layout.
10 | version=0.5.0
11 | author=Gispo Ltd.
12 | email=joona@gispo.fi
13 |
14 | about=This plugin builds a globe from Natural Earth countries GeoJSON and an Azimuthal orthographic projection ("world from space") projection described here. This plugin uses OpenStreetMap Nominatim geocoding API. The OpenStreetMap data is licensed under ODbL license. Made with Natural Earth. Free vectior and raster map data naturalearthdata.com. This plugin lists Sentinel-2 cloudless as an optional data source. Sentinel-2 cloudless data https://s2maps.eu/ by EOX IT Services GmbH (Contains modified Copernicus Sentinel data 2017 & 2018) released under Creative Commons Attribution-NonCommercial-ShareAlike 4.0 International License. For commercial usage of Sentinel-2 cloudless please see https://cloudless.eox.at.
15 |
16 | tracker=https://github.com/GispoCoding/GlobeBuilder/issues
17 | repository=https://github.com/GispoCoding/GlobeBuilder
18 | # End of mandatory metadata
19 |
20 | # Recommended items:
21 |
22 | hasProcessingProvider=no
23 | # Uncomment the following line and add your changelog:
24 | changelog=
25 |
26 | # Tags are comma separated with spaces allowed
27 | tags=python, globe, world
28 |
29 | homepage=https://github.com/GispoCoding/GlobeBuilder
30 | category=Plugins
31 | icon=icon.png
32 | # experimental flag
33 | experimental=False
34 |
35 | # deprecated flag (applies to the whole plugin, not just a single version)
36 | deprecated=False
37 |
38 | # Since QGIS 3.8, a comma separated list of plugins to be installed
39 | # (or upgraded) can be specified.
40 | # Check the documentation for more information.
41 | # plugin_dependencies=
42 |
43 | # If the plugin can run on QGIS Server.
44 | server=False
45 |
46 |
--------------------------------------------------------------------------------
/GlobeBuilder/plugin.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 |
3 |
4 | # Copyright (C) 2020-2021 GlobeBuilder contributors.
5 | #
6 | #
7 | # This file is part of GlobeBuilder.
8 | #
9 | # GlobeBuilder is free software: you can redistribute it and/or modify
10 | # it under the terms of the GNU General Public License as published by
11 | # the Free Software Foundation, either version 2 of the License, or
12 | # (at your option) any later version.
13 | #
14 | # GlobeBuilder is distributed in the hope that it will be useful,
15 | # but WITHOUT ANY WARRANTY; without even the implied warranty of
16 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17 | # GNU General Public License for more details.
18 | #
19 | # You should have received a copy of the GNU General Public License
20 | # along with GlobeBuilder. If not, see .
21 |
22 | from qgis.PyQt.QtCore import QTranslator, QCoreApplication, Qt
23 | from qgis.PyQt.QtGui import QIcon
24 | from qgis.PyQt.QtWidgets import QAction
25 |
26 | from .qgis_plugin_tools.tools.custom_logging import setup_logger, teardown_logger
27 | from .qgis_plugin_tools.tools.i18n import setup_translation, tr
28 | from .qgis_plugin_tools.tools.resources import plugin_name, resources_path
29 | from .ui.globe_builder_dockwidget import GlobeBuilderDockWidget
30 |
31 |
32 | class GlobeBuilder:
33 | """QGIS Plugin Implementation."""
34 |
35 | def __init__(self, iface):
36 | """Constructor.
37 |
38 | :param iface: An interface instance that will be passed to this class
39 | which provides the hook by which you can manipulate the QGIS
40 | application at run time.
41 | :type iface: QgsInterface
42 | """
43 | # Save reference to the QGIS interface
44 | self.iface = iface
45 |
46 | setup_logger(plugin_name())
47 |
48 | # initialize locale
49 | locale, file_path = setup_translation()
50 | if file_path:
51 | translator = QTranslator()
52 | translator.load(file_path)
53 | # noinspection PyCallByClass,PyArgumentList
54 | QCoreApplication.installTranslator(translator)
55 | else:
56 | pass
57 |
58 | # Declare instance attributes
59 | self.actions = []
60 | self.menu = tr(u'&Globe Builder')
61 |
62 | # Check if plugin was started the first time in current QGIS session
63 | # Must be set in initGui() to survive plugin reloads
64 | self.pluginIsActive = False
65 | self.dockwidget = None
66 |
67 | # noinspection PyMethodMayBeStatic
68 | def tr(self, message):
69 | """Get the translation for a string using Qt translation API.
70 |
71 | We implement this ourselves since we do not inherit QObject.
72 |
73 | :param message: String for translation.
74 | :type message: str, QString
75 |
76 | :returns: Translated version of message.
77 | :rtype: QString
78 | """
79 | # noinspection PyTypeChecker,PyArgumentList,PyCallByClass
80 | return QCoreApplication.translate('GlobeBuilder', message)
81 |
82 | def add_action(
83 | self,
84 | icon_path,
85 | text,
86 | callback,
87 | enabled_flag=True,
88 | add_to_menu=True,
89 | add_to_toolbar=True,
90 | status_tip=None,
91 | whats_this=None,
92 | parent=None):
93 | """Add a toolbar icon to the toolbar.
94 |
95 | :param icon_path: Path to the icon for this action. Can be a resource
96 | path (e.g. ':/plugins/foo/bar.png') or a normal file system path.
97 | :type icon_path: str
98 |
99 | :param text: Text that should be shown in menu items for this action.
100 | :type text: str
101 |
102 | :param callback: Function to be called when the action is triggered.
103 | :type callback: function
104 |
105 | :param enabled_flag: A flag indicating if the action should be enabled
106 | by default. Defaults to True.
107 | :type enabled_flag: bool
108 |
109 | :param add_to_menu: Flag indicating whether the action should also
110 | be added to the menu. Defaults to True.
111 | :type add_to_menu: bool
112 |
113 | :param add_to_toolbar: Flag indicating whether the action should also
114 | be added to the toolbar. Defaults to True.
115 | :type add_to_toolbar: bool
116 |
117 | :param status_tip: Optional text to show in a popup when mouse pointer
118 | hovers over the action.
119 | :type status_tip: str
120 |
121 | :param parent: Parent widget for the new action. Defaults None.
122 | :type parent: QWidget
123 |
124 | :param whats_this: Optional text to show in the status bar when the
125 | mouse pointer hovers over the action.
126 |
127 | :returns: The action that was created. Note that the action is also
128 | added to self.actions list.
129 | :rtype: QAction
130 | """
131 |
132 | icon = QIcon(icon_path)
133 | action = QAction(icon, text, parent)
134 | # noinspection PyUnresolvedReferences
135 | action.triggered.connect(callback)
136 | action.setEnabled(enabled_flag)
137 |
138 | if status_tip is not None:
139 | action.setStatusTip(status_tip)
140 |
141 | if whats_this is not None:
142 | action.setWhatsThis(whats_this)
143 |
144 | if add_to_toolbar:
145 | # Adds plugin icon to Plugins toolbar
146 | self.iface.addToolBarIcon(action)
147 |
148 | if add_to_menu:
149 | self.iface.addPluginToMenu(
150 | self.menu,
151 | action)
152 |
153 | self.actions.append(action)
154 |
155 | return action
156 |
157 | def initGui(self):
158 | """Create the menu entries and toolbar icons inside the QGIS GUI."""
159 |
160 | # noinspection PyTypeChecker
161 | self.add_action(
162 | resources_path('icon.png'),
163 | text=tr(u'Build Globe view'),
164 | callback=self.run,
165 | parent=self.iface.mainWindow())
166 |
167 | # will be set False in run()
168 | self.first_start = True
169 |
170 | def onClosePlugin(self):
171 | """Cleanup necessary items here when plugin dockwidget is closed"""
172 |
173 | # disconnects
174 | self.dockwidget.closingPlugin.disconnect(self.onClosePlugin)
175 |
176 | # remove this statement if dockwidget is to remain
177 | # for reuse if plugin is reopened
178 | # Commented next statement since it causes QGIS crashe
179 | # when closing the docked window:
180 | # self.dockwidget = None
181 |
182 | self.pluginIsActive = False
183 |
184 | def unload(self):
185 | """Removes the plugin menu item and icon from QGIS GUI."""
186 | for action in self.actions:
187 | self.iface.removePluginMenu(
188 | tr(u'&Globe Builder'),
189 | action)
190 | self.iface.removeToolBarIcon(action)
191 | teardown_logger(plugin_name())
192 |
193 | def run(self):
194 | """Run method that performs all the real work"""
195 | if not self.pluginIsActive:
196 | self.pluginIsActive = True
197 |
198 | # dockwidget may not exist if:
199 | # first run of plugin
200 | # removed on close (see self.onClosePlugin method)
201 | if self.dockwidget == None:
202 | # Create the dockwidget (after translation) and keep reference
203 | self.dockwidget = GlobeBuilderDockWidget(self.iface)
204 |
205 | # connect to provide cleanup on closing of dockwidget
206 | self.dockwidget.closingPlugin.connect(self.onClosePlugin)
207 |
208 | # show the dockwidget
209 | self.iface.addDockWidget(Qt.RightDockWidgetArea, self.dockwidget)
210 | self.dockwidget.show()
211 |
--------------------------------------------------------------------------------
/GlobeBuilder/pylintrc:
--------------------------------------------------------------------------------
1 | [MASTER]
2 |
3 | # Specify a configuration file.
4 | #rcfile=
5 |
6 | # Python code to execute, usually for sys.path manipulation such as
7 | # pygtk.require().
8 | #init-hook=
9 |
10 | # Profiled execution.
11 | profile=no
12 |
13 | # Add files or directories to the blacklist. They should be base names, not
14 | # paths.
15 | ignore=CVS
16 |
17 | # Pickle collected data for later comparisons.
18 | persistent=yes
19 |
20 | # List of plugins (as comma separated values of python modules names) to load,
21 | # usually to register additional checkers.
22 | load-plugins=
23 |
24 |
25 | [MESSAGES CONTROL]
26 |
27 | # Enable the message, report, category or checker with the given id(s). You can
28 | # either give multiple identifier separated by comma (,) or put this option
29 | # multiple time. See also the "--disable" option for examples.
30 | #enable=
31 |
32 | # Disable the message, report, category or checker with the given id(s). You
33 | # can either give multiple identifiers separated by comma (,) or put this
34 | # option multiple times (only on the command line, not in the configuration
35 | # file where it should appear only once).You can also use "--disable=all" to
36 | # disable everything first and then reenable specific checks. For example, if
37 | # you want to run only the similarities checker, you can use "--disable=all
38 | # --enable=similarities". If you want to run only the classes checker, but have
39 | # no Warning level messages displayed, use"--disable=all --enable=classes
40 | # --disable=W"
41 | # see http://stackoverflow.com/questions/21487025/pylint-locally-defined-disables-still-give-warnings-how-to-suppress-them
42 | disable=locally-disabled,C0103
43 |
44 |
45 | [REPORTS]
46 |
47 | # Set the output format. Available formats are text, parseable, colorized, msvs
48 | # (visual studio) and html. You can also give a reporter class, eg
49 | # mypackage.mymodule.MyReporterClass.
50 | output-format=text
51 |
52 | # Put messages in a separate file for each module / package specified on the
53 | # command line instead of printing them on stdout. Reports (if any) will be
54 | # written in a file name "pylint_global.[txt|html]".
55 | files-output=no
56 |
57 | # Tells whether to display a full report or only the messages
58 | reports=yes
59 |
60 | # Python expression which should return a note less than 10 (10 is the highest
61 | # note). You have access to the variables errors warning, statement which
62 | # respectively contain the number of errors / warnings messages and the total
63 | # number of statements analyzed. This is used by the global evaluation report
64 | # (RP0004).
65 | evaluation=10.0 - ((float(5 * error + warning + refactor + convention) / statement) * 10)
66 |
67 | # Add a comment according to your evaluation note. This is used by the global
68 | # evaluation report (RP0004).
69 | comment=no
70 |
71 | # Template used to display messages. This is a python new-style format string
72 | # used to format the message information. See doc for all details
73 | #msg-template=
74 |
75 |
76 | [BASIC]
77 |
78 | # Required attributes for module, separated by a comma
79 | required-attributes=
80 |
81 | # List of builtins function names that should not be used, separated by a comma
82 | bad-functions=map,filter,apply,input
83 |
84 | # Regular expression which should only match correct module names
85 | module-rgx=(([a-z_][a-z0-9_]*)|([A-Z][a-zA-Z0-9]+))$
86 |
87 | # Regular expression which should only match correct module level names
88 | const-rgx=(([A-Z_][A-Z0-9_]*)|(__.*__))$
89 |
90 | # Regular expression which should only match correct class names
91 | class-rgx=[A-Z_][a-zA-Z0-9]+$
92 |
93 | # Regular expression which should only match correct function names
94 | function-rgx=[a-z_][a-z0-9_]{2,30}$
95 |
96 | # Regular expression which should only match correct method names
97 | method-rgx=[a-z_][a-z0-9_]{2,30}$
98 |
99 | # Regular expression which should only match correct instance attribute names
100 | attr-rgx=[a-z_][a-z0-9_]{2,30}$
101 |
102 | # Regular expression which should only match correct argument names
103 | argument-rgx=[a-z_][a-z0-9_]{2,30}$
104 |
105 | # Regular expression which should only match correct variable names
106 | variable-rgx=[a-z_][a-z0-9_]{2,30}$
107 |
108 | # Regular expression which should only match correct attribute names in class
109 | # bodies
110 | class-attribute-rgx=([A-Za-z_][A-Za-z0-9_]{2,30}|(__.*__))$
111 |
112 | # Regular expression which should only match correct list comprehension /
113 | # generator expression variable names
114 | inlinevar-rgx=[A-Za-z_][A-Za-z0-9_]*$
115 |
116 | # Good variable names which should always be accepted, separated by a comma
117 | good-names=i,j,k,ex,Run,_
118 |
119 | # Bad variable names which should always be refused, separated by a comma
120 | bad-names=foo,bar,baz,toto,tutu,tata
121 |
122 | # Regular expression which should only match function or class names that do
123 | # not require a docstring.
124 | no-docstring-rgx=__.*__
125 |
126 | # Minimum line length for functions/classes that require docstrings, shorter
127 | # ones are exempt.
128 | docstring-min-length=-1
129 |
130 |
131 | [MISCELLANEOUS]
132 |
133 | # List of note tags to take in consideration, separated by a comma.
134 | notes=FIXME,XXX,TODO
135 |
136 |
137 | [TYPECHECK]
138 |
139 | # Tells whether missing members accessed in mixin class should be ignored. A
140 | # mixin class is detected if its name ends with "mixin" (case insensitive).
141 | ignore-mixin-members=yes
142 |
143 | # List of classes names for which member attributes should not be checked
144 | # (useful for classes with attributes dynamically set).
145 | ignored-classes=SQLObject
146 |
147 | # When zope mode is activated, add a predefined set of Zope acquired attributes
148 | # to generated-members.
149 | zope=no
150 |
151 | # List of members which are set dynamically and missed by pylint inference
152 | # system, and so shouldn't trigger E0201 when accessed. Python regular
153 | # expressions are accepted.
154 | generated-members=REQUEST,acl_users,aq_parent
155 |
156 |
157 | [VARIABLES]
158 |
159 | # Tells whether we should check for unused import in __init__ files.
160 | init-import=no
161 |
162 | # A regular expression matching the beginning of the name of dummy variables
163 | # (i.e. not used).
164 | dummy-variables-rgx=_$|dummy
165 |
166 | # List of additional names supposed to be defined in builtins. Remember that
167 | # you should avoid to define new builtins when possible.
168 | additional-builtins=
169 |
170 |
171 | [FORMAT]
172 |
173 | # Maximum number of characters on a single line.
174 | max-line-length=80
175 |
176 | # Regexp for a line that is allowed to be longer than the limit.
177 | ignore-long-lines=^\s*(# )??$
178 |
179 | # Allow the body of an if to be on the same line as the test if there is no
180 | # else.
181 | single-line-if-stmt=no
182 |
183 | # List of optional constructs for which whitespace checking is disabled
184 | no-space-check=trailing-comma,dict-separator
185 |
186 | # Maximum number of lines in a module
187 | max-module-lines=1000
188 |
189 | # String used as indentation unit. This is usually " " (4 spaces) or "\t" (1
190 | # tab).
191 | indent-string=' '
192 |
193 |
194 | [SIMILARITIES]
195 |
196 | # Minimum lines number of a similarity.
197 | min-similarity-lines=4
198 |
199 | # Ignore comments when computing similarities.
200 | ignore-comments=yes
201 |
202 | # Ignore docstrings when computing similarities.
203 | ignore-docstrings=yes
204 |
205 | # Ignore imports when computing similarities.
206 | ignore-imports=no
207 |
208 |
209 | [IMPORTS]
210 |
211 | # Deprecated modules which should not be used, separated by a comma
212 | deprecated-modules=regsub,TERMIOS,Bastion,rexec
213 |
214 | # Create a graph of every (i.e. internal and external) dependencies in the
215 | # given file (report RP0402 must not be disabled)
216 | import-graph=
217 |
218 | # Create a graph of external dependencies in the given file (report RP0402 must
219 | # not be disabled)
220 | ext-import-graph=
221 |
222 | # Create a graph of internal dependencies in the given file (report RP0402 must
223 | # not be disabled)
224 | int-import-graph=
225 |
226 |
227 | [DESIGN]
228 |
229 | # Maximum number of arguments for function / method
230 | max-args=5
231 |
232 | # Argument names that match this expression will be ignored. Default to name
233 | # with leading underscore
234 | ignored-argument-names=_.*
235 |
236 | # Maximum number of locals for function / method body
237 | max-locals=15
238 |
239 | # Maximum number of return / yield for function / method body
240 | max-returns=6
241 |
242 | # Maximum number of branch for function / method body
243 | max-branches=12
244 |
245 | # Maximum number of statements in function / method body
246 | max-statements=50
247 |
248 | # Maximum number of parents for a class (see R0901).
249 | max-parents=7
250 |
251 | # Maximum number of attributes for a class (see R0902).
252 | max-attributes=7
253 |
254 | # Minimum number of public methods for a class (see R0903).
255 | min-public-methods=2
256 |
257 | # Maximum number of public methods for a class (see R0904).
258 | max-public-methods=20
259 |
260 |
261 | [CLASSES]
262 |
263 | # List of interface methods to ignore, separated by a comma. This is used for
264 | # instance to not check methods defines in Zope's Interface base class.
265 | ignore-iface-methods=isImplementedBy,deferred,extends,names,namesAndDescriptions,queryDescriptionFor,getBases,getDescriptionFor,getDoc,getName,getTaggedValue,getTaggedValueTags,isEqualOrExtendedBy,setTaggedValue,isImplementedByInstancesOf,adaptWith,is_implemented_by
266 |
267 | # List of method names used to declare (i.e. assign) instance attributes.
268 | defining-attr-methods=__init__,__new__,setUp
269 |
270 | # List of valid names for the first argument in a class method.
271 | valid-classmethod-first-arg=cls
272 |
273 | # List of valid names for the first argument in a metaclass class method.
274 | valid-metaclass-classmethod-first-arg=mcs
275 |
276 |
277 | [EXCEPTIONS]
278 |
279 | # Exceptions that will emit a warning when being caught. Defaults to
280 | # "Exception"
281 | overgeneral-exceptions=Exception
282 |
--------------------------------------------------------------------------------
/GlobeBuilder/resources/icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/GispoCoding/GlobeBuilder/3a9ec05ef91e39fb67e52962c3bf24007b2d3046/GlobeBuilder/resources/icon.png
--------------------------------------------------------------------------------
/GlobeBuilder/resources/ui/globe_builder_dockwidget_base.ui:
--------------------------------------------------------------------------------
1 |
2 |
3 | GlobeBuilderDockWidgetBase
4 |
5 |
6 |
7 | 0
8 | 0
9 | 546
10 | 888
11 |
12 |
13 |
14 | false
15 |
16 |
17 | Globe Builder
18 |
19 |
20 |
21 | -
22 |
23 |
24 | QLayout::SetNoConstraint
25 |
26 |
-
27 |
28 |
29 | true
30 |
31 |
32 |
33 |
34 | 0
35 | -143
36 | 510
37 | 886
38 |
39 |
40 |
41 |
-
42 |
43 |
-
44 |
45 |
46 | Data sources (optional)
47 |
48 |
49 | false
50 |
51 |
52 |
-
53 |
54 |
-
55 |
56 |
-
57 |
58 |
59 |
60 |
61 |
62 | true
63 |
64 |
65 |
66 | -
67 |
68 |
-
69 |
70 | Countries 50m
71 |
72 |
73 | -
74 |
75 | Countries 110m
76 |
77 |
78 |
79 |
80 | -
81 |
82 |
83 | Qt::Horizontal
84 |
85 |
86 |
87 | 40
88 | 20
89 |
90 |
91 |
92 |
93 | -
94 |
95 |
96 |
97 |
98 |
99 | true
100 |
101 |
102 |
103 | -
104 |
105 |
-
106 |
107 | Graticules 10
108 |
109 |
110 | -
111 |
112 | Graticules 2
113 |
114 |
115 | -
116 |
117 | Graticules 5
118 |
119 |
120 | -
121 |
122 | Graticules 20
123 |
124 |
125 | -
126 |
127 | Graticules 30
128 |
129 |
130 |
131 |
132 | -
133 |
134 |
135 | Qt::Horizontal
136 |
137 |
138 |
139 | 40
140 | 20
141 |
142 |
143 |
144 |
145 | -
146 |
147 |
148 | Sentinel-2 cloudless
149 |
150 |
151 | true
152 |
153 |
154 |
155 |
156 |
157 |
158 |
159 |
160 |
161 |
162 |
163 |
164 | -
165 |
166 |
167 | Projection
168 |
169 |
170 |
-
171 |
172 |
173 | Projection
174 |
175 |
176 |
177 | -
178 |
179 |
180 |
181 |
182 |
183 | -
184 |
185 |
186 | true
187 |
188 |
189 | Center the Globe based on:
190 |
191 |
192 | false
193 |
194 |
195 | false
196 |
197 |
198 |
-
199 |
200 |
-
201 |
202 |
-
203 |
204 |
205 | Coordinates
206 |
207 |
208 | true
209 |
210 |
211 |
212 | -
213 |
214 |
-
215 |
216 |
217 | 'Longitude, Latitude' in decimal degrees
218 |
219 |
220 |
221 | -
222 |
223 |
224 |
225 |
226 |
227 |
228 | -
229 |
230 |
231 | Qt::Horizontal
232 |
233 |
234 |
235 | -
236 |
237 |
238 | Center to the current view
239 |
240 |
241 |
242 | -
243 |
244 |
245 | Qt::Horizontal
246 |
247 |
248 |
249 | -
250 |
251 |
-
252 |
253 |
254 | Layer
255 |
256 |
257 |
258 | -
259 |
260 |
261 |
262 |
263 | -
264 |
265 |
266 | Qt::Horizontal
267 |
268 |
269 |
270 | -
271 |
272 |
-
273 |
274 |
275 | Geocoding
276 |
277 |
278 |
279 | -
280 |
281 |
-
282 |
283 |
284 | Address for the geolocation of interest:
285 |
286 |
287 |
288 | -
289 |
290 |
-
291 |
292 |
293 | -
294 |
295 |
296 | Search
297 |
298 |
299 |
300 |
301 |
302 | -
303 |
304 |
305 | -
306 |
307 |
-
308 |
309 |
310 | Qt::Horizontal
311 |
312 |
313 |
314 | 40
315 | 20
316 |
317 |
318 |
319 |
320 | -
321 |
322 |
323 |
324 | 0
325 | 0
326 |
327 |
328 |
329 | Maximum number of addresses to list:
330 |
331 |
332 |
333 | -
334 |
335 |
336 |
337 | 0
338 | 0
339 |
340 |
341 |
342 |
343 | 50
344 | 16777215
345 |
346 |
347 |
348 | 1
349 |
350 |
351 | 10
352 |
353 |
354 | 5
355 |
356 |
357 |
358 |
359 |
360 |
361 |
362 |
363 |
364 |
365 |
366 |
367 |
368 |
369 | -
370 |
371 |
372 | Visualization
373 |
374 |
375 | false
376 |
377 |
378 |
-
379 |
380 |
-
381 |
382 |
-
383 |
384 |
-
385 |
386 |
387 | Fill
388 |
389 |
390 |
391 | -
392 |
393 |
394 |
395 | 50
396 | false
397 |
398 |
399 |
400 | Halo
401 |
402 |
403 |
404 | -
405 |
406 |
407 | Line
408 |
409 |
410 |
411 | -
412 |
413 |
414 | Background
415 |
416 |
417 |
418 | -
419 |
420 |
421 | true
422 |
423 |
424 |
425 | -
426 |
427 |
428 | Graticules
429 |
430 |
431 |
432 | -
433 |
434 |
435 | true
436 |
437 |
438 |
439 | -
440 |
441 |
442 | Countries
443 |
444 |
445 |
446 | -
447 |
448 |
449 | true
450 |
451 |
452 |
453 | -
454 |
455 |
456 | Optional visualizations
457 |
458 |
459 |
460 | -
461 |
462 |
463 | true
464 |
465 |
466 |
467 | -
468 |
469 |
470 |
471 |
472 | -
473 |
474 |
475 | Qt::Vertical
476 |
477 |
478 |
479 | 20
480 | 40
481 |
482 |
483 |
484 |
485 |
486 |
487 | -
488 |
489 |
490 | Qt::Vertical
491 |
492 |
493 |
494 | -
495 |
496 |
-
497 |
498 |
499 | Halo type
500 |
501 |
502 |
503 | -
504 |
505 |
506 | Qt::Vertical
507 |
508 |
509 |
510 | 20
511 | 40
512 |
513 |
514 |
515 |
516 | -
517 |
518 |
519 | Halo, no fill
520 |
521 |
522 | true
523 |
524 |
525 |
526 | -
527 |
528 |
529 | Halo, fill
530 |
531 |
532 |
533 | -
534 |
535 |
536 | Outline, no fill
537 |
538 |
539 |
540 | -
541 |
542 |
543 | Outline, fill
544 |
545 |
546 |
547 |
548 |
549 |
550 |
551 | -
552 |
553 |
554 | Qt::Horizontal
555 |
556 |
557 |
558 | -
559 |
560 |
-
561 |
562 |
563 | Qt::Horizontal
564 |
565 |
566 |
567 | 40
568 | 20
569 |
570 |
571 |
572 |
573 |
574 |
575 |
576 |
577 |
578 | -
579 |
580 |
581 | Layout
582 |
583 |
584 | false
585 |
586 |
587 |
-
588 |
589 |
-
590 |
591 |
-
592 |
593 |
594 | Map composer:
595 |
596 |
597 |
598 | -
599 |
600 |
601 |
602 |
603 |
604 |
605 | -
606 |
607 |
-
608 |
609 |
610 | Globe Layout background
611 |
612 |
613 |
614 | -
615 |
616 |
617 |
618 | 50
619 | 16
620 |
621 |
622 |
623 | true
624 |
625 |
626 |
627 |
628 |
629 | -
630 |
631 |
-
632 |
633 |
634 | Color for Countries intersecting the current view
635 |
636 |
637 |
638 | -
639 |
640 |
641 | true
642 |
643 |
644 |
645 |
646 |
647 | -
648 |
649 |
-
650 |
651 |
652 | Qt::Horizontal
653 |
654 |
655 |
656 | 40
657 | 20
658 |
659 |
660 |
661 |
662 | -
663 |
664 |
665 |
666 | 0
667 | 0
668 |
669 |
670 |
671 | Globe size in mm
672 |
673 |
674 |
675 | -
676 |
677 |
678 |
679 | 0
680 | 0
681 |
682 |
683 |
684 |
685 | 50
686 | 16777215
687 |
688 |
689 |
690 | 1
691 |
692 |
693 | 10000
694 |
695 |
696 | 80
697 |
698 |
699 |
700 |
701 |
702 |
703 |
704 |
705 | -
706 |
707 |
708 | Qt::Vertical
709 |
710 |
711 |
712 | 20
713 | 40
714 |
715 |
716 |
717 |
718 |
719 |
720 |
721 |
722 | -
723 |
724 |
-
725 |
726 |
727 | Qt::Horizontal
728 |
729 |
730 |
731 | 40
732 | 20
733 |
734 |
735 |
736 |
737 | -
738 |
739 |
740 | Apply changes
741 |
742 |
743 |
744 | -
745 |
746 |
747 | Add the Globe to a Map
748 |
749 |
750 |
751 | -
752 |
753 |
754 | Add the Globe to a Layout
755 |
756 |
757 |
758 |
759 |
760 | -
761 |
762 |
763 | Attributions
764 |
765 |
766 |
-
767 |
768 |
769 | <html><head/><body><p><span style=" font-size:10pt;">Please,
770 | note that the plugin utilizes the </span><a href="https://wiki.openstreetmap.org/wiki/Nominatim"><span
771 | style=" font-size:10pt; text-decoration: underline; color:#0000ff;">OpenStreetMap Nominatim</span></a><span
772 | style=" font-size:10pt;"> geocoding API. Please, </span><span style="
773 | font-size:10pt; font-weight:600;">use maximum of 1 geocoding request per second</span><span
774 | style=" font-size:10pt;">. The OpenStreetMap data is licensed under </span><a href="https://opendatacommons.org/licenses/odbl/"><span
775 | style=" font-size:10pt; text-decoration: underline; color:#0000ff;">ODbL license</span></a><span
776 | style=" font-size:10pt;">. Made with Natural Earth. Free vectior and raster map data @ </span><a
777 | href="naturalearthdata.com"><span style=" font-size:10pt; text-decoration: underline;
778 | color:#0000ff;">naturalearthdata.com</span></a><span style=" font-size:10pt;">.
779 | Sentinel-2 cloudless data- </span><a href="https://s2maps.eu/"><span style="
780 | font-size:10pt; text-decoration: underline; color:#0000ff;">https://s2maps.eu</span></a><span
781 | style=" font-size:10pt;"> by EOX IT Services GmbH (Contains modified Copernicus Sentinel data
782 | 2017 & 2018) released under </span><a href="https://creativecommons.org/licenses/by-nc-sa/4.0/"><span
783 | style=" font-size:10pt; text-decoration: underline; color:#0000ff;">Creative Commons
784 | Attribution-NonCommercial-ShareAlike 4.0 International License</span></a><span style="
785 | font-size:10pt;">. For commercial usage of Sentinel-2 cloudless please see </span><a href="https://cloudless.eox.at"><span
786 | style=" font-size:10pt; text-decoration: underline; color:#0000ff;">https://cloudless.eox.at</span></a><span
787 | style=" font-size:10pt;">. </span></p></body></html>
788 |
789 |
790 |
791 | true
792 |
793 |
794 |
795 |
796 |
797 |
798 |
799 |
800 |
801 |
802 |
803 |
804 |
805 | QgsCollapsibleGroupBox
806 | QGroupBox
807 |
808 | 1
809 |
810 |
811 | QgsColorButton
812 | QToolButton
813 |
814 |
815 |
816 | QgsMapLayerComboBox
817 | QComboBox
818 |
819 |
820 |
821 |
822 |
823 |
824 |
--------------------------------------------------------------------------------
/GlobeBuilder/ui/__init__.py:
--------------------------------------------------------------------------------
1 | # Copyright (C) 2020-2021 GlobeBuilder contributors.
2 | #
3 | #
4 | # This file is part of GlobeBuilder.
5 | #
6 | # GlobeBuilder is free software: you can redistribute it and/or modify
7 | # it under the terms of the GNU General Public License as published by
8 | # the Free Software Foundation, either version 2 of the License, or
9 | # (at your option) any later version.
10 | #
11 | # GlobeBuilder is distributed in the hope that it will be useful,
12 | # but WITHOUT ANY WARRANTY; without even the implied warranty of
13 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 | # GNU General Public License for more details.
15 | #
16 | # You should have received a copy of the GNU General Public License
17 | # along with GlobeBuilder. If not, see .
18 |
--------------------------------------------------------------------------------
/GlobeBuilder/ui/globe_builder_dockwidget.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 |
3 |
4 | # Copyright (C) 2020-2021 GlobeBuilder contributors.
5 | #
6 | #
7 | # This file is part of GlobeBuilder.
8 | #
9 | # GlobeBuilder is free software: you can redistribute it and/or modify
10 | # it under the terms of the GNU General Public License as published by
11 | # the Free Software Foundation, either version 2 of the License, or
12 | # (at your option) any later version.
13 | #
14 | # GlobeBuilder is distributed in the hope that it will be useful,
15 | # but WITHOUT ANY WARRANTY; without even the implied warranty of
16 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17 | # GNU General Public License for more details.
18 | #
19 | # You should have received a copy of the GNU General Public License
20 | # along with GlobeBuilder. If not, see .
21 |
22 | import logging
23 |
24 | from PyQt5.QtCore import pyqtSignal, pyqtSlot
25 | from qgis.PyQt import QtWidgets
26 | from qgis.core import QgsProject
27 |
28 | from ..core.globe import Globe
29 | from ..core.utils.geocoder import Geocoder
30 | from ..core.utils.utils import create_layout, transform_to_wgs84, get_map_center_coordinates
31 | from ..definitions.projections import Projections
32 | from ..definitions.settings import (DEFAULT_MAX_NUMBER_OF_RESULTS, DEFAULT_USE_NE_COUNTRIES,
33 | DEFAULT_USE_NE_GRATICULES, DEFAULT_USE_S2_CLOUDLESS, DEFAULT_ORIGIN,
34 | DEFAULT_BACKGROUND_COLOR, DEFAULT_HALO_COLOR, DEFAULT_HALO_FILL_COLOR,
35 | DEFAULT_LAYOUT_BACKGROUND_COLOR, DEFAULT_COUNTRIES_COLOR,
36 | DEFAULT_GRATICULES_COLOR, DEFAULT_INTERSECTING_COUNTRIES_COLOR)
37 | from ..qgis_plugin_tools.tools.custom_logging import bar_msg
38 | from ..qgis_plugin_tools.tools.i18n import tr
39 | from ..qgis_plugin_tools.tools.resources import load_ui, plugin_name
40 | from ..qgis_plugin_tools.tools.settings import get_setting, set_setting
41 | from ..qgis_plugin_tools.tools.version import proj_version
42 |
43 | FORM_CLASS = load_ui('globe_builder_dockwidget_base.ui')
44 | LOGGER = logging.getLogger(plugin_name())
45 |
46 |
47 | class GlobeBuilderDockWidget(QtWidgets.QDockWidget, FORM_CLASS):
48 | closingPlugin = pyqtSignal()
49 |
50 | def __init__(self, iface, parent=None):
51 | """Constructor."""
52 | self.is_initializing = True
53 |
54 | super(GlobeBuilderDockWidget, self).__init__(parent)
55 |
56 | # Set up the user interface from Designer.
57 | # After setupUI you can access any designer object by doing
58 | # self., and you can use autoconnect slots - see
59 | # http://doc.qt.io/qt-5/designer-using-a-ui-file.html
60 | # #widgets-and-dialogs-with-auto-connect
61 | self.setupUi(self)
62 |
63 | self.iface = iface
64 | # noinspection PyArgumentList
65 | self.qgis_instance = QgsProject.instance()
66 | self.layout_mngr = self.qgis_instance.layoutManager()
67 | self.globe = Globe(iface)
68 | self.geocoder = Geocoder(lambda results: self.on_geocoding_finished(results))
69 |
70 | # Set default values
71 | self.spinBoxMaxResults.setValue(get_setting("maxNumberOfResults", DEFAULT_MAX_NUMBER_OF_RESULTS, int))
72 | self.checkBoxCountries.setChecked(get_setting("useNE-countries", DEFAULT_USE_NE_COUNTRIES, bool))
73 | self.checkBoxGraticules.setChecked(get_setting("useNE-graticules", DEFAULT_USE_NE_GRATICULES, bool))
74 | self.checkBoxS2cloudless.setChecked(get_setting("useS2cloudless", DEFAULT_USE_S2_CLOUDLESS, bool))
75 |
76 | self.lineEditLonLat.setText("{lon}, {lat}".format(**DEFAULT_ORIGIN))
77 | self.on_radioButtonCoordinates_toggled(self.radioButtonCoordinates.isChecked())
78 | self.on_radioButtonLayer_toggled(self.radioButtonLayer.isChecked())
79 | self.on_radioButtonGeocoding_toggled(self.radioButtonGeocoding.isChecked())
80 | self.on_radioButtonHFill_toggled(self.radioButtonHFill.isChecked())
81 | self.on_radioButtonHFillWithHalo_toggled(self.radioButtonHFillWithHalo.isChecked())
82 | self.on_checkBoxIntCountries_stateChanged()
83 |
84 | self.populate_comboBoxLayouts()
85 | self.populate_comboBoxProjections()
86 |
87 | self.mColorButtonBackground.setColor(DEFAULT_BACKGROUND_COLOR)
88 | self.mColorButtonHalo.setColor(DEFAULT_HALO_COLOR)
89 | self.mColorButtonHFill.setColor(DEFAULT_HALO_FILL_COLOR)
90 | self.mColorButtonLayoutBackground.setColor(DEFAULT_LAYOUT_BACKGROUND_COLOR)
91 | self.mColorButtonCountries.setColor(DEFAULT_COUNTRIES_COLOR)
92 | self.mColorButtonGraticules.setColor(DEFAULT_GRATICULES_COLOR)
93 | self.mColorButtonIntCountries.setColor(DEFAULT_INTERSECTING_COUNTRIES_COLOR)
94 |
95 | self.geolocations = {}
96 | self.old_coordinates = DEFAULT_ORIGIN
97 | self.old_projection = Projections.proj_from_id(self.comboBoxProjections.currentText())
98 |
99 | # connections
100 | self.layout_mngr.layoutAdded.connect(self.populate_comboBoxLayouts)
101 | self.layout_mngr.layoutRemoved.connect(self.populate_comboBoxLayouts)
102 | self.layout_mngr.layoutRenamed.connect(self.populate_comboBoxLayouts)
103 | self.comboBoxProjections.currentTextChanged.connect(self.projection_changed)
104 |
105 | self.is_initializing = False
106 |
107 | def closeEvent(self, event):
108 | # noinspection PyUnresolvedReferences
109 | self.closingPlugin.emit()
110 | event.accept()
111 |
112 | # Without this annotation the signal is handled twice,
113 | # see https://stackoverflow.com/questions/14311578/event-signal-is-emmitted-twice-every-time
114 | @pyqtSlot(bool)
115 | def on_radioButtonCoordinates_toggled(self, is_checked):
116 | self.lineEditLonLat.setEnabled(is_checked)
117 |
118 | @pyqtSlot(bool)
119 | def on_radioButtonLayer_toggled(self, is_checked):
120 | self.mMapLayerComboBox.setEnabled(is_checked)
121 |
122 | @pyqtSlot(bool)
123 | def on_radioButtonGeocoding_toggled(self, is_checked):
124 | self.lineEditGeocoding.setEnabled(is_checked)
125 | self.pushButtonSearch.setEnabled(is_checked)
126 | self.listWidgetGeocodingResults.setEnabled(is_checked)
127 | self.spinBoxMaxResults.setEnabled(is_checked)
128 |
129 |
130 | def on_radioButtonHFillWithHalo_toggled(self, is_checked):
131 | self.mColorButtonHFill.setEnabled(is_checked or self.radioButtonHFill.isChecked())
132 |
133 | @pyqtSlot(bool)
134 | def on_radioButtonHFill_toggled(self, is_checked):
135 | self.mColorButtonHFill.setEnabled(is_checked or self.radioButtonHFillWithHalo.isChecked())
136 |
137 | @pyqtSlot(int)
138 | def on_spinBoxMaxResults_valueChanged(self, value):
139 | set_setting("maxNumberOfResults", value)
140 |
141 | def on_checkBoxCountries_stateChanged(self):
142 | set_setting("useNE-countries", self.checkBoxCountries.isChecked())
143 |
144 | def on_checkBoxGraticules_stateChanged(self):
145 | set_setting("useNE-graticules", self.checkBoxGraticules.isChecked())
146 |
147 | def on_checkBoxS2cloudless_stateChanged(self):
148 | set_setting("useS2cloudless", self.checkBoxS2cloudless.isChecked())
149 |
150 | def on_checkBoxIntCountries_stateChanged(self):
151 | set_setting("intCountries", self.checkBoxIntCountries.isChecked())
152 | self.mColorButtonIntCountries.setEnabled(self.checkBoxIntCountries.isChecked())
153 |
154 | @pyqtSlot()
155 | def on_pushButtonSearch_clicked(self):
156 | text = self.lineEditGeocoding.text()
157 | if len(text.strip()):
158 | self.listWidgetGeocodingResults.clear()
159 | self.geolocations.clear()
160 | self.geocoder.geocode(text, self.spinBoxMaxResults.value())
161 |
162 | @pyqtSlot()
163 | def on_pushButtonApplyVisualizations_clicked(self):
164 | if not self.is_initializing:
165 | coordinates = self.calculate_origin_coordinates()
166 | if coordinates != self.old_coordinates:
167 | self.old_coordinates = coordinates
168 | self.globe.set_origin(coordinates)
169 | projection = Projections.proj_from_id(self.comboBoxProjections.currentText())
170 | if projection != self.old_projection:
171 | self.old_projection = projection
172 | self.globe.set_projection(projection)
173 | self.globe.change_project_projection()
174 | self.load_data_to_globe()
175 | self.globe.change_background_color(self.mColorButtonBackground.color())
176 | self.mColorButtonBackground.setColor(self.iface.mapCanvas().canvasColor())
177 | self.globe.set_group_visibility(True)
178 | self.add_halo_to_globe()
179 |
180 | @pyqtSlot()
181 | def on_pushButtonRun_clicked(self):
182 | self.load_data_to_globe(False)
183 | self.old_coordinates = self.calculate_origin_coordinates()
184 | self.globe.set_origin(self.old_coordinates)
185 | self.old_projection = Projections.proj_from_id(self.comboBoxProjections.currentText())
186 | self.globe.set_projection(self.old_projection)
187 | self.globe.change_background_color(self.mColorButtonBackground.color())
188 | self.mColorButtonBackground.setColor(self.iface.mapCanvas().canvasColor())
189 | self.globe.change_project_projection()
190 | self.globe.set_group_visibility(True)
191 | self.add_halo_to_globe()
192 |
193 | @pyqtSlot()
194 | def on_pushButtonAddToLayout_clicked(self):
195 | selected_layouts = tuple(
196 | filter(lambda l: l.name() == self.comboBoxLayouts.currentText(), self.layout_mngr.layouts()))
197 | layout = selected_layouts[0] if len(selected_layouts) == 1 else create_layout("LayoutGlobe", self.qgis_instance)
198 | crs = self.qgis_instance.crs()
199 |
200 | # For some reason layout mode can't handle azimuthal ortographic projection with any decimals and the
201 | # projection needs to be set to project level before attempting to use it in a layout
202 | self.globe.set_origin(
203 | {key: float("{:.0f}".format(val)) for key, val in self.calculate_origin_coordinates().items()})
204 | self.globe.set_projection(Projections.proj_from_id(self.comboBoxProjections.currentText()))
205 | self.globe.change_temporarily_to_globe_projection()
206 | self.globe.delete_group()
207 | self.load_data_to_globe()
208 |
209 | self.add_halo_to_globe()
210 | self.globe.set_group_visibility(False)
211 | self.globe.refresh_theme()
212 | self.globe.add_to_layout(layout, background_color=self.mColorButtonLayoutBackground.color(),
213 | size=self.spinBoxGlobeSize.value())
214 |
215 | self.qgis_instance.setCrs(crs)
216 |
217 | def populate_comboBoxLayouts(self, *args):
218 | self.comboBoxLayouts.clear()
219 | self.comboBoxLayouts.addItem(tr(u"Create new layout (LayoutGlobe)"))
220 | for layout in self.layout_mngr.layouts():
221 | self.comboBoxLayouts.addItem(layout.name())
222 |
223 | def populate_comboBoxProjections(self, *args):
224 | try:
225 | proj_v = proj_version()
226 | except AttributeError:
227 | proj_v = (0, 0)
228 | self.comboBoxProjections.clear()
229 | for projection in Projections:
230 | if proj_v >= projection.value.min_proj:
231 | self.comboBoxProjections.addItem(projection.value.name)
232 |
233 | def add_halo_to_globe(self):
234 | self.globe.add_halo(self.radioButtonHHalo.isChecked(), self.mColorButtonHalo.color(),
235 | self.get_halo_fill_color(), self.radioButtonHFillWithHalo.isChecked())
236 | self.globe.refresh_theme()
237 |
238 | def get_halo_fill_color(self):
239 | return self.mColorButtonHFill.color() if (
240 | self.radioButtonHFill.isChecked() or self.radioButtonHFillWithHalo.isChecked()) else None
241 |
242 | def get_intersecting_countries_color(self):
243 | return self.mColorButtonIntCountries.color() if self.checkBoxIntCountries.isChecked() else None
244 |
245 | def on_geocoding_finished(self, geolocations):
246 | self.geolocations = geolocations.copy()
247 | [self.listWidgetGeocodingResults.addItem(name) for name in self.geolocations.keys()]
248 |
249 | def load_data_to_globe(self, possibly_use_intersecting_colors=True):
250 | self.globe.load_data(self.checkBoxS2cloudless.isChecked(), self.checkBoxCountries.isChecked(),
251 | self.checkBoxGraticules.isChecked(), self.mColorButtonCountries.color(),
252 | self.mColorButtonGraticules.color(),
253 | self.get_intersecting_countries_color() if possibly_use_intersecting_colors else None,
254 | self.comboBoxCountries.currentText().split(" ")[-1],
255 | self.comboBoxGraticules.currentText().split(" ")[-1])
256 |
257 | def get_geocoded_coordinates(self):
258 | coordinates = None
259 | if (len(self.geolocations) and
260 | self.listWidgetGeocodingResults.count() > 0 and
261 | self.listWidgetGeocodingResults.currentItem() is not None):
262 | geolocation = self.listWidgetGeocodingResults.currentItem().text()
263 | coordinates = self.geolocations.get(geolocation, None)
264 | coordinates = {'lon': coordinates[0], 'lat': coordinates[1]}
265 |
266 | return coordinates
267 |
268 | def calculate_origin_coordinates(self):
269 | coordinates = None
270 | try:
271 | if self.radioButtonCoordinates.isChecked():
272 | coordinates = tuple(map(lambda c: float(c.strip()), self.lineEditLonLat.text().split(',')))
273 | coordinates = {'lon': coordinates[0], 'lat': coordinates[1]}
274 |
275 | elif self.radioButtonGeocoding.isChecked():
276 | coordinates = self.get_geocoded_coordinates()
277 | if not coordinates:
278 | coordinates = coordinates
279 | raise ValueError(tr(u"Make sure to select an item from the Geolocation list"))
280 |
281 | elif self.radioButtonLayer.isChecked():
282 | layer = self.mMapLayerComboBox.currentLayer()
283 | if layer is None:
284 | raise ValueError(tr(u"Make sure to have at least one layer in the project"))
285 | center_point = layer.extent().center()
286 |
287 | center_point = transform_to_wgs84(center_point, layer.crs(), self.qgis_instance)
288 | coordinates = {'lon': center_point.x(), 'lat': center_point.y()}
289 |
290 | elif self.radioButtonCenter.isChecked():
291 | coordinates = get_map_center_coordinates(self.iface, self.qgis_instance)
292 |
293 | except ValueError as e:
294 | LOGGER.warning(tr(u"Error occurred while parsing center of the globe"),
295 | bar_msg(f"{tr(u'Traceback')}: {e}", duration=6))
296 | return coordinates
297 |
298 | def projection_changed(self, projection_id: str):
299 | # TODO: check if this causes problems in newer QGIS versions
300 | projection = Projections.proj_from_id(projection_id)
301 | # Centering is disabled due to rendering artifacts
302 | self.centeringGroupBox.setEnabled(projection == Projections.AZIMUTHAL_ORTHOGRAPHIC)
303 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | GNU GENERAL PUBLIC LICENSE
2 | Version 2, June 1991
3 |
4 | Copyright (C) 1989, 1991 Free Software Foundation, Inc.,
5 | 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
6 | Everyone is permitted to copy and distribute verbatim copies
7 | of this license document, but changing it is not allowed.
8 |
9 | Preamble
10 |
11 | The licenses for most software are designed to take away your
12 | freedom to share and change it. By contrast, the GNU General Public
13 | License is intended to guarantee your freedom to share and change free
14 | software--to make sure the software is free for all its users. This
15 | General Public License applies to most of the Free Software
16 | Foundation's software and to any other program whose authors commit to
17 | using it. (Some other Free Software Foundation software is covered by
18 | the GNU Lesser General Public License instead.) You can apply it to
19 | your programs, too.
20 |
21 | When we speak of free software, we are referring to freedom, not
22 | price. Our General Public Licenses are designed to make sure that you
23 | have the freedom to distribute copies of free software (and charge for
24 | this service if you wish), that you receive source code or can get it
25 | if you want it, that you can change the software or use pieces of it
26 | in new free programs; and that you know you can do these things.
27 |
28 | To protect your rights, we need to make restrictions that forbid
29 | anyone to deny you these rights or to ask you to surrender the rights.
30 | These restrictions translate to certain responsibilities for you if you
31 | distribute copies of the software, or if you modify it.
32 |
33 | For example, if you distribute copies of such a program, whether
34 | gratis or for a fee, you must give the recipients all the rights that
35 | you have. You must make sure that they, too, receive or can get the
36 | source code. And you must show them these terms so they know their
37 | rights.
38 |
39 | We protect your rights with two steps: (1) copyright the software, and
40 | (2) offer you this license which gives you legal permission to copy,
41 | distribute and/or modify the software.
42 |
43 | Also, for each author's protection and ours, we want to make certain
44 | that everyone understands that there is no warranty for this free
45 | software. If the software is modified by someone else and passed on, we
46 | want its recipients to know that what they have is not the original, so
47 | that any problems introduced by others will not reflect on the original
48 | authors' reputations.
49 |
50 | Finally, any free program is threatened constantly by software
51 | patents. We wish to avoid the danger that redistributors of a free
52 | program will individually obtain patent licenses, in effect making the
53 | program proprietary. To prevent this, we have made it clear that any
54 | patent must be licensed for everyone's free use or not licensed at all.
55 |
56 | The precise terms and conditions for copying, distribution and
57 | modification follow.
58 |
59 | GNU GENERAL PUBLIC LICENSE
60 | TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
61 |
62 | 0. This License applies to any program or other work which contains
63 | a notice placed by the copyright holder saying it may be distributed
64 | under the terms of this General Public License. The "Program", below,
65 | refers to any such program or work, and a "work based on the Program"
66 | means either the Program or any derivative work under copyright law:
67 | that is to say, a work containing the Program or a portion of it,
68 | either verbatim or with modifications and/or translated into another
69 | language. (Hereinafter, translation is included without limitation in
70 | the term "modification".) Each licensee is addressed as "you".
71 |
72 | Activities other than copying, distribution and modification are not
73 | covered by this License; they are outside its scope. The act of
74 | running the Program is not restricted, and the output from the Program
75 | is covered only if its contents constitute a work based on the
76 | Program (independent of having been made by running the Program).
77 | Whether that is true depends on what the Program does.
78 |
79 | 1. You may copy and distribute verbatim copies of the Program's
80 | source code as you receive it, in any medium, provided that you
81 | conspicuously and appropriately publish on each copy an appropriate
82 | copyright notice and disclaimer of warranty; keep intact all the
83 | notices that refer to this License and to the absence of any warranty;
84 | and give any other recipients of the Program a copy of this License
85 | along with the Program.
86 |
87 | You may charge a fee for the physical act of transferring a copy, and
88 | you may at your option offer warranty protection in exchange for a fee.
89 |
90 | 2. You may modify your copy or copies of the Program or any portion
91 | of it, thus forming a work based on the Program, and copy and
92 | distribute such modifications or work under the terms of Section 1
93 | above, provided that you also meet all of these conditions:
94 |
95 | a) You must cause the modified files to carry prominent notices
96 | stating that you changed the files and the date of any change.
97 |
98 | b) You must cause any work that you distribute or publish, that in
99 | whole or in part contains or is derived from the Program or any
100 | part thereof, to be licensed as a whole at no charge to all third
101 | parties under the terms of this License.
102 |
103 | c) If the modified program normally reads commands interactively
104 | when run, you must cause it, when started running for such
105 | interactive use in the most ordinary way, to print or display an
106 | announcement including an appropriate copyright notice and a
107 | notice that there is no warranty (or else, saying that you provide
108 | a warranty) and that users may redistribute the program under
109 | these conditions, and telling the user how to view a copy of this
110 | License. (Exception: if the Program itself is interactive but
111 | does not normally print such an announcement, your work based on
112 | the Program is not required to print an announcement.)
113 |
114 | These requirements apply to the modified work as a whole. If
115 | identifiable sections of that work are not derived from the Program,
116 | and can be reasonably considered independent and separate works in
117 | themselves, then this License, and its terms, do not apply to those
118 | sections when you distribute them as separate works. But when you
119 | distribute the same sections as part of a whole which is a work based
120 | on the Program, the distribution of the whole must be on the terms of
121 | this License, whose permissions for other licensees extend to the
122 | entire whole, and thus to each and every part regardless of who wrote it.
123 |
124 | Thus, it is not the intent of this section to claim rights or contest
125 | your rights to work written entirely by you; rather, the intent is to
126 | exercise the right to control the distribution of derivative or
127 | collective works based on the Program.
128 |
129 | In addition, mere aggregation of another work not based on the Program
130 | with the Program (or with a work based on the Program) on a volume of
131 | a storage or distribution medium does not bring the other work under
132 | the scope of this License.
133 |
134 | 3. You may copy and distribute the Program (or a work based on it,
135 | under Section 2) in object code or executable form under the terms of
136 | Sections 1 and 2 above provided that you also do one of the following:
137 |
138 | a) Accompany it with the complete corresponding machine-readable
139 | source code, which must be distributed under the terms of Sections
140 | 1 and 2 above on a medium customarily used for software interchange; or,
141 |
142 | b) Accompany it with a written offer, valid for at least three
143 | years, to give any third party, for a charge no more than your
144 | cost of physically performing source distribution, a complete
145 | machine-readable copy of the corresponding source code, to be
146 | distributed under the terms of Sections 1 and 2 above on a medium
147 | customarily used for software interchange; or,
148 |
149 | c) Accompany it with the information you received as to the offer
150 | to distribute corresponding source code. (This alternative is
151 | allowed only for noncommercial distribution and only if you
152 | received the program in object code or executable form with such
153 | an offer, in accord with Subsection b above.)
154 |
155 | The source code for a work means the preferred form of the work for
156 | making modifications to it. For an executable work, complete source
157 | code means all the source code for all modules it contains, plus any
158 | associated interface definition files, plus the scripts used to
159 | control compilation and installation of the executable. However, as a
160 | special exception, the source code distributed need not include
161 | anything that is normally distributed (in either source or binary
162 | form) with the major components (compiler, kernel, and so on) of the
163 | operating system on which the executable runs, unless that component
164 | itself accompanies the executable.
165 |
166 | If distribution of executable or object code is made by offering
167 | access to copy from a designated place, then offering equivalent
168 | access to copy the source code from the same place counts as
169 | distribution of the source code, even though third parties are not
170 | compelled to copy the source along with the object code.
171 |
172 | 4. You may not copy, modify, sublicense, or distribute the Program
173 | except as expressly provided under this License. Any attempt
174 | otherwise to copy, modify, sublicense or distribute the Program is
175 | void, and will automatically terminate your rights under this License.
176 | However, parties who have received copies, or rights, from you under
177 | this License will not have their licenses terminated so long as such
178 | parties remain in full compliance.
179 |
180 | 5. You are not required to accept this License, since you have not
181 | signed it. However, nothing else grants you permission to modify or
182 | distribute the Program or its derivative works. These actions are
183 | prohibited by law if you do not accept this License. Therefore, by
184 | modifying or distributing the Program (or any work based on the
185 | Program), you indicate your acceptance of this License to do so, and
186 | all its terms and conditions for copying, distributing or modifying
187 | the Program or works based on it.
188 |
189 | 6. Each time you redistribute the Program (or any work based on the
190 | Program), the recipient automatically receives a license from the
191 | original licensor to copy, distribute or modify the Program subject to
192 | these terms and conditions. You may not impose any further
193 | restrictions on the recipients' exercise of the rights granted herein.
194 | You are not responsible for enforcing compliance by third parties to
195 | this License.
196 |
197 | 7. If, as a consequence of a court judgment or allegation of patent
198 | infringement or for any other reason (not limited to patent issues),
199 | conditions are imposed on you (whether by court order, agreement or
200 | otherwise) that contradict the conditions of this License, they do not
201 | excuse you from the conditions of this License. If you cannot
202 | distribute so as to satisfy simultaneously your obligations under this
203 | License and any other pertinent obligations, then as a consequence you
204 | may not distribute the Program at all. For example, if a patent
205 | license would not permit royalty-free redistribution of the Program by
206 | all those who receive copies directly or indirectly through you, then
207 | the only way you could satisfy both it and this License would be to
208 | refrain entirely from distribution of the Program.
209 |
210 | If any portion of this section is held invalid or unenforceable under
211 | any particular circumstance, the balance of the section is intended to
212 | apply and the section as a whole is intended to apply in other
213 | circumstances.
214 |
215 | It is not the purpose of this section to induce you to infringe any
216 | patents or other property right claims or to contest validity of any
217 | such claims; this section has the sole purpose of protecting the
218 | integrity of the free software distribution system, which is
219 | implemented by public license practices. Many people have made
220 | generous contributions to the wide range of software distributed
221 | through that system in reliance on consistent application of that
222 | system; it is up to the author/donor to decide if he or she is willing
223 | to distribute software through any other system and a licensee cannot
224 | impose that choice.
225 |
226 | This section is intended to make thoroughly clear what is believed to
227 | be a consequence of the rest of this License.
228 |
229 | 8. If the distribution and/or use of the Program is restricted in
230 | certain countries either by patents or by copyrighted interfaces, the
231 | original copyright holder who places the Program under this License
232 | may add an explicit geographical distribution limitation excluding
233 | those countries, so that distribution is permitted only in or among
234 | countries not thus excluded. In such case, this License incorporates
235 | the limitation as if written in the body of this License.
236 |
237 | 9. The Free Software Foundation may publish revised and/or new versions
238 | of the General Public License from time to time. Such new versions will
239 | be similar in spirit to the present version, but may differ in detail to
240 | address new problems or concerns.
241 |
242 | Each version is given a distinguishing version number. If the Program
243 | specifies a version number of this License which applies to it and "any
244 | later version", you have the option of following the terms and conditions
245 | either of that version or of any later version published by the Free
246 | Software Foundation. If the Program does not specify a version number of
247 | this License, you may choose any version ever published by the Free Software
248 | Foundation.
249 |
250 | 10. If you wish to incorporate parts of the Program into other free
251 | programs whose distribution conditions are different, write to the author
252 | to ask for permission. For software which is copyrighted by the Free
253 | Software Foundation, write to the Free Software Foundation; we sometimes
254 | make exceptions for this. Our decision will be guided by the two goals
255 | of preserving the free status of all derivatives of our free software and
256 | of promoting the sharing and reuse of software generally.
257 |
258 | NO WARRANTY
259 |
260 | 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY
261 | FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN
262 | OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES
263 | PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED
264 | OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
265 | MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS
266 | TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE
267 | PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING,
268 | REPAIR OR CORRECTION.
269 |
270 | 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
271 | WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR
272 | REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES,
273 | INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING
274 | OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED
275 | TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY
276 | YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER
277 | PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE
278 | POSSIBILITY OF SUCH DAMAGES.
279 |
280 | END OF TERMS AND CONDITIONS
281 |
282 | How to Apply These Terms to Your New Programs
283 |
284 | If you develop a new program, and you want it to be of the greatest
285 | possible use to the public, the best way to achieve this is to make it
286 | free software which everyone can redistribute and change under these terms.
287 |
288 | To do so, attach the following notices to the program. It is safest
289 | to attach them to the start of each source file to most effectively
290 | convey the exclusion of warranty; and each file should have at least
291 | the "copyright" line and a pointer to where the full notice is found.
292 |
293 |
294 | Copyright (C)
295 |
296 | This program is free software; you can redistribute it and/or modify
297 | it under the terms of the GNU General Public License as published by
298 | the Free Software Foundation; either version 2 of the License, or
299 | (at your option) any later version.
300 |
301 | This program is distributed in the hope that it will be useful,
302 | but WITHOUT ANY WARRANTY; without even the implied warranty of
303 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
304 | GNU General Public License for more details.
305 |
306 | You should have received a copy of the GNU General Public License along
307 | with this program; if not, write to the Free Software Foundation, Inc.,
308 | 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
309 |
310 | Also add information on how to contact you by electronic and paper mail.
311 |
312 | If the program is interactive, make it output a short notice like this
313 | when it starts in an interactive mode:
314 |
315 | Gnomovision version 69, Copyright (C) year name of author
316 | Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
317 | This is free software, and you are welcome to redistribute it
318 | under certain conditions; type `show c' for details.
319 |
320 | The hypothetical commands `show w' and `show c' should show the appropriate
321 | parts of the General Public License. Of course, the commands you use may
322 | be called something other than `show w' and `show c'; they could even be
323 | mouse-clicks or menu items--whatever suits your program.
324 |
325 | You should also get your employer (if you work as a programmer) or your
326 | school, if any, to sign a "copyright disclaimer" for the program, if
327 | necessary. Here is a sample; alter the names:
328 |
329 | Yoyodyne, Inc., hereby disclaims all copyright interest in the program
330 | `Gnomovision' (which makes passes at compilers) written by James Hacker.
331 |
332 | , 1 April 1989
333 | Ty Coon, President of Vice
334 |
335 | This General Public License does not permit incorporating your program into
336 | proprietary programs. If your program is a subroutine library, you may
337 | consider it more useful to permit linking proprietary applications with the
338 | library. If this is what you want to do, use the GNU Lesser General
339 | Public License instead of this License.
340 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | Globe Builder
2 | =============
3 | 
4 | [](https://codecov.io/github/GispoCoding/GlobeBuilder?branch=master)
5 | 
6 | 
7 | [](https://www.gnu.org/licenses/old-licenses/gpl-2.0.en.html)
8 |
9 | QGIS 3 plugin that is meant for adding globe visualization to the current map using.
10 |
11 | 
12 |
13 | Inspired by these blog posts by
14 | * [statsmapsnpix](http://www.statsmapsnpix.com/2019/09/globe-projections-and-insets-in-qgis.html)
15 | * [Gispo](https://www.gispo.fi/en/blog/the-power-of-community-30daymapchallenge/)
16 | * [gislounge](https://www.gislounge.com/how-to-use-the-equal-earth-projection-using-qgis-on-the-mac/)
17 |
18 |
19 | The plugin is still in beta-development. Please report issues preferably to Issues.
20 |
21 | **Developed by [Gispo Ltd.](https://www.gispo.fi/en/home/)**
22 |
23 | ## Installation instructions
24 | ### QGIS Plugin
25 | The plugin can be installed trough QGIS Plugins Repository. It can also be installed by downloading a release from this
26 | repository:
27 |
28 | 1. Download the latest release zip from GitHub releases (above).
29 |
30 | 2. Launch QGIS and the plugins menu by selecting Plugins - Manage and Install Plugins from the top menu.
31 |
32 | 3. Select the Install from ZIP tab, browse to the zip file you just downloaded, and click Install Plugin!
33 |
34 |
35 | ## Usage
36 |
37 | The Globe could be added either to the current map or to a layout.
38 |
39 | 
40 |
41 | ## Gist
42 | There is also a Github [Gist](https://gist.github.com/Joonalai/7b8693ef904df75cb15cb9af0e82c032) that
43 | provides the core functionality of the plugin. The usage can be seen in the following gif.
44 |
45 | 
46 |
47 |
48 | ## Development
49 |
50 | Refer to [development instructions](docs/development.md).
51 |
52 | ## Licence
53 |
54 | This plugin is licenced with [GNU Genereal Public License, version 2](https://www.gnu.org/licenses/old-licenses/gpl-2.0.en.html).
55 | This plugin uses OpenStreetMap Nominatim geocoding API.
56 | The OpenStreetMap data is licensed under ODbL license.
57 | This plugin lists Sentinel-2 cloudless as an optional data source. Sentinel-2 cloudless data by
58 | by EOX IT Services GmbH (Contains modified Copernicus Sentinel data 2017 & 2018) released under
59 | [Creative Commons Attribution-NonCommercial-ShareAlike 4.0 International License](https://creativecommons.org/licenses/by-nc-sa/4.0/).
60 | For commercial usage of Sentinel-2 cloudless please see https://cloudless.eox.at.
61 |
--------------------------------------------------------------------------------
/docs/development.md:
--------------------------------------------------------------------------------
1 | Development of GlobeBuilder plugin
2 | ===========================
3 |
4 | The code for the plugin is in the [Qaava](../GlobeBuilder) folder. Make sure you have required tools, such as
5 | Qt with Qt Editor and Qt Linquist installed by following this
6 | [tutorial](https://www.qgistutorials.com/en/docs/3/building_a_python_plugin.html#get-the-tools).
7 |
8 | For building the plugin use platform independent [build.py](../GlobeBuilder/build.py) script.
9 |
10 | ### Adding or editing source files
11 | If you create or edit source files make sure that:
12 | * they contain relative imports
13 | ```python
14 | '''file GlobeBuilder/database_tools/db_initializer.py'''
15 |
16 | from ..utils.exceptions import GlobeBuilderAuthConfigException # Good
17 |
18 | from GlobeBuilder.utils.exceptions import GlobeBuilderAuthConfigException # Bad
19 | ```
20 | * they will be found by [build.py](../GlobeBuilder/build.py) script (`py_files` and `ui_files` values)
21 | * you consider adding test files for the new functionality
22 |
23 | ### Deployment
24 |
25 | Edit [build.py](../GlobeBuilder/build.py) to contain working values for *profile*, *lrelease* and *pyrcc*.
26 | If you are running on Windows, make sure the value *QGIS_INSTALLATION_DIR* points to right folder
27 |
28 | Run the deployment with:
29 | ```shell script
30 | python build.py deploy
31 | ```
32 |
33 | After deploying and restarting QGIS you should see the plugin in the QGIS installed plugins
34 | where you have to activate it.
35 |
36 | #### Testing
37 | Install Docker, docker-compose and python packages listed in [requirements.txt](requirements.txt)
38 | to run tests with:
39 |
40 | ```shell script
41 | python build.py test
42 | ```
43 |
44 | #### Translating
45 |
46 | The translation files are in [i18n](../GlobeBuilder/resources/i18n) folder.
47 | Translatable content in python files is code such as `tr(u"Hello World")`.
48 |
49 | To update language *.ts* files to contain newest lines to translate, run
50 | ```shell script
51 | python build.py transup
52 | ```
53 |
54 | You can then open the *.ts* files you wish to translate with Qt Linguist and make the changes.
55 |
56 | Compile the translations to *.qm* files with:
57 | ```shell script
58 | python build.py transcompile
59 | ```
60 |
61 | ### Creating a release
62 | Follow these steps to create a release
63 | * Add changelog information to [CHANGELOG.md](../CHANGELOG.md) using this
64 | [format](https://raw.githubusercontent.com/opengisch/qgis-plugin-ci/master/CHANGELOG.md)
65 | * Make a new commit. (`git add -A && git commit -m "Release v0.1.0"`)
66 | * Create new tag for it (`git tag -a v0.1.0 -m "Version 0.1.0"`)
67 | * Push tag to Github using `git push --follow-tags`
68 | * Create Github release
69 | * [qgis-plugin-ci](https://github.com/opengisch/qgis-plugin-ci) adds release zip automatically as an asset
70 |
--------------------------------------------------------------------------------
/images/icon.xcf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/GispoCoding/GlobeBuilder/3a9ec05ef91e39fb67e52962c3bf24007b2d3046/images/icon.xcf
--------------------------------------------------------------------------------
/images/screenshots/geocoding1.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/GispoCoding/GlobeBuilder/3a9ec05ef91e39fb67e52962c3bf24007b2d3046/images/screenshots/geocoding1.gif
--------------------------------------------------------------------------------
/images/screenshots/globe_view_gist.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/GispoCoding/GlobeBuilder/3a9ec05ef91e39fb67e52962c3bf24007b2d3046/images/screenshots/globe_view_gist.gif
--------------------------------------------------------------------------------
/images/screenshots/layout1.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/GispoCoding/GlobeBuilder/3a9ec05ef91e39fb67e52962c3bf24007b2d3046/images/screenshots/layout1.gif
--------------------------------------------------------------------------------
/requirements-dev.txt:
--------------------------------------------------------------------------------
1 | # Testing
2 | pytest~=6.2.3
3 | pytest-cov~=2.12.1
4 | pytest-qgis~=1.0.1
--------------------------------------------------------------------------------