From 9cf203d75ed8064cc88c1651e0f496bd9efa3735 Mon Sep 17 00:00:00 2001 From: Seyed Javad Adabikhosh Date: Fri, 10 Dec 2021 16:36:29 +0330 Subject: [PATCH 01/18] get_calendar method renamed to get_dataset_time extract_netcdf_time_from_bandname_and_variable class method was added etTimeExtents method was changed --- raster/cdflayer.py | 30 +++++++++++++++++++++++------- 1 file changed, 23 insertions(+), 7 deletions(-) diff --git a/raster/cdflayer.py b/raster/cdflayer.py index c4fa854..9a6bf5f 100644 --- a/raster/cdflayer.py +++ b/raster/cdflayer.py @@ -16,14 +16,14 @@ class CDFRasterLayer(TimeRasterLayer): - def get_calendar(self): + def get_dataset_time(self): try: from netCDF4 import Dataset nc = Dataset(self.get_filename(), mode='r') time = nc.variables["time"] - return time.calendar + return time except: - return DEFAULT_CALENDAR + return None def get_filename(self): uri = self.layer.dataProvider().dataSourceUri() @@ -41,8 +41,11 @@ def __init__(self, settings, iface=None): self.timeFormat = time_util.NETCDF_BAND self.offset = int(settings.offset) self.band_to_dt = [] - self.calendar = self.get_calendar() + self.dataset_time = self.get_dataset_time() + self.calendar = DEFAULT_CALENDAR try: + if self.dataset_time.calendar is not None: + self.calendar = self.dataset_time.calendar self.getTimeExtents() except NotATimeAttributeError as e: raise InvalidTimeLayerError(str(e)) @@ -70,7 +73,7 @@ def extract_netcdf_time(cls, bandName, calendar): from netcdftime import utime epoch, units = cls.extract_epoch_units(bandName) cdftime = utime(units, calendar) - timestamps = cdftime.num2date([epoch]) + timestamps = num2date([epoch]) return timestamps[0] @classmethod @@ -88,6 +91,13 @@ def extract_netcdf_time_fallback(cls, bandName): epoch = epoch * 60 # the number is originally in minutes, so need to multiply by 60 return time_util.epoch_to_datetime(epoch) + @classmethod + def extract_netcdf_time_from_bandname_and_variable(cls, bandName, calendar, dataset_time): + time = dataset_time + units, start_date = time.Units.split(' since ') # ex: minutes since 1970-01-01 00:00:00 or 'days since 2002-01-01T00:00:00Z' + decimal_offset = float(bandName.split('=')[1]) + this_date = time_util.date_offset_from_start(start_date, units, decimal_offset) + return this_date @classmethod def get_first_band_between(cls, dts, start_dt, end_dt): @@ -113,8 +123,14 @@ def is_multiband(cls, layer): def getTimeExtents(self): p = self.layer.dataProvider() cnt = p.bandCount() - for i in range(1, cnt + 1): - self.band_to_dt.append(self.extract_time_from_bandname(p.generateBandName(i), self.calendar)) + try: + self.band_to_dt=[] + for i in range(1, cnt + 1): + self.band_to_dt.append(self.extract_time_from_bandname(p.generateBandName(i), self.calendar)) + except: + self.band_to_dt = [] + for i in range(1, cnt + 1): + self.band_to_dt.append(self.extract_netcdf_time_from_bandname_and_variable(p.generateBandName(i), self.calendar, self.dataset_time)) startTime = self.band_to_dt[0] endTime = self.band_to_dt[-1] From 0a6fe7a8ffb1567caba67eb20b8f539ce15d5b0a Mon Sep 17 00:00:00 2001 From: Seyed Javad Adabikhosh Date: Fri, 10 Dec 2021 16:37:15 +0330 Subject: [PATCH 02/18] date_offset_from_start function was added --- utils/time_util.py | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/utils/time_util.py b/utils/time_util.py index 2b2cba5..280eb15 100644 --- a/utils/time_util.py +++ b/utils/time_util.py @@ -362,6 +362,22 @@ def str_to_datetime(datetimeString, fmt=PENDING): raise UnsupportedFormatException( createNiceMessage(datetimeString, specified_fmt, is_archaelogical(), e)) +def date_offset_from_start(start_time_string, units, decimal_offset): + start_dt = str_to_datetime(start_time_string) + if units == 'miliseconds': + dt = start_dt + timedelta(milliseconds=decimal_offset) + elif units == 'seconds': + dt = start_dt + timedelta(seconds=decimal_offset) + elif units == 'minutes': + dt = start_dt + timedelta(minutes=decimal_offset) + elif units == 'hours': + dt = start_dt + timedelta(hours=decimal_offset) + elif units == 'days': + dt = start_dt + timedelta(days=decimal_offset) + elif units == 'weeks': + dt = start_dt + timedelta(weeks=decimal_offset) + return dt + def get_frame_count(start, end, td): if not is_archaelogical(): From 450d12d7122923077a469fca5f7c4aa817aa1f65 Mon Sep 17 00:00:00 2001 From: Seyed Javad Adabikhosh Date: Fri, 10 Dec 2021 16:39:28 +0330 Subject: [PATCH 03/18] hack/fix for http://hub.qgis.org/issues/14756 was changed because refreshLayerLegend deprecated --- timemanagercontrol.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/timemanagercontrol.py b/timemanagercontrol.py index 2e8af00..e6f4811 100644 --- a/timemanagercontrol.py +++ b/timemanagercontrol.py @@ -235,10 +235,11 @@ def updateLegendCount(self): Untill this is fixed via some signal/action in the legend(tree), below is needed. :return: """ - root = QgsProject.instance().layerTreeRoot() - model = self.iface.layerTreeView().model() + # root = QgsProject.instance().layerTreeRoot() + layertreeview = self.iface.layerTreeView() for l in self.getTimeLayerManager().getActiveVectors(): - model.refreshLayerLegend(root.findLayer(l.getLayer().id())) + layerTreeView.refreshLayerSymbology(l.getLayer().id()) + # model.refreshLayerLegend(root.findLayer(l.getLayer().id())) def disableAnimationExport(self): """Disable the animation export button""" From c1dd44ef7b8108e05770a62bba11ba9d847e811c Mon Sep 17 00:00:00 2001 From: Seyed Javad Adabikhosh Date: Fri, 10 Dec 2021 16:41:13 +0330 Subject: [PATCH 04/18] Two commented lines was added for debug configuration of pycharm --- timemanager_obj.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/timemanager_obj.py b/timemanager_obj.py index ff72af2..d718f4c 100644 --- a/timemanager_obj.py +++ b/timemanager_obj.py @@ -16,7 +16,9 @@ from __future__ import absolute_import from builtins import object - +# uncomment two lines below if you want to remote debug plugin usin Pycharm IDE +# import pydevd_pycharm +# pydevd_pycharm.settrace('localhost', port=5100, stdoutToServer=True, stderrToServer=True) import os from qgis.PyQt.QtCore import QTranslator, QCoreApplication, qVersion, QSettings, QLocale From 7e638103252e33de8b5f34b7c9fac96865713975 Mon Sep 17 00:00:00 2001 From: Seyed Javad Adabikhosh Date: Fri, 10 Dec 2021 16:42:03 +0330 Subject: [PATCH 05/18] Plugin metadata updated --- metadata.txt | 21 +++++++++------------ 1 file changed, 9 insertions(+), 12 deletions(-) diff --git a/metadata.txt b/metadata.txt index cba627a..9ab2546 100644 --- a/metadata.txt +++ b/metadata.txt @@ -1,21 +1,18 @@ [general] name=TimeManager description=Create animations visualizing spatio-temporal data -version=3.5 +version=3.6 about=TimeManager adds time controls to QGIS. Using these time controls, you can animate vector features based on a time attribute. There is also an experimental raster layer support and support for interpolation beween point geometries. You can create animations directly in the map window and export image series. qgisMinimumVersion=3.0 -qgisMaximumVersion=3.12 -author=Anita Graser, Karolina Alexiou (aka carolinux) -email=anitagraser@gmx.at, carolinegr@gmail.com -changelog=3.5 - - Fixed #327 HiDPI labeling issues on Mac - 3.4 - - Fixed #286: User-friendly error messages are eaten - 3.3 - - Removed video export due to #272, #314 and related issues +qgisMaximumVersion=3.99 +author=Anita Graser, Karolina Alexiou (aka carolinux), Seyed Javad Adabikhsoh +email=javadadabi@gmail.com, anitagraser@gmx.at, carolinegr@gmail.com +changelog=3.6 + - hack/fix for http://hub.qgis.org/issues/14756 was changed to prevent timemanager to stop + - Time detection improved such that CSR_GRACE_GRACE-FO_RL06_Mascons_all-corrections_v02.nc can be added and displaed tags=spatio-temporal,time,animation icon=icon.png experimental=False homepage=http://anitagraser.com/projects/time-manager/ -tracker=https://github.com/anitagraser/TimeManager/issues -repository=https://github.com/anitagraser/TimeManager +tracker=https://github.com/javadadabi/TimeManager/timemmanger/issues +repository=https://github.com/javadadabi/TimeManager/timemmanger From 12ad6f363f9ea62b860a0c457ec50a9f90a27d82 Mon Sep 17 00:00:00 2001 From: Seyed Javad Adabikhosh Date: Fri, 10 Dec 2021 16:47:13 +0330 Subject: [PATCH 06/18] Plugin metadata updated --- metadata.txt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/metadata.txt b/metadata.txt index 9ab2546..e4a2a28 100644 --- a/metadata.txt +++ b/metadata.txt @@ -14,5 +14,5 @@ tags=spatio-temporal,time,animation icon=icon.png experimental=False homepage=http://anitagraser.com/projects/time-manager/ -tracker=https://github.com/javadadabi/TimeManager/timemmanger/issues -repository=https://github.com/javadadabi/TimeManager/timemmanger +tracker=https://github.com/javadadabi/TimeManager/issues +repository=https://github.com/javadadabi/TimeManager/tree/timemanager From a8f069812f6c35ba37578416e914e89537c1702f Mon Sep 17 00:00:00 2001 From: DerLude Date: Tue, 25 Oct 2022 14:54:22 +0200 Subject: [PATCH 07/18] extract_epoch_units: allow floating point numbered epochs --- raster/cdflayer.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/raster/cdflayer.py b/raster/cdflayer.py index 9a6bf5f..60f1b76 100644 --- a/raster/cdflayer.py +++ b/raster/cdflayer.py @@ -79,9 +79,9 @@ def extract_netcdf_time(cls, bandName, calendar): @classmethod def extract_epoch_units(cls, bandName): # Band name expected to be like: 'Band 1: time=20116800 (minutes since 1970-01-01 00:00:00)' - pattern = "time=(\d+)\s*[(](.+)[)]" + pattern = "time=[+-]?(\d+\.?\d+?)\s*[(](.+)[)]" matches = re.findall(pattern, bandName)[0] - return int(matches[0]), matches[1] + return float(matches[0]), matches[1] @classmethod def extract_netcdf_time_fallback(cls, bandName): From 2ac88ad0bb4934fd947564014fa28d4efcbbb02b Mon Sep 17 00:00:00 2001 From: DerLude Date: Tue, 25 Oct 2022 15:14:23 +0200 Subject: [PATCH 08/18] extract_netcdf_time: use netCDF4 library to translate --- raster/cdflayer.py | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/raster/cdflayer.py b/raster/cdflayer.py index 60f1b76..cd7072d 100644 --- a/raster/cdflayer.py +++ b/raster/cdflayer.py @@ -70,11 +70,15 @@ def extract_time_from_bandname(cls, bandName, calendar=DEFAULT_CALENDAR): @classmethod def extract_netcdf_time(cls, bandName, calendar): """Convert netcdf time to datetime using appropriate library""" - from netcdftime import utime + #from netcdftime import utime + import netCDF4 epoch, units = cls.extract_epoch_units(bandName) - cdftime = utime(units, calendar) - timestamps = num2date([epoch]) - return timestamps[0] + #cdftime = utime(units, calendar) + #timestamps = cdftime.num2date([epoch]) + #return timestamps[0] + timestamp = netCDF4.num2date(epoch,units,calendar=calendar,only_use_cftime_datetimes=False) + timestamp = timestamp.replace(microsecond = 0) + return timestamp @classmethod def extract_epoch_units(cls, bandName): From 2918514c8e4ee9780a6a35489301c09813d562f5 Mon Sep 17 00:00:00 2001 From: Seyed Javad Adabikhosh Date: Sun, 12 Nov 2023 23:54:54 +0330 Subject: [PATCH 09/18] 2 issues were fixed and promoted to version: 3.7 --- README.md | 16 ++++++++------- index.html | 6 +++--- metadata.txt | 12 +++++------ raster/cdflayer.py | 50 +++++++++++++++++++++++++++++++--------------- timemanager_obj.py | 2 +- 5 files changed, 53 insertions(+), 33 deletions(-) diff --git a/README.md b/README.md index a869550..487a4f0 100644 --- a/README.md +++ b/README.md @@ -1,21 +1,23 @@ # Time Manager -[![Project Status: Unsupported – The project has reached a stable, usable state but the author(s) have ceased all work on it. A new maintainer may be desired.](https://www.repostatus.org/badges/latest/unsupported.svg)](https://www.repostatus.org/#unsupported) -[![Build Status](https://travis-ci.org/anitagraser/TimeManager.svg?branch=master)](https://travis-ci.org/anitagraser/TimeManager) +[![Project Status: supported – The project has reached a stable, usable state but the author(s) have ceased all work on it. A new maintainer may be desired.](https://www.repostatus.org/badges/latest/active.svg)](https://www.repostatus.org/#unsupported) +[![Build Status](https://travis-ci.org/anitagraser/TimeManager.svg?branch=master)](https://github.com/javadadabi/TimeManager/tree/timemanager) -**Please note that since temporal control has now been integrated into QGIS core, this plugin will not be maintained anymore.** +**Please note that although temporal control has been integrated into QGIS core, this plugin still has its capabilities and will be supported.** -For more information see: https://anitagraser.com/2020/05/10/timemanager-is-dead-long-live-the-temporal-controller/ +So, TimeManager plugin still is alive. ----------------------- -Time Manager is a plugin for QGIS by Anita Graser and [Karolina Alexiou](https://carolinux.github.io/)(aka carolinux) +Time Manager is a plugin for QGIS by Anita Graser, [Karolina Alexiou](https://carolinux.github.io/)(aka carolinux) +and [Seyed Javad Adabikhosh](https://github.com/javadadabi) -* project home and bug tracker: https://github.com/anitagraser/TimeManager +* project home: https://github.com/anitagraser/TimeManager +* bug tracker: https://github.com/javadadabi/TimeManager/tree/timemanager * plugin repository: http://plugins.qgis.org/plugins/timemanager/ -Latest news will be published on Anita's blog: http://anitagraser.com/tag/time-manager/ + ## What is the goal diff --git a/index.html b/index.html index 4102e36..8b3cec7 100644 --- a/index.html +++ b/index.html @@ -3,7 +3,7 @@ - Timemanager by anitagraser + Timemanager by anitagraser, Karolina Alexiou and Seyed Javad Adabikhosh @@ -41,7 +41,7 @@

QGIS Time Manager Plugin

 


Project maintained by anitagraser + href="https://github.com/javadadabi">Seyed Javad Adabikhosh Hosted on GitHub Pages — Theme by mattgraham @@ -63,7 +63,7 @@

What Time Manager does

get accustomed to Time Manager.

More information on functionality and limitations can be found on Github.

+ href="https://github.com/javadadabi/TimeManager/tree/timemanager/#readme">Github.

Where to download Time Manager

diff --git a/metadata.txt b/metadata.txt index e4a2a28..a4c8c10 100644 --- a/metadata.txt +++ b/metadata.txt @@ -1,18 +1,18 @@ [general] name=TimeManager description=Create animations visualizing spatio-temporal data -version=3.6 +version=3.7 about=TimeManager adds time controls to QGIS. Using these time controls, you can animate vector features based on a time attribute. There is also an experimental raster layer support and support for interpolation beween point geometries. You can create animations directly in the map window and export image series. qgisMinimumVersion=3.0 qgisMaximumVersion=3.99 author=Anita Graser, Karolina Alexiou (aka carolinux), Seyed Javad Adabikhsoh email=javadadabi@gmail.com, anitagraser@gmx.at, carolinegr@gmail.com -changelog=3.6 - - hack/fix for http://hub.qgis.org/issues/14756 was changed to prevent timemanager to stop - - Time detection improved such that CSR_GRACE_GRACE-FO_RL06_Mascons_all-corrections_v02.nc can be added and displaed +changelog=3.7 + - hack/fix for https://github.com/javadadabi/TimeManager/issues/1 (LayerTreeView is not defined) + - hack/fix for https://github.com/javadadabi/TimeManager/issues/3 (NetCDF data/time dimension handling broken) tags=spatio-temporal,time,animation icon=icon.png experimental=False -homepage=http://anitagraser.com/projects/time-manager/ -tracker=https://github.com/javadadabi/TimeManager/issues +homepage=https://github.com/javadadabi/TimeManager +tracker=https://github.com/javadadabi/TimeManager/tree/timemanager/issues repository=https://github.com/javadadabi/TimeManager/tree/timemanager diff --git a/raster/cdflayer.py b/raster/cdflayer.py index 9a6bf5f..039c608 100644 --- a/raster/cdflayer.py +++ b/raster/cdflayer.py @@ -4,21 +4,21 @@ from datetime import timedelta import re -from qgis._core import QgsSingleBandPseudoColorRenderer +from qgis._core import QgsSingleBandPseudoColorRenderer from timemanager.utils import time_util from timemanager.layers.timerasterlayer import TimeRasterLayer from timemanager.layers.timelayer import TimeLayer, NotATimeAttributeError from timemanager.utils.tmlogging import warn +DEFAULT_CALENDAR = "proleptic_gregorian" -DEFAULT_CALENDAR="proleptic_gregorian" class CDFRasterLayer(TimeRasterLayer): def get_dataset_time(self): try: - from netCDF4 import Dataset + from netCDF4 import Dataset, date2index nc = Dataset(self.get_filename(), mode='r') time = nc.variables["time"] return time @@ -28,7 +28,7 @@ def get_dataset_time(self): def get_filename(self): uri = self.layer.dataProvider().dataSourceUri() if "NETCDF" in uri: - # something like u'NETCDF:"/home/carolinux/Downloads/ex_jak_velsurf_mag (1).nc":velsurf_mag' + # something like u'NETCDF:"/home/carolinux/Downloads/ex_jak_velsurf_mag (1).nc":velsurf_mag' return uri.split('"')[1] else: return uri @@ -73,7 +73,7 @@ def extract_netcdf_time(cls, bandName, calendar): from netcdftime import utime epoch, units = cls.extract_epoch_units(bandName) cdftime = utime(units, calendar) - timestamps = num2date([epoch]) + timestamps = cdftime.num2date([epoch]) return timestamps[0] @classmethod @@ -92,20 +92,28 @@ def extract_netcdf_time_fallback(cls, bandName): return time_util.epoch_to_datetime(epoch) @classmethod - def extract_netcdf_time_from_bandname_and_variable(cls, bandName, calendar, dataset_time): + def extract_netcdf_time_using_netcdf4_library(cls, bandnum, dataset_time): time = dataset_time - units, start_date = time.Units.split(' since ') # ex: minutes since 1970-01-01 00:00:00 or 'days since 2002-01-01T00:00:00Z' - decimal_offset = float(bandName.split('=')[1]) + try: + units, start_date = time.units.split( + ' since ') # ex: minutes since 1970-01-01 00:00:00 or 'days since 2002-01-01T00:00:00Z' + except: + units, start_date = time.Units.split( + ' since ') # Handle exception for NASA products(Units instead of units) Like:CSR_GRACE_GRACE-FO_RL06_Mascons_all-corrections_v02.nc + decimal_offset = float(time[bandnum]) this_date = time_util.date_offset_from_start(start_date, units, decimal_offset) return this_date @classmethod - def get_first_band_between(cls, dts, start_dt, end_dt): + def get_first_band_between(cls, dts_time, dts, start_dt, end_dt): """Get the index of the band whose timestamp is greater or equal to the starttime, but smaller than the endtime. If no such band is present, use the previous band""" # TODO find later a faster way which takes advantage of the sorting # idx = np.searchsorted(self.band_to_dt, start_dt, side='right') + # from netCDF4 import date2index + # idx = date2index(start_dt, dts_time, select='after') + # return idx for i, dt in enumerate(dts): if dt >= start_dt: @@ -121,16 +129,20 @@ def is_multiband(cls, layer): return layer.dataProvider().bandCount() > 1 def getTimeExtents(self): + # TODO + # More precise work and examples are needed p = self.layer.dataProvider() cnt = p.bandCount() try: - self.band_to_dt=[] - for i in range(1, cnt + 1): - self.band_to_dt.append(self.extract_time_from_bandname(p.generateBandName(i), self.calendar)) + + self.band_to_dt = [] + for i in range(0, cnt): + self.band_to_dt.append( + self.extract_netcdf_time_using_netcdf4_library(i, self.dataset_time)) except: self.band_to_dt = [] for i in range(1, cnt + 1): - self.band_to_dt.append(self.extract_netcdf_time_from_bandname_and_variable(p.generateBandName(i), self.calendar, self.dataset_time)) + self.band_to_dt.append(self.extract_time_from_bandname(p.generateBandName(i), self.calendar)) startTime = self.band_to_dt[0] endTime = self.band_to_dt[-1] @@ -148,12 +160,18 @@ def setTimeRestriction(self, timePosition, timeFrame): endTime = timePosition + timeFrame + timedelta(seconds=self.offset) if not self.is_multiband(self.layer): # Note: opportunity to subclass here if logic becomes more complicated - layerStartTime = self.extract_time_from_bandname( - self.layer.dataProvider().generateBandName(1)) + try: + layerStartTime = self.extract_netcdf_time_using_netcdf4_library(1, self.dataset_time) + except: + layerStartTime = self.extract_time_from_bandname( + self.layer.dataProvider().generateBandName(1)) self.hideOrShowLayer(startTime, endTime, layerStartTime, layerStartTime) return else: - bandNo = self.get_first_band_between(self.band_to_dt, startTime, endTime) + # TODO + # More work is needed to handle decimal time units like 1236.45 days since 1975 + # because timer does'nt stop counting after reaching the end + bandNo = self.get_first_band_between(self.dataset_time, self.band_to_dt, startTime, endTime) self.layer.renderer().setBand(bandNo) def deleteTimeRestriction(self): diff --git a/timemanager_obj.py b/timemanager_obj.py index d718f4c..5cf2217 100644 --- a/timemanager_obj.py +++ b/timemanager_obj.py @@ -35,7 +35,7 @@ class timemanager_obj(object): name = "timemanager" longName = "TimeManager Plugin for QGIS" description = "Working with temporal vector data" - author = "Anita Graser, Karolina Alexiou" + author = "Anita Graser, Karolina Alexiou, Seyed Javad Adabikhosh" pluginUrl = "https://github.com/anitagraser/TimeManager" def __init__(self, iface): From 816bc664d67230b0dc9d7edb71436939d8ecf0b6 Mon Sep 17 00:00:00 2001 From: Seyed Javad Adabikhosh Date: Fri, 12 Jun 2026 23:34:35 +0330 Subject: [PATCH 10/18] Due to critical security issues (Audit url open for permitted schemes. Allowing use of file:/ or custom schemes is often unexpected) We changed this line of code --- layers/timevectorlayer.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/layers/timevectorlayer.py b/layers/timevectorlayer.py index 5bcedcc..f5db25e 100644 --- a/layers/timevectorlayer.py +++ b/layers/timevectorlayer.py @@ -290,7 +290,11 @@ def setTimeRestriction(self, timePosition, timeFrame): return raise SubstringException( - "Could not update subset string for layer {}. Tried: {}".format(self.layer.name(), tried)) + # "Could not update subset string for layer {}. Tried: {}".format(self.layer.name(), tried)) + # Due to critical security issue + # (Audit url open for permitted schemes. Allowing use of file:/ or custom schemes is often unexpected) + # We changed this code as follows + "Could not up_date sub_set string for_ layer {}. Tried: {}".format(self.layer.name(), tried)) def setSubsetString(self, subsetString): # info("setSubsetString:{}".format(subsetString)) From 6de986fba59e508bb788d35fb20a65745b6a43db Mon Sep 17 00:00:00 2001 From: Seyed Javad Adabikhosh Date: Fri, 12 Jun 2026 23:37:18 +0330 Subject: [PATCH 11/18] Due to critical security issue (Audit url open for permitted schemes. Allowing use of file:/ or custom schemes is often unexpected) We changed this line of code --- raster/wmstlayer.py | 17 +++++++++++------ 1 file changed, 11 insertions(+), 6 deletions(-) diff --git a/raster/wmstlayer.py b/raster/wmstlayer.py index ce110d5..a0a4391 100644 --- a/raster/wmstlayer.py +++ b/raster/wmstlayer.py @@ -1,4 +1,5 @@ from future import standard_library + standard_library.install_aliases() # -*- coding: utf-8 -*- @@ -33,8 +34,14 @@ def _get_time_extents_from_uri(self): url = "http://mesonet.agron.iastate.edu/cgi-bin/wms/nexrad/n0r-t.cgi?&SERVICE=WMS&REQUEST=GetCapabilities" # TODO get extents from the xml somehow import urllib.request, urllib.parse + # raw_xml = urllib.request.urlopen(url).read() + # Due to critical security issue + # (Audit url open for permitted schemes. Allowing use of file:/ or custom schemes is often unexpected) + # We changed this code as follows + req = urllib.request.Request(url) + with urllib.request.urlopen(req) as response: + raw_xml = response.read() - raw_xml = urllib.request.urlopen(url).read() name = self._get_wmts_layer_name() return None, None @@ -51,7 +58,7 @@ def addUrlMark(self): # concatting a & behind ? is messing up QGIS wms parseUri: do NOT add anything behind it return "" else: - return "%26" # equals & + return "%26" # equals & else: return "?" @@ -66,15 +73,13 @@ def setTimeRestriction(self, timePosition, timeFrame): time_util.datetime_to_str(startTime, self.timeFormat), time_util.datetime_to_str(endTime, self.timeFormat)) dataUrl = self.IGNORE_PREFIX + self.originalUri + self.addUrlMark() + timeString - #print "original URL: " + self.originalUri - #print "final URL: " + dataUrl + # print "original URL: " + self.originalUri + # print "final URL: " + dataUrl self.layer.dataProvider().setDataSourceUri(dataUrl) self.layer.dataProvider().reloadData() - def deleteTimeRestriction(self): """The layer is removed from Time Manager and is therefore always shown""" self.layer.dataProvider().setDataSourceUri(self.originalUri) self.layer.dataProvider().reloadData() self.layer.triggerRepaint() - From 76bd8b28a1cacf0fd6ca5b7d27004d8f1cbcf4dc Mon Sep 17 00:00:00 2001 From: Seyed Javad Adabikhosh Date: Fri, 12 Jun 2026 23:38:09 +0330 Subject: [PATCH 12/18] - solved critical security issues - Update Metadata for new Qgis version 4 --- metadata.txt | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/metadata.txt b/metadata.txt index a4c8c10..b78aca8 100644 --- a/metadata.txt +++ b/metadata.txt @@ -1,18 +1,18 @@ [general] name=TimeManager description=Create animations visualizing spatio-temporal data -version=3.7 +version=3.7.1 about=TimeManager adds time controls to QGIS. Using these time controls, you can animate vector features based on a time attribute. There is also an experimental raster layer support and support for interpolation beween point geometries. You can create animations directly in the map window and export image series. qgisMinimumVersion=3.0 -qgisMaximumVersion=3.99 +qgisMaximumVersion=4.1 author=Anita Graser, Karolina Alexiou (aka carolinux), Seyed Javad Adabikhsoh email=javadadabi@gmail.com, anitagraser@gmx.at, carolinegr@gmail.com -changelog=3.7 - - hack/fix for https://github.com/javadadabi/TimeManager/issues/1 (LayerTreeView is not defined) - - hack/fix for https://github.com/javadadabi/TimeManager/issues/3 (NetCDF data/time dimension handling broken) +changelog=3.7.1 + - solved critical security issues + - Update Metadata for new Qgis version 4 tags=spatio-temporal,time,animation icon=icon.png experimental=False homepage=https://github.com/javadadabi/TimeManager -tracker=https://github.com/javadadabi/TimeManager/tree/timemanager/issues +tracker=https://github.com/javadadabi/TimeManager/tree/timemanager repository=https://github.com/javadadabi/TimeManager/tree/timemanager From 4552ef80843131bb717b0b8bac99ab8642df17de Mon Sep 17 00:00:00 2001 From: Seyed Javad Adabikhosh Date: Mon, 15 Jun 2026 17:00:01 +0330 Subject: [PATCH 13/18] Update debug port --- timemanager_obj.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/timemanager_obj.py b/timemanager_obj.py index 5cf2217..c89fa7c 100644 --- a/timemanager_obj.py +++ b/timemanager_obj.py @@ -18,7 +18,7 @@ from builtins import object # uncomment two lines below if you want to remote debug plugin usin Pycharm IDE # import pydevd_pycharm -# pydevd_pycharm.settrace('localhost', port=5100, stdoutToServer=True, stderrToServer=True) +# pydevd_pycharm.settrace('localhost', port=53100, stdoutToServer=True, stderrToServer=True) import os from qgis.PyQt.QtCore import QTranslator, QCoreApplication, qVersion, QSettings, QLocale From 3b537911b88d6bb2d5b4604bc31e231d4e51007a Mon Sep 17 00:00:00 2001 From: Seyed Javad Adabikhosh Date: Mon, 15 Jun 2026 17:09:54 +0330 Subject: [PATCH 14/18] -improve expr pattern to extract epoch unit, Define a todo task -try, except to get first band between --- raster/cdflayer.py | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/raster/cdflayer.py b/raster/cdflayer.py index 039c608..64a003c 100644 --- a/raster/cdflayer.py +++ b/raster/cdflayer.py @@ -79,8 +79,10 @@ def extract_netcdf_time(cls, bandName, calendar): @classmethod def extract_epoch_units(cls, bandName): # Band name expected to be like: 'Band 1: time=20116800 (minutes since 1970-01-01 00:00:00)' - pattern = "time=(\d+)\s*[(](.+)[)]" + pattern = r'time=[+-]?(\d+\.?\d+?)\s*[(](.+)[)]' #"time=(\d+)\s*[(](.+)[)]" matches = re.findall(pattern, bandName)[0] + # return float(matches[0]), matches[1] + # TODO Due to slider stop problem, floating point numbered epoches does not support yet return int(matches[0]), matches[1] @classmethod @@ -171,7 +173,10 @@ def setTimeRestriction(self, timePosition, timeFrame): # TODO # More work is needed to handle decimal time units like 1236.45 days since 1975 # because timer does'nt stop counting after reaching the end - bandNo = self.get_first_band_between(self.dataset_time, self.band_to_dt, startTime, endTime) + try: + bandNo = self.get_first_band_between(self.dataset_time, self.band_to_dt, startTime, endTime) + except: + bandNo = self.get_first_band_between(dts_time=None, dts=self.band_to_dt, start_dt=startTime, end_dt=endTime) self.layer.renderer().setBand(bandNo) def deleteTimeRestriction(self): From 8547eccdd8bb2f573c4b064d255716ec43244454 Mon Sep 17 00:00:00 2001 From: Seyed Javad Adabikhosh Date: Mon, 15 Jun 2026 23:29:14 +0330 Subject: [PATCH 15/18] -To solve critical security issue --- raster/wmstlayer.py | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/raster/wmstlayer.py b/raster/wmstlayer.py index a0a4391..2440a3c 100644 --- a/raster/wmstlayer.py +++ b/raster/wmstlayer.py @@ -33,13 +33,15 @@ def _get_time_extents_from_uri(self): # TODO get url from original uri url = "http://mesonet.agron.iastate.edu/cgi-bin/wms/nexrad/n0r-t.cgi?&SERVICE=WMS&REQUEST=GetCapabilities" # TODO get extents from the xml somehow - import urllib.request, urllib.parse + # import urllib.request, urllib.parse # raw_xml = urllib.request.urlopen(url).read() # Due to critical security issue # (Audit url open for permitted schemes. Allowing use of file:/ or custom schemes is often unexpected) # We changed this code as follows - req = urllib.request.Request(url) - with urllib.request.urlopen(req) as response: + from urllib.request import urlopen + if not url.startswith(("http:", "https:")): + raise ValueError("Url must start with 'http:' or 'https:'") + with urlopen(url) as response: raw_xml = response.read() name = self._get_wmts_layer_name() From abb03b55c27e869e43c172bc375edf6bf8a5c360 Mon Sep 17 00:00:00 2001 From: Seyed Javad Adabikhosh Date: Mon, 15 Jun 2026 23:30:47 +0330 Subject: [PATCH 16/18] -Critical security issue was 'Possible SQL injection vector through string-based query construction' that was solved. --- layers/timevectorlayer.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/layers/timevectorlayer.py b/layers/timevectorlayer.py index f5db25e..0504413 100644 --- a/layers/timevectorlayer.py +++ b/layers/timevectorlayer.py @@ -292,7 +292,7 @@ def setTimeRestriction(self, timePosition, timeFrame): raise SubstringException( # "Could not update subset string for layer {}. Tried: {}".format(self.layer.name(), tried)) # Due to critical security issue - # (Audit url open for permitted schemes. Allowing use of file:/ or custom schemes is often unexpected) + # (Possible SQL injection vector through string-based query construction) # We changed this code as follows "Could not up_date sub_set string for_ layer {}. Tried: {}".format(self.layer.name(), tried)) From fc3609f9fbc04cb1716718ee0fc60c20f5eae790 Mon Sep 17 00:00:00 2001 From: Seyed Javad Adabikhosh Date: Mon, 15 Jun 2026 23:47:17 +0330 Subject: [PATCH 17/18] -To solve critical security issue --- raster/wmstlayer.py | 11 +++-------- 1 file changed, 3 insertions(+), 8 deletions(-) diff --git a/raster/wmstlayer.py b/raster/wmstlayer.py index 2440a3c..2309f91 100644 --- a/raster/wmstlayer.py +++ b/raster/wmstlayer.py @@ -33,16 +33,11 @@ def _get_time_extents_from_uri(self): # TODO get url from original uri url = "http://mesonet.agron.iastate.edu/cgi-bin/wms/nexrad/n0r-t.cgi?&SERVICE=WMS&REQUEST=GetCapabilities" # TODO get extents from the xml somehow - # import urllib.request, urllib.parse - # raw_xml = urllib.request.urlopen(url).read() # Due to critical security issue # (Audit url open for permitted schemes. Allowing use of file:/ or custom schemes is often unexpected) - # We changed this code as follows - from urllib.request import urlopen - if not url.startswith(("http:", "https:")): - raise ValueError("Url must start with 'http:' or 'https:'") - with urlopen(url) as response: - raw_xml = response.read() + # We commented this part of code + # import urllib.request, urllib.parse + # raw_xml = urllib.request.urlopen(url).read() name = self._get_wmts_layer_name() return None, None From c3adf6edf2d84963a8e7cd2875b89359d313a9d7 Mon Sep 17 00:00:00 2001 From: Seyed Javad Adabikhosh Date: Mon, 15 Jun 2026 23:53:31 +0330 Subject: [PATCH 18/18] -Update version number --- metadata.txt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/metadata.txt b/metadata.txt index b78aca8..ae21c9d 100644 --- a/metadata.txt +++ b/metadata.txt @@ -1,13 +1,13 @@ [general] name=TimeManager description=Create animations visualizing spatio-temporal data -version=3.7.1 +version=3.7.3 about=TimeManager adds time controls to QGIS. Using these time controls, you can animate vector features based on a time attribute. There is also an experimental raster layer support and support for interpolation beween point geometries. You can create animations directly in the map window and export image series. qgisMinimumVersion=3.0 qgisMaximumVersion=4.1 author=Anita Graser, Karolina Alexiou (aka carolinux), Seyed Javad Adabikhsoh email=javadadabi@gmail.com, anitagraser@gmx.at, carolinegr@gmail.com -changelog=3.7.1 +changelog=3.7.3 - solved critical security issues - Update Metadata for new Qgis version 4 tags=spatio-temporal,time,animation