Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Changing Dataset CRS #758

Open
wants to merge 9 commits into
base: develop
Choose a base branch
from
16 changes: 7 additions & 9 deletions LDMP/areaofinterest.py
Original file line number Diff line number Diff line change
Expand Up @@ -521,12 +521,9 @@ def open_settings():


def prepare_area_of_interest() -> AOI:
if conf.settings_manager.get_value(conf.Setting.CUSTOM_CRS_ENABLED):
crs_dst = qgis.core.QgsCoordinateReferenceSystem(
conf.settings_manager.get_value(conf.Setting.CUSTOM_CRS)
)
else:
crs_dst = qgis.core.QgsCoordinateReferenceSystem("epsg:4326")
crs_dst = conf.settings_crs()
if not crs_dst:
raise TypeError("Dataset CRS not defined or is invalid.")

area_of_interest = AOI(crs_dst)
area_method = conf.settings_manager.get_value(conf.Setting.AREA_FROM_OPTION)
Expand All @@ -549,15 +546,16 @@ def prepare_area_of_interest() -> AOI:
if geojson is None:
raise RuntimeError(error_msg)
area_of_interest.update_from_geojson(
geojson=geojson, wrap=False # FIXME: add the corresponding setting
geojson=geojson,
wrap=conf.settings_manager.get_value(conf.Setting.CUSTOM_CRS_WRAP),
)
elif area_method == conf.AreaSetting.VECTOR_LAYER.value:
vector_path, error_msg = validate_vector_path()
if vector_path is None:
raise RuntimeError(error_msg)
area_of_interest.update_from_file(
f=conf.settings_manager.get_value(conf.Setting.VECTOR_FILE_PATH),
wrap=False, # FIXME: add the corresponding setting
wrap=conf.settings_manager.get_value(conf.Setting.CUSTOM_CRS_WRAP),
)
elif area_method == conf.AreaSetting.POINT.value:
# Area from point
Expand All @@ -573,7 +571,7 @@ def prepare_area_of_interest() -> AOI:
geojson = json.loads(qgis.core.QgsGeometry.fromPointXY(point).asJson())
area_of_interest.update_from_geojson(
geojson=geojson,
wrap=False, # FIXME: add the corresponding setting
wrap=conf.settings_manager.get_value(conf.Setting.CUSTOM_CRS_WRAP),
datatype="point",
)
else:
Expand Down
50 changes: 48 additions & 2 deletions LDMP/calculate.py
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@
from .conf import OPTIONS_TITLE
from .conf import REMOTE_DATASETS
from .conf import Setting
from .conf import settings_crs
from .conf import settings_manager
from .logger import log

Expand Down Expand Up @@ -502,7 +503,7 @@ def update_current_region(self):
self.changed_region.emit()

def run_settings(self):
self.iface.showOptionsDialog(currentPage=OPTIONS_TITLE)
self.iface.showOptionsDialog(self.iface.mainWindow(), currentPage=OPTIONS_TITLE)
self.update_current_region()

def showEvent(self, event):
Expand All @@ -522,7 +523,52 @@ def accept(self):
pass

def settings_btn_clicked(self):
self.iface.showOptionsDialog(currentPage=OPTIONS_TITLE)
self.iface.showOptionsDialog(self.iface.mainWindow(), currentPage=OPTIONS_TITLE)

def _validate_crs_multi_layer(self, layer_defn: list) -> bool:
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It would be worthwhile to offer users an option to automatically reproject any layers that are in a CRS other than that chosen in their user settings. Otherwise there is no easy way for a user to user layers they may have produced in a different CRS (within Trends.Earth that is), without re-running the analysis. Of course there might be boundary effects, due to the reprojection, there might not be transformations among the CRS if they were user defined, etc., but worth offering it as an option if possible.

"""
Compares the CRS of the layer(s) in the given definition against
the one defined for datasets in settings.
Each item in 'layer_defn' should a tuple containing the QgsMapLayer
subclass and the corresponding friendly name to display in the user
message.
"""
is_valid = True
dt_crs = settings_crs()
settings_crs_description = self._crs_description(dt_crs)
msgs = []
for ld in layer_defn:
if not ld[0]:
continue
lyr_crs = ld[0].crs()
layer_name = ld[1]
if lyr_crs != dt_crs:
crs_description = self._crs_description(lyr_crs)
tr_msg = tr_calculate.tr(
f"CRS for {layer_name} ({crs_description}) does not match "
f"the one defined in settings "
f"({settings_crs_description})"
)
msgs.append(tr_msg)

num_msg = len(msgs)
if num_msg > 0:
is_valid = False
if num_msg == 1:
msg = msgs[0]
else:
s = "\n- ".join(msgs)
msg = f"- {s}"
QtWidgets.QMessageBox.critical(self, tr_calculate.tr("CRS Error"), msg)

return is_valid

@classmethod
def _crs_description(cls, crs) -> str:
if not crs:
return cls.tr_calculate.tr("Not defined")

return crs.userFriendlyIdentifier()

def btn_calculate(self):
area_method = settings_manager.get_value(Setting.AREA_FROM_OPTION)
Expand Down
19 changes: 17 additions & 2 deletions LDMP/calculate_drought_vulnerability.py
Original file line number Diff line number Diff line change
Expand Up @@ -271,8 +271,23 @@ def btn_calculate(self):
log("failed layer validation")

return
else:
log("passed all validation checks")

# Check layers' CRS
crs_check_defn = [
(
self.combo_dataset_drought.get_current_layer(),
self.tr("drought (hazard and exposure) layer"),
),
(
self.combo_layer_so3_vulnerability.get_layer(),
self.tr("drought (vulnerability) layer"),
),
]

if not self._validate_crs_multi_layer(crs_check_defn):
return

log("passed all validation checks")

params = {
"task_name": self.options_tab.task_name.text(),
Expand Down
9 changes: 9 additions & 0 deletions LDMP/calculate_lc.py
Original file line number Diff line number Diff line change
Expand Up @@ -194,6 +194,15 @@ def calculate_locally(self):
)
return

# Check layers' CRS
crs_check_defn = [
(initial_layer, self.tr("initial layer")),
(final_layer, self.tr("target layer")),
]

if not self._validate_crs_multi_layer(crs_check_defn):
return

self.close()

trans_matrix = self.lc_define_deg_widget.get_trans_matrix_from_widget()
Expand Down
13 changes: 11 additions & 2 deletions LDMP/calculate_ldn.py
Original file line number Diff line number Diff line change
Expand Up @@ -899,8 +899,17 @@ def _res(layer):
),
)
return False
else:
return True

# Include check for CRS defined in settings
crs_check_defn = [
(model_layer, model_layer_name),
(check_layer, check_layer_name),
]

if not self._validate_crs_multi_layer(crs_check_defn):
return False

return True

def validate_layer_crs(self, combo_boxes, pop_mode):
"""check all layers have the same resolution and CRS"""
Expand Down
12 changes: 11 additions & 1 deletion LDMP/calculate_soc.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@
from . import calculate
from . import data_io
from . import lc_setup
from .conf import settings_crs
from .jobs.manager import job_manager
from .lc_setup import get_trans_matrix
from .logger import log
Expand Down Expand Up @@ -191,7 +192,7 @@ def calculate_locally(self):
self.tr("Warning"),
self.tr(
f"The initial year ({year_initial}) is greater than or "
"equal to the final year ({year_final}) - this analysis "
f"equal to the final year ({year_final}) - this analysis "
"might generate strange results."
),
)
Expand Down Expand Up @@ -224,6 +225,15 @@ def calculate_locally(self):
)
return

# Check layers' CRS
crs_check_defn = [
(initial_layer, self.tr("initial layer")),
(final_layer, self.tr("target layer")),
]

if not self._validate_crs_multi_layer(crs_check_defn):
return

self.close()

initial_usable = self.lc_setup_widget.initial_year_layer_cb.get_current_band()
Expand Down
33 changes: 33 additions & 0 deletions LDMP/conf.py
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,7 @@ class Setting(enum.Enum):
BASE_DIR = "advanced/base_data_directory"
CUSTOM_CRS_ENABLED = "region_of_interest/custom_crs_enabled"
CUSTOM_CRS = "region_of_interest/custom_crs"
CUSTOM_CRS_WRAP = "region_of_interest/custom_crs_wrap"
POLL_REMOTE = "advanced/poll_remote_server"
REMOTE_POLLING_FREQUENCY = "advanced/remote_polling_frequency_seconds"
DOWNLOAD_RESULTS = "advanced/download_remote_results_automatically"
Expand Down Expand Up @@ -111,6 +112,7 @@ class SettingsManager:
),
Setting.CUSTOM_CRS_ENABLED: False,
Setting.CUSTOM_CRS: "epsg:4326",
Setting.CUSTOM_CRS_WRAP: False,
Setting.POLL_REMOTE: True,
Setting.DOWNLOAD_RESULTS: True,
Setting.BUFFER_CHECKED: False,
Expand Down Expand Up @@ -204,6 +206,37 @@ def _load_algorithm_config(
)


def settings_crs() -> qgis.core.QgsCoordinateReferenceSystem:
"""
Returns the CRS stored in settings or None if settings is not defined
or if the CRS is invalid.
"""
crs_str = settings_manager.get_value(Setting.CUSTOM_CRS)
if not crs_str:
return None

crs = qgis.core.QgsCoordinateReferenceSystem(crs_str)
if not crs.isValid():
return None

return crs


def settings_crs_as_wkt():
"""
Returns the CRS stored in settings as a WKT string or an empty string
if the CRS is not defined or is invalid.
The variant of the WKT is biased for use with the GDAL library, else
this can be changed by specifying the
QgsCoordinateReferenceSystem.WktVariant flag.
"""
crs = settings_crs()
if not crs:
return ""

return crs.toWkt(qgis.core.QgsCoordinateReferenceSystem.WktVariant.WKT1_GDAL)


datasets_file = os.path.join(
os.path.dirname(os.path.realpath(__file__)), "data", "gee_datasets.json"
)
Expand Down
Loading