apsg 1.3.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,284 @@
1
+ # -*- coding: utf-8 -*-
2
+
3
+ """
4
+ API to read data from PySDB database
5
+ """
6
+
7
+ import sqlite3
8
+ from os.path import isfile
9
+ from apsg.feature._geodata import Lineation, Foliation
10
+ from apsg.feature._container import LineationSet, FoliationSet
11
+
12
+
13
+ class SDB(object):
14
+ """
15
+ sqlite3 based read-only interface to PySDB database
16
+
17
+ Args:
18
+ sdbfile (str): filename of PySDB database
19
+
20
+ Example:
21
+ >>> db = SDB('database.sdb')
22
+
23
+ """
24
+
25
+ _SELECT = """SELECT sites.name as name, sites.x_coord as x,
26
+ sites.y_coord as y, units.name as unit, structdata.azimuth as azimuth,
27
+ structdata.inclination as inclination, structype.structure as structure,
28
+ structype.planar as planar, structdata.description as description,
29
+ GROUP_CONCAT(tags.name) AS tags
30
+ FROM structdata
31
+ INNER JOIN sites ON structdata.id_sites=sites.id
32
+ INNER JOIN structype ON structype.id = structdata.id_structype
33
+ INNER JOIN units ON units.id = sites.id_units
34
+ LEFT OUTER JOIN tagged ON structdata.id = tagged.id_structdata
35
+ LEFT OUTER JOIN tags ON tags.id = tagged.id_tags"""
36
+
37
+ _SITE_SELECT = """SELECT sites.name as name, units.name as unit,
38
+ sites.x_coord as x, sites.y_coord as y, sites.description as description
39
+ FROM sites
40
+ INNER JOIN units ON units.id = sites.id_units
41
+ ORDER BY sites.name"""
42
+
43
+ def __new__(cls, sdb_file):
44
+ assert isfile(sdb_file), "Database does not exists."
45
+ cls.conn = sqlite3.connect(sdb_file)
46
+ cls.conn.row_factory = sqlite3.Row
47
+ cls.conn.execute("pragma encoding='UTF-8'")
48
+ cls.conn.execute(SDB._SELECT + " LIMIT 1")
49
+ return super(SDB, cls).__new__(cls)
50
+
51
+ def __repr__(self):
52
+ return "PySDB database version: {}".format(self.meta("version"))
53
+
54
+ def meta(self, name, val=None, delete=False):
55
+ if delete:
56
+ try:
57
+ self.conn.execute("DELETE FROM meta WHERE name=?", (name,))
58
+ self.conn.commit()
59
+ except sqlite3.OperationalError:
60
+ self.conn.rollback()
61
+ print("Metadata '{}' not deleted.".format(name))
62
+ raise
63
+ elif val is None:
64
+ if name == "crs": # keep compatible with old sdb files
65
+ val = self.conn.execute(
66
+ "SELECT value FROM meta WHERE name='crs'"
67
+ ).fetchall()
68
+ if not val:
69
+ name = "proj4"
70
+ res = self.conn.execute(
71
+ "SELECT value FROM meta WHERE name=?", (name,)
72
+ ).fetchall()
73
+ if res:
74
+ return res[0][0]
75
+ else:
76
+ raise ValueError("SDB: Metadata '{}' does not exists".format(name))
77
+ else:
78
+ try:
79
+ exval = self.conn.execute(
80
+ "SELECT value FROM meta WHERE name=?", (name,)
81
+ ).fetchall()
82
+ if not exval:
83
+ self.conn.execute(
84
+ "INSERT INTO meta (name,value) VALUES (?,?)", (name, val)
85
+ )
86
+ else:
87
+ self.conn.execute(
88
+ "UPDATE meta SET value = ? WHERE name = ?", (val, name)
89
+ )
90
+ self.conn.commit()
91
+ except sqlite3.OperationalError:
92
+ self.conn.rollback()
93
+ print("Metadata '{}' not updated.".format(name))
94
+ raise
95
+
96
+ def info(self, report="basic"):
97
+ lines = []
98
+ if report == "basic":
99
+ lines.append("PySDB database version: {}".format(self.meta("version")))
100
+ lines.append("PySDB database CRS: {}".format(self.meta("crs")))
101
+ lines.append("PySDB database created: {}".format(self.meta("created")))
102
+ lines.append("PySDB database updated: {}".format(self.meta("updated")))
103
+ lines.append("Number of sites: {}".format(len(self.sites())))
104
+ lines.append("Number of units: {}".format(len(self.units())))
105
+ lines.append("Number of structures: {}".format(len(self.structures())))
106
+ r = self.execsql(self._make_select())
107
+ lines.append("Number of measurements: {}".format(len(r)))
108
+ elif report == "data":
109
+ for s in self.structures():
110
+ r = self.execsql(self._make_select(structs=s))
111
+ if len(r) > 0:
112
+ lines.append("Number of {} measurements: {}".format(s, len(r)))
113
+ elif report == "tags":
114
+ for s in self.structures():
115
+ r = self.execsql(self._make_select(tags=s))
116
+ if len(r) > 0:
117
+ lines.append("{} measurements tagged as {}.".format(len(r), s))
118
+ else:
119
+ lines.append("No report.")
120
+
121
+ return "\n".join(lines)
122
+
123
+ def _make_select(self, structs=None, sites=None, units=None, tags=None):
124
+ w = []
125
+ if structs:
126
+ if isinstance(structs, str):
127
+ w.append("structype.structure='%s'" % structs)
128
+ elif isinstance(structs, (list, tuple)):
129
+ u = " OR ".join(
130
+ ["structype.structure='{}'".format(struct) for struct in structs]
131
+ )
132
+ w.append("(" + u + ")")
133
+ else:
134
+ raise ValueError("Keyword structs must be list or string.")
135
+ if sites:
136
+ if isinstance(sites, str):
137
+ w.append("sites.name='{}'".format(sites))
138
+ elif isinstance(sites, (list, tuple)):
139
+ u = " OR ".join(["sites.name='{}'".format(site) for site in sites])
140
+ w.append("(" + u + ")")
141
+ else:
142
+ raise ValueError("Keyword sites must be list or string.")
143
+ if units:
144
+ if isinstance(units, str):
145
+ w.append("unit='{}'".format(units))
146
+ elif isinstance(units, (list, tuple)):
147
+ u = " OR ".join(["unit='{}'".format(unit) for unit in units])
148
+ w.append("(" + u + ")")
149
+ else:
150
+ raise ValueError("Keyword units must be list or string.")
151
+ if tags:
152
+ if isinstance(tags, str):
153
+ tagw = ["tags LIKE '%{}%'".format(tags)]
154
+ elif isinstance(tags, (list, tuple)):
155
+ u = " AND ".join(["tags LIKE '%{}%'".format(tag) for tag in tags])
156
+ tagw = ["({})".format(u)]
157
+ else:
158
+ raise ValueError("Keyword tags must be list or string.")
159
+ insel = SDB._SELECT
160
+ if w:
161
+ insel += " WHERE {} GROUP BY structdata.id".format(" AND ".join(w))
162
+ else:
163
+ insel += " GROUP BY structdata.id"
164
+ sel = "SELECT * FROM ({}) WHERE {}".format(insel, " AND ".join(tagw))
165
+ else:
166
+ sel = SDB._SELECT
167
+ if w:
168
+ sel += " WHERE {} GROUP BY structdata.id".format(" AND ".join(w))
169
+ else:
170
+ sel += " GROUP BY structdata.id"
171
+ return sel
172
+
173
+ def execsql(self, sql):
174
+ return self.conn.execute(sql).fetchall()
175
+
176
+ def structures(self, **kwargs):
177
+ """
178
+ Return list of structures in database.
179
+
180
+ For kwargs see getset method
181
+ """
182
+ if kwargs:
183
+ dtsel = self._make_select(**kwargs)
184
+ res = set([el["structure"] for el in self.execsql(dtsel)])
185
+ return sorted(list(res))
186
+ else:
187
+ dtsel = "SELECT structure FROM structype ORDER BY pos"
188
+ return [el["structure"] for el in self.execsql(dtsel)]
189
+
190
+ def sites(self, **kwargs):
191
+ """
192
+ Return list of sites in database.
193
+
194
+ For kwargs see getset method.
195
+ """
196
+ if kwargs:
197
+ dtsel = self._make_select(**kwargs)
198
+ res = set([el["name"] for el in self.execsql(dtsel)])
199
+ return sorted(list(res))
200
+ else:
201
+ dtsel = "SELECT name FROM sites ORDER BY id"
202
+ return [el["name"] for el in self.execsql(dtsel)]
203
+
204
+ def units(self, **kwargs):
205
+ """
206
+ Return list of units in database.
207
+
208
+ For kwargs see getset method.
209
+ """
210
+ if kwargs:
211
+ dtsel = self._make_select(**kwargs)
212
+ res = set([el["unit"] for el in self.execsql(dtsel)])
213
+ return sorted(list(res))
214
+ else:
215
+ dtsel = "SELECT name FROM units ORDER BY pos"
216
+ return [el["name"] for el in self.execsql(dtsel)]
217
+
218
+ def tags(self, **kwargs):
219
+ """
220
+ Return list of tags in database.
221
+
222
+ For kwargs see getset method.
223
+ """
224
+ if kwargs:
225
+ dtsel = self._make_select(**kwargs)
226
+ tags = [el["tags"] for el in self.execsql(dtsel) if el["tags"] is not None]
227
+ return sorted(list(set(",".join(tags).split(","))))
228
+ else:
229
+ dtsel = "SELECT name FROM tags ORDER BY pos"
230
+ return [el["name"] for el in self.execsql(dtsel)]
231
+
232
+ def is_planar(self, structs):
233
+ if isinstance(structs, str):
234
+ tpsel = "SELECT planar FROM structype WHERE structure='{}'".format(structs)
235
+ res = self.execsql(tpsel)
236
+ return res[0][0] == 1
237
+ elif isinstance(structs, (list, tuple)):
238
+ res = [self.is_planar(s) for s in structs]
239
+ if all(res):
240
+ return True
241
+ elif not any(res):
242
+ return False
243
+ else:
244
+ raise ValueError("All structures must be either planar or linear.")
245
+ else:
246
+ raise ValueError("Keyword structs must be list or string.")
247
+
248
+ def getset(self, structs, **kwargs):
249
+ """Method to retrieve data from SDB database to ``FeatureSet``.
250
+
251
+ Args:
252
+ structs (str): structure or list of structures to retrieve
253
+
254
+ Keyword Args:
255
+ sites (str): name or list of names of sites to retrieve from
256
+ units (str): name or list of names of units to retrieve from
257
+ tags (str): tag or list of tags to retrieve
258
+ labels (bool): if True return also list of sites. Default False
259
+
260
+ """
261
+ labels = kwargs.pop("labels", False)
262
+ dtsel = self._make_select(structs=structs, **kwargs)
263
+ sel = self.execsql(dtsel)
264
+ if sel:
265
+ if isinstance(structs, str):
266
+ name = structs
267
+ else:
268
+ name = " ".join(structs)
269
+ if self.is_planar(structs):
270
+ res = FoliationSet(
271
+ [Foliation(el["azimuth"], el["inclination"]) for el in sel],
272
+ name=name,
273
+ )
274
+ else:
275
+ res = LineationSet(
276
+ [Lineation(el["azimuth"], el["inclination"]) for el in sel],
277
+ name=name,
278
+ )
279
+ if labels:
280
+ return res, [el["name"] for el in sel]
281
+ else:
282
+ return res
283
+ else:
284
+ raise ValueError("No structures found using provided criteria.")
@@ -0,0 +1,5 @@
1
+ # -*- coding: utf-8 -*-
2
+
3
+ from apsg.decorator._decorator import ensure_first_arg_same, ensure_arguments
4
+
5
+ __all__ = ("ensure_first_arg_same", "ensure_arguments")
@@ -0,0 +1,43 @@
1
+ import numpy as np
2
+
3
+ ##################
4
+ # DECORATORS #
5
+ ##################
6
+
7
+
8
+ def ensure_arguments(*datatypes):
9
+ def decorator(method):
10
+ def wrapper(self, *args, **kwargs):
11
+ nargs = list(args)
12
+ ok = []
13
+ for ix, cls in enumerate(datatypes):
14
+ try:
15
+ nargs[ix] = cls(nargs[ix])
16
+ ok.append(True)
17
+ except Exception:
18
+ ok.append(False)
19
+ if all(ok):
20
+ return method(self, *nargs, **kwargs)
21
+ else:
22
+ raise TypeError(
23
+ f'Unsupported arguments for {method.__name__}. \
24
+ Must be {" or ".join([dt.__name__ for dt in datatypes])}'
25
+ )
26
+
27
+ return wrapper
28
+
29
+ return decorator
30
+
31
+
32
+ def ensure_first_arg_same(method):
33
+ def arg_check(self, *args):
34
+ nargs = list(args)
35
+ cls = type(self)
36
+ if np.asarray(args[0]).shape == cls.__shape__:
37
+ nargs[0] = cls(args[0])
38
+ return method(self, *nargs)
39
+ raise TypeError(
40
+ f"Unsupported argument for {method.__name__}. Expecting {cls.__name__}"
41
+ )
42
+
43
+ return arg_check
@@ -0,0 +1,79 @@
1
+ # -*- coding: utf-8 -*-
2
+ import sys
3
+ from apsg.feature._geodata import Lineation, Foliation, Pair, Fault, Cone
4
+ from apsg.feature._container import (
5
+ FeatureSet,
6
+ Vector2Set,
7
+ Vector3Set,
8
+ LineationSet,
9
+ FoliationSet,
10
+ PairSet,
11
+ FaultSet,
12
+ ConeSet,
13
+ EllipseSet,
14
+ EllipsoidSet,
15
+ OrientationTensor2Set,
16
+ OrientationTensor3Set,
17
+ G,
18
+ ClusterSet,
19
+ )
20
+ from apsg.feature._tensor3 import (
21
+ DeformationGradient3,
22
+ VelocityGradient3,
23
+ Stress3,
24
+ Ellipsoid,
25
+ OrientationTensor3,
26
+ )
27
+ from apsg.feature._tensor2 import (
28
+ DeformationGradient2,
29
+ VelocityGradient2,
30
+ Stress2,
31
+ Ellipse,
32
+ OrientationTensor2,
33
+ )
34
+ from apsg.feature._paleomag import Core
35
+
36
+ __all__ = (
37
+ "Lineation",
38
+ "Foliation",
39
+ "Pair",
40
+ "Fault",
41
+ "Cone",
42
+ "DeformationGradient3",
43
+ "VelocityGradient3",
44
+ "Stress3",
45
+ "Ellipsoid",
46
+ "OrientationTensor3",
47
+ "DeformationGradient2",
48
+ "VelocityGradient2",
49
+ "Stress2",
50
+ "Ellipse",
51
+ "OrientationTensor2",
52
+ "Vector2Set",
53
+ "FeatureSet",
54
+ "Vector3Set",
55
+ "LineationSet",
56
+ "FoliationSet",
57
+ "PairSet",
58
+ "FaultSet",
59
+ "ConeSet",
60
+ "EllipseSet",
61
+ "EllipsoidSet",
62
+ "OrientationTensor2Set",
63
+ "OrientationTensor3Set",
64
+ "G",
65
+ "ClusterSet",
66
+ "Core",
67
+ )
68
+
69
+
70
+ def feature_from_json(obj_json):
71
+ dtype_cls = getattr(sys.modules[__name__], obj_json["datatype"])
72
+ args = []
73
+ for arg in obj_json["args"]:
74
+ if isinstance(arg, dict):
75
+ args.append([feature_from_json(jd) for jd in arg["collection"]])
76
+ else:
77
+ args.append(arg)
78
+ kwargs = obj_json.get("kwargs", {})
79
+ return dtype_cls(*args, **kwargs)