forked from anitagraser/TimeManager
-
Notifications
You must be signed in to change notification settings - Fork 0
/
timevectorlayer.py
235 lines (197 loc) · 10.2 KB
/
timevectorlayer.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
# -*- coding: utf-8 -*-
"""
Created on Thu Mar 22 17:28:19 2012
@author: Anita
"""
import traceback
from PyQt4 import QtCore
from PyQt4.QtGui import QMessageBox
from timelayer import *
from time_util import timeval_to_datetime, \
datetime_to_str, QDateTime_to_datetime, str_to_datetime, DateTypes
import time_util
from query_builder import QueryIdioms
import query_builder
from datetime import timedelta
from conf import SAVE_DELIMITER
import layer_settings as ls
from logging import info, warn
POSTGRES_TYPE='PostgreSQL database with PostGIS extension'
DELIMITED_TEXT_TYPE='Delimited text file'
STORAGE_TYPES_WITH_SQL=[POSTGRES_TYPE, DELIMITED_TEXT_TYPE]
class SubstringException(Exception):
pass
def isNull(val):
"""Determine null values from providers"""
return val is None or val=="NULL" or str(val)=="NULL" # yes it's possible the string "NULL" is returned (!)
class TimeVectorLayer(TimeLayer):
def getOriginalSubsetString(self):
return self.originalSubsetString
def geometriesCountForExport(self):
return self.geometriesCount
def __init__(self,settings, iface=None):
TimeLayer.__init__(self,settings.layer,settings.isEnabled)
self.layer = settings.layer
self.iface = iface
self.minValue,self.maxValue = None,None
self.fromTimeAttribute = settings.startTimeAttribute
self.toTimeAttribute = settings.endTimeAttribute
self.originalSubsetString = settings.subsetStr
self.currSubsetString = self.originalSubsetString
self.setSubsetString(self.originalSubsetString)
self.geometriesCount = settings.geometriesCount
self.type = DateTypes.determine_type(self.getRawMinValue())
type2 = DateTypes.determine_type(self.getRawMaxValue())
self.timeFormat = self.determine_format(self.getRawMinValue(), settings.timeFormat)
tf2 = self.determine_format(self.getRawMaxValue(), settings.timeFormat)
if self.type!=type2 or self.timeFormat!=tf2:
raise InvalidTimeLayerError("Invalid time layer: To and From attributes must have "
"exact same format")
self.offset = int(settings.offset)
assert(self.timeFormat != time_util.PENDING)
try:
info("Layer extents"+str(self.getTimeExtents()))
except Exception, e:
raise InvalidTimeLayerError(traceback.format_exc(e))
def hasSubsetStr(self):
return True
def getDateType(self):
"""return the type of dates this layer has stored"""
return self.type
def getTimeAttributes(self):
"""return the tuple of timeAttributes (fromTimeAttribute,toTimeAttribute)"""
return (self.fromTimeAttribute,self.toTimeAttribute)
def getTimeFormat(self):
"""returns the layer's time format"""
return self.timeFormat
def getOffset(self):
"""returns the layer's offset, integer in seconds"""
return self.offset
def debug(self, msg):
QMessageBox.information(self.iface.mainWindow(),'Info', msg)
def getProvider(self):
return self.layer # the layer itself can be the provider,
# which means that it can now about joined fields
def getRawMinValue(self):
"""returns the raw minimum value. May not be the expected minimum value semantically if we
have dates that are saves as strings because of lexicographic comparisons"""
fromTimeAttributeIndex = self.getProvider().fieldNameIndex(self.fromTimeAttribute)
minValue = self.getProvider().minimumValue(fromTimeAttributeIndex)
if isNull(minValue): # if we are unlucky and have some null data we need to sort through the values
minValue = min(filter(lambda x: not isNull(x),self.getProvider().uniqueValues(fromTimeAttributeIndex)))
#info("Min value:"+str(minValue)+str(isNull(minValue)))
return minValue
def getRawMaxValue(self):
"""returns the raw maximum value. May not be the expected minimum value semantically if we
have dates that are saves as strings because of lexicographic comparisons"""
toTimeAttributeIndex = self.getProvider().fieldNameIndex(self.toTimeAttribute)
maxValue = self.getProvider().maximumValue(toTimeAttributeIndex)
if isNull(maxValue):
maxValue = max(filter(lambda x: not isNull(x),self.getProvider().uniqueValues(toTimeAttributeIndex)))
return maxValue
def getMinMaxValues(self):
"""Returns str"""
if self.minValue is None or self.maxValue is None: # if not already computed
provider = self.getProvider()
fmt = self.getTimeFormat()
fromTimeAttributeIndex = provider.fieldNameIndex(self.fromTimeAttribute)
toTimeAttributeIndex = provider.fieldNameIndex(self.toTimeAttribute)
if self.getDateType() == DateTypes.IntegerTimestamps:
self.minValue = self.getRawMinValue()
self.maxValue = self.getRawMaxValue()
else:
# need to find min max by looking at all the unique values
# QGIS doesn't get sorting right
unique_vals = provider.uniqueValues(fromTimeAttributeIndex)
# those can be either strings or qdate(time) values
def vals_to_dt(vals, fmt):
res = []
for val in vals:
try:
dt = timeval_to_datetime(val,fmt)
res.append(dt)
#info("{} converted to {}".format(val, dt))
except Exception,e:
warn("Unparseable value {} in layer {} ignored. Cause {}".format(val, self.layer.name(),e))
pass
return res
unique_vals = vals_to_dt(unique_vals, fmt)
if len(unique_vals)==0:
raise Exception("Could not parse any dates while trying to get time extents")
minValue= datetime_to_str(min(unique_vals),fmt)
if fromTimeAttributeIndex == toTimeAttributeIndex:
maxValue = datetime_to_str(max(unique_vals),fmt)
else:
unique_vals = provider.uniqueValues(toTimeAttributeIndex)
unique_vals = vals_to_dt(unique_vals,fmt)
maxValue= datetime_to_str(max(unique_vals),fmt)
if type(minValue) in [QtCore.QDate, QtCore.QDateTime]:
minValue = datetime_to_str(QDateTime_to_datetime(minValue), self.getTimeFormat())
maxValue = datetime_to_str(QDateTime_to_datetime(maxValue), self.getTimeFormat())
self.minValue = minValue
self.maxValue = maxValue
return self.minValue, self.maxValue
def getTimeExtents(self):
"""Get layer's temporal extent in datetime format
using the fields and the format defined in the layer"""
start_str, end_str = self.getMinMaxValues()
startTime = str_to_datetime(start_str, self.getTimeFormat())
endTime = str_to_datetime(end_str, self.getTimeFormat())
# apply offset
startTime += timedelta(seconds=self.offset)
endTime += timedelta(seconds=self.offset)
return startTime, endTime
def getStartTime(self, timePosition, timeFrame):
return timePosition + timedelta(seconds=self.offset)
def getEndTime(self, timePosition, timeFrame):
return timePosition + timeFrame + timedelta(seconds=self.offset)
def setTimeRestriction(self, timePosition, timeFrame):
"""Constructs the query, including the original subset"""
if not self.isEnabled():
self.deleteTimeRestriction()
return
startTime = self.getStartTime(timePosition, timeFrame)
endTime = self.getEndTime(timePosition, timeFrame)
idioms_to_try = [QueryIdioms.SQL, QueryIdioms.OGR]
if self.getDateType() in DateTypes.QDateTypes:
idioms_to_try = [QueryIdioms.OGR]
#if self.layer.dataProvider().storageType() in STORAGE_TYPES_WITH_SQL:
# idioms_to_try = [QueryIdioms.SQL]
tried = []
for idiom in idioms_to_try:
subsetString = query_builder.build_query(startTime, endTime, self.fromTimeAttribute,
self.toTimeAttribute, date_type =
self.getDateType(), date_format=self.getTimeFormat(), query_idiom=idiom)
try:
self.setSubsetString(subsetString)
except SubstringException:
tried.append(subsetString)
# try the other one
# not sure if trying several idioms could make the screen flash
continue
info("Subsetstring:"+subsetString)
return
raise SubstringException("Could not update subset string for layer {}. Tried: {}".format(self.layer.name(), tried))
def setSubsetString(self,subsetString):
if self.originalSubsetString !='':
subsetString= "{} AND {}".format(self.originalSubsetString, subsetString)
success = self.layer.setSubsetString(subsetString)
if not success:
raise SubstringException("Could not set substring to".format(subsetString))
else:
self.currSubsetString = subsetString
def deleteTimeRestriction(self):
"""Restore original subset"""
self.setSubsetString(self.originalSubsetString)
def hasTimeRestriction(self):
"""returns true if current layer.subsetString is not equal to originalSubsetString"""
return self.layer.subsetString() != self.originalSubsetString
def getSaveString(self):
"""get string to save in project file"""
settings = ls.getSettingsFromLayer(self)
res = SAVE_DELIMITER.join([settings.layerId, settings.subsetStr,
settings.startTimeAttribute, settings.endTimeAttribute,
str(settings.isEnabled),settings.timeFormat,
str(settings.offset), settings.idAttribute,
str(settings.interpolationEnabled), settings.interpolationMode, str(settings.geometriesCount)])
return res