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

Timemanager version: 3.7 #339

Open
wants to merge 11 commits into
base: master
Choose a base branch
from
16 changes: 9 additions & 7 deletions README.md
Original file line number Diff line number Diff line change
@@ -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/
<!---Latest news will be published on Anita's blog: http://anitagraser.com/tag/time-manager/-->

## What is the goal

Expand Down
6 changes: 3 additions & 3 deletions index.html
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="chrome=1">
<title>Timemanager by anitagraser</title>
<title>Timemanager by anitagraser, Karolina Alexiou and Seyed Javad Adabikhosh</title>
<link rel="stylesheet" href="stylesheets/styles.css">
<link rel="stylesheet" href="stylesheets/pygment_trac.css">
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.7.1/jquery.min.js"></script>
Expand Down Expand Up @@ -41,7 +41,7 @@ <h1>QGIS Time Manager Plugin</h1>
<p>&nbsp;</p>
<hr>
<span class="credits left">Project maintained by <a
href="https://github.com/anitagraser">anitagraser</a></span>
href="https://github.com/javadadabi">Seyed Javad Adabikhosh</a></span>
<span class="credits right">Hosted on GitHub Pages &mdash; Theme by <a
href="https://twitter.com/#!/michigangraham">mattgraham</a></span>
</div>
Expand All @@ -63,7 +63,7 @@ <h2>What Time Manager does</h2>
get accustomed to Time Manager.</p>

<p>More information on functionality and limitations can be found on <a
href="https://github.com/anitagraser/TimeManager/#readme">Github</a>.</p>
href="https://github.com/javadadabi/TimeManager/tree/timemanager/#readme">Github</a>.</p>

<h2>Where to download Time Manager</h2>

Expand Down
23 changes: 10 additions & 13 deletions metadata.txt
Original file line number Diff line number Diff line change
@@ -1,21 +1,18 @@
[general]
name=TimeManager
description=Create animations visualizing spatio-temporal data
version=3.5
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.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.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/anitagraser/TimeManager/issues
repository=https://github.com/anitagraser/TimeManager
homepage=https://github.com/javadadabi/TimeManager
tracker=https://github.com/javadadabi/TimeManager/tree/timemanager/issues
repository=https://github.com/javadadabi/TimeManager/tree/timemanager
68 changes: 51 additions & 17 deletions raster/cdflayer.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,31 +4,31 @@
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_calendar(self):
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.calendar
return time
except:
return DEFAULT_CALENDAR
return None

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
Expand All @@ -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))
Expand Down Expand Up @@ -72,13 +75,13 @@ def extract_netcdf_time(cls, bandName, calendar):
cdftime = utime(units, calendar)
timestamps = cdftime.num2date([epoch])
return timestamps[0]

@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):
Expand All @@ -88,14 +91,29 @@ 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_using_netcdf4_library(cls, bandnum, dataset_time):
time = dataset_time
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:
Expand All @@ -111,10 +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()
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(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_time_from_bandname(p.generateBandName(i), self.calendar))

startTime = self.band_to_dt[0]
endTime = self.band_to_dt[-1]
Expand All @@ -132,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):
Expand Down
6 changes: 4 additions & 2 deletions timemanager_obj.py
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand All @@ -33,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):
Expand Down
7 changes: 4 additions & 3 deletions timemanagercontrol.py
Original file line number Diff line number Diff line change
Expand Up @@ -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"""
Expand Down
16 changes: 16 additions & 0 deletions utils/time_util.py
Original file line number Diff line number Diff line change
Expand Up @@ -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():
Expand Down