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