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,609 @@
1
+ # -*- coding: utf-8 -*-
2
+
3
+ """
4
+ SQLAlchemy API to access PySDB database
5
+ """
6
+
7
+ import os
8
+ from datetime import datetime
9
+ import contextlib
10
+
11
+ from apsg.feature._geodata import Lineation, Foliation, Pair
12
+ from apsg.feature._container import LineationSet, FoliationSet
13
+
14
+ from sqlalchemy import create_engine, event
15
+ from sqlalchemy.orm import sessionmaker
16
+ from sqlalchemy import Column, Float, ForeignKey, Integer, String, Table, Text, text
17
+ from sqlalchemy.orm import relationship
18
+ from sqlalchemy.ext.declarative import declarative_base
19
+
20
+ Base = declarative_base()
21
+ metadata = Base.metadata
22
+
23
+
24
+ class Meta(Base):
25
+ __tablename__ = "meta"
26
+
27
+ id = Column(Integer, primary_key=True)
28
+ name = Column(String(16), nullable=False)
29
+ value = Column(Text)
30
+
31
+
32
+ class Site(Base):
33
+ __tablename__ = "sites"
34
+
35
+ id = Column(Integer, primary_key=True)
36
+ id_units = Column(ForeignKey("units.id"), nullable=False, index=True)
37
+ name = Column(String(16), nullable=False, server_default=text("''"))
38
+ x_coord = Column(Float, server_default=text("NULL"))
39
+ y_coord = Column(Float, server_default=text("NULL"))
40
+ description = Column(Text)
41
+
42
+ unit = relationship("Unit", back_populates="sites")
43
+
44
+ structdata = relationship("Structdata", back_populates="site")
45
+
46
+ def __repr__(self):
47
+ return "Site:{} ({})".format(self.name, self.unit.name)
48
+
49
+
50
+ tagged = Table(
51
+ "tagged",
52
+ metadata,
53
+ Column("id", Integer, autoincrement=True),
54
+ Column("id_tags", Integer, ForeignKey("tags.id"), primary_key=True),
55
+ Column("id_structdata", Integer, ForeignKey("structdata.id"), primary_key=True),
56
+ )
57
+
58
+
59
+ class Attached(Base):
60
+ __tablename__ = "attach"
61
+
62
+ id = Column(Integer, primary_key=True)
63
+ id_structdata_planar = Column(
64
+ ForeignKey("structdata.id"), nullable=False, index=True
65
+ )
66
+ id_structdata_linear = Column(
67
+ ForeignKey("structdata.id"), nullable=False, index=True
68
+ )
69
+
70
+ def __repr__(self):
71
+ return "{} - {}".format(self.planar, self.linear)
72
+
73
+
74
+ class Structdata(Base):
75
+ __tablename__ = "structdata"
76
+
77
+ id = Column(Integer, primary_key=True)
78
+ id_sites = Column(ForeignKey("sites.id"), nullable=False, index=True)
79
+ id_structype = Column(ForeignKey("structype.id"), nullable=False, index=True)
80
+ azimuth = Column(Float, nullable=False, server_default=text("0"))
81
+ inclination = Column(Float, nullable=False, server_default=text("0"))
82
+ description = Column(Text)
83
+
84
+ site = relationship("Site", back_populates="structdata")
85
+ structype = relationship("Structype", back_populates="structdata")
86
+
87
+ tags = relationship("Tag", secondary=tagged, back_populates="structdata")
88
+
89
+ attach_planar = relationship(
90
+ "Attached", backref="planar", primaryjoin=id == Attached.id_structdata_planar
91
+ )
92
+ attach_linear = relationship(
93
+ "Attached", backref="linear", primaryjoin=id == Attached.id_structdata_linear
94
+ )
95
+
96
+ def __repr__(self):
97
+ return "{}:{:g}/{:g}".format(
98
+ self.structype.structure, self.azimuth, self.inclination
99
+ )
100
+
101
+
102
+ class Structype(Base):
103
+ __tablename__ = "structype"
104
+
105
+ id = Column(Integer, primary_key=True)
106
+ pos = Column(Integer, nullable=False, server_default=text("0"))
107
+ structure = Column(String(16), nullable=False)
108
+ description = Column(Text)
109
+ structcode = Column(Integer, server_default=text("0"))
110
+ groupcode = Column(Integer, server_default=text("0"))
111
+ planar = Column(Integer, server_default=text("1"))
112
+
113
+ structdata = relationship("Structdata", back_populates="structype")
114
+
115
+ def __repr__(self):
116
+ return "Type:{}".format(self.structure)
117
+
118
+
119
+ class Tag(Base):
120
+ __tablename__ = "tags"
121
+
122
+ id = Column(Integer, primary_key=True)
123
+ pos = Column(Integer, nullable=False, server_default=text("0"))
124
+ name = Column(String(16), nullable=False)
125
+ description = Column(Text)
126
+
127
+ structdata = relationship("Structdata", secondary=tagged, back_populates="tags")
128
+
129
+ def __repr__(self):
130
+ return "Tag:{}".format(self.name)
131
+
132
+
133
+ class Unit(Base):
134
+ __tablename__ = "units"
135
+
136
+ id = Column(Integer, primary_key=True)
137
+ pos = Column(Integer, nullable=False, server_default=text("0"))
138
+ name = Column(String(60), nullable=False)
139
+ description = Column(Text)
140
+
141
+ sites = relationship("Site", back_populates="unit")
142
+
143
+ def __repr__(self):
144
+ return "Unit:{}".format(self.name)
145
+
146
+
147
+ def default_meta():
148
+ return dict(
149
+ version="3.1.0",
150
+ crs="EPSG:4326",
151
+ created=datetime.now().strftime("%d.%m.%Y %H:%M"),
152
+ updated=datetime.now().strftime("%d.%m.%Y %H:%M"),
153
+ accessed=datetime.now().strftime("%d.%m.%Y %H:%M"),
154
+ )
155
+
156
+
157
+ def default_initial_values():
158
+ return [
159
+ Structype(
160
+ pos=1,
161
+ structure="S",
162
+ description="Default planar feature",
163
+ structcode=35,
164
+ groupcode=13,
165
+ planar=1,
166
+ ),
167
+ Structype(
168
+ pos=2,
169
+ structure="L",
170
+ description="Default linear feature",
171
+ structcode=78,
172
+ groupcode=13,
173
+ planar=0,
174
+ ),
175
+ Unit(pos=1, name="Default", description="Default unit"),
176
+ ]
177
+
178
+
179
+ def before_commit_meta_update(session):
180
+ u = session.query(Meta).filter_by(name="updated").first()
181
+ u.value = datetime.now().strftime("%d.%m.%Y %H:%M")
182
+
183
+
184
+ def before_insert_pos_update(mapper, connection, target):
185
+ if target.pos is None:
186
+ t = str(mapper.persist_selectable)
187
+ query = "SELECT max({}.pos) FROM {}".format(t, t)
188
+ maxpos = connection.scalar(text(query))
189
+ if maxpos is None:
190
+ maxpos = 1
191
+ else:
192
+ maxpos += 1
193
+ target.pos = maxpos
194
+
195
+
196
+ class SDBSession:
197
+ """
198
+ SqlAlchemy interface to PySDB database
199
+
200
+ Args:
201
+ sdbfile (str): filename of PySDB database
202
+
203
+ Keyword Args:
204
+ create (bool): if True existing sdbfile will be deleted
205
+ and new database will be created
206
+ autocommit(bool): if True, each operation is autocommitted
207
+
208
+ Example:
209
+ >>> db = SDBSession('database.sdb', create=True)
210
+
211
+ """
212
+
213
+ def __init__(self, sdb_file, **kwargs):
214
+ if kwargs.get("create", False):
215
+ with contextlib.suppress(FileNotFoundError):
216
+ os.remove(sdb_file)
217
+ self.sdb_engine = create_engine("sqlite:///{}".format(sdb_file))
218
+ if kwargs.get("create", False):
219
+ metadata.create_all(self.sdb_engine)
220
+ sdb_Session = sessionmaker(bind=self.sdb_engine)
221
+ self.session = sdb_Session()
222
+ if kwargs.get("create", False):
223
+ meta = default_meta()
224
+ meta.update(kwargs.get("meta", {}))
225
+ self.session.add_all(default_initial_values())
226
+ self.session.add_all([Meta(name=n, value=v) for n, v in meta.items()])
227
+ self.session.commit()
228
+ self.autocommit = kwargs.get("autocommit", False)
229
+ # add listeners
230
+ event.listen(self.session, "before_commit", before_commit_meta_update)
231
+ event.listen(Unit, "before_insert", before_insert_pos_update)
232
+ event.listen(Structype, "before_insert", before_insert_pos_update)
233
+ event.listen(Tag, "before_insert", before_insert_pos_update)
234
+
235
+ def __repr__(self):
236
+ return self.info()
237
+
238
+ def info(self, data=False):
239
+ lines = []
240
+ lines.append(f"PySDB database version: {self.meta('version').value}")
241
+ lines.append(f"PySDB database CRS: {self.meta('crs').value}")
242
+ lines.append(f"PySDB database created: {self.meta('created').value}")
243
+ lines.append(f"PySDB database updated: {self.meta('created').value}")
244
+ lines.append(f"Number of sites: {self.session.query(Site).count()}")
245
+ lines.append(f"Number of units: {self.session.query(Unit).count()}")
246
+ lines.append(f"Number of structures: {self.session.query(Structype).count()}")
247
+ lines.append(
248
+ f"Number of measurements: {self.session.query(Structdata).count()}"
249
+ )
250
+ if data:
251
+ for s in self.structypes():
252
+ n = self.session.query(Structdata).filter_by(structype=s).count()
253
+ if n > 0:
254
+ lines.append(f"Number of {s.structure} measurements: {n}")
255
+ return "\n".join(lines)
256
+
257
+ def close(self):
258
+ """
259
+ Close session
260
+
261
+ """
262
+ self.session.close()
263
+
264
+ def commit(self):
265
+ """
266
+ commit session
267
+
268
+ """
269
+ self.session.commit()
270
+
271
+ def rollback(self):
272
+ """
273
+ rollback session
274
+
275
+ """
276
+ self.session.rollback()
277
+
278
+ def meta(self, name, **kwargs):
279
+ """
280
+ Insert, update or retrieve (when kwargs empty) Meta
281
+
282
+ Args:
283
+ name (str): meta name
284
+
285
+ Keyword Args:
286
+ value (str): meta value
287
+
288
+ Returns:
289
+ Meta
290
+ """
291
+ meta = self.session.query(Meta).filter_by(name=name).first()
292
+ if kwargs:
293
+ if meta is None:
294
+ meta = Meta(name=name, **kwargs)
295
+ self.session.add(meta)
296
+ else:
297
+ self.session.query(Meta).filter_by(name=name).update(kwargs)
298
+ if self.autocommit:
299
+ self.commit()
300
+ return meta
301
+
302
+ def site(self, name, **kwargs):
303
+ """
304
+ Insert, update or retrieve (when kwargs empty) Site
305
+
306
+ Args:
307
+ name (str): site name
308
+
309
+ Keyword Args:
310
+ x_coord (float): x coord or longitude
311
+ y_coord (float): y coord or latitude
312
+ description (str): site description
313
+ unit (Unit): unit instance (mus be provided)
314
+
315
+ Returns:
316
+ Site
317
+ """
318
+ site = self.session.query(Site).filter_by(name=name).first()
319
+ if kwargs:
320
+ if site is None:
321
+ site = Site(name=name, **kwargs)
322
+ self.session.add(site)
323
+ else:
324
+ self.session.query(Site).filter_by(name=name).update(kwargs)
325
+ if self.autocommit:
326
+ self.commit()
327
+ return site
328
+
329
+ def unit(self, name, **kwargs):
330
+ """
331
+ Insert, update or retrieve (when kwargs empty) Unit
332
+
333
+ Args:
334
+ name (str): unit name
335
+
336
+ Keyword Args:
337
+ description (str): unit description
338
+
339
+ Returns:
340
+ Unit
341
+ """
342
+ unit = self.session.query(Unit).filter_by(name=name).first()
343
+ if kwargs:
344
+ if unit is None:
345
+ unit = Unit(name=name, **kwargs)
346
+ self.session.add(unit)
347
+ else:
348
+ self.session.query(Unit).filter_by(name=name).update(kwargs)
349
+ if self.autocommit:
350
+ self.commit()
351
+ return unit
352
+
353
+ def tag(self, name, **kwargs):
354
+ """
355
+ Insert, update or retrieve (when kwargs empty) Tag
356
+
357
+ Args:
358
+ name (str): tag name
359
+
360
+ Keyword Args:
361
+ description (str): tag description
362
+
363
+ Returns:
364
+ Tag
365
+ """
366
+ tag = self.session.query(Tag).filter_by(name=name).first()
367
+ if kwargs:
368
+ if tag is None:
369
+ tag = Tag(name=name, **kwargs)
370
+ self.session.add(tag)
371
+ else:
372
+ self.session.query(Tag).filter_by(name=name).update(kwargs)
373
+ if self.autocommit:
374
+ self.commit()
375
+ return tag
376
+
377
+ def structype(self, structure, **kwargs):
378
+ """
379
+ Insert, update or retrieve (when kwargs empty) Structype
380
+
381
+ Args:
382
+ structure (str): label for structure
383
+
384
+ Keyword Args:
385
+ description (str): structype description
386
+ planar (int): 1 for planar 0 for linear
387
+ structcode (int): structcode (optional)
388
+ groupcode (int): groupcode (optional)
389
+
390
+ Returns:
391
+ Structype
392
+ """
393
+ structype = self.session.query(Structype).filter_by(structure=structure).first()
394
+ if kwargs:
395
+ if structype is None:
396
+ structype = Structype(structure=structure, **kwargs)
397
+ self.session.add(structype)
398
+ else:
399
+ self.session.query(Structype).filter_by(structure=structure).update(
400
+ kwargs
401
+ )
402
+ if self.autocommit:
403
+ self.commit()
404
+ return structype
405
+
406
+ def add_structdata(self, site, structype, azimuth, inclination, **kwargs):
407
+ """
408
+ Add structdata to site
409
+
410
+ Args:
411
+ site (Site): site instance
412
+ structype (Structype): structype instance
413
+ azimuth (float): dip direction or plunge direction
414
+ inclination (float): dip or plunge
415
+
416
+ Keyword Args:
417
+ description (str): structdata description
418
+
419
+ Returns:
420
+ Structdata
421
+
422
+ """
423
+ data = Structdata(
424
+ site=site,
425
+ structype=structype,
426
+ azimuth=azimuth,
427
+ inclination=inclination,
428
+ **kwargs,
429
+ )
430
+ self.session.add(data)
431
+ if self.autocommit:
432
+ self.commit()
433
+ return data
434
+
435
+ def add_fol(self, site, structype, fol, **kwargs):
436
+ """
437
+ Add Foliation to site
438
+
439
+ Args:
440
+ site (Site): site instance
441
+ structype (Structype): structype instance
442
+ fol (Foliation): foliation instance
443
+
444
+ Keyword Args:
445
+ description (str): structdata description
446
+
447
+ Returns:
448
+ Structdata
449
+
450
+ """
451
+ assert isinstance(
452
+ fol, Foliation
453
+ ), "fol argument must be instance of Foliation class"
454
+ assert structype.planar, "structype must be planar"
455
+ azimuth, inclination = fol.geo
456
+ return self.add_structdata(site, structype, azimuth, inclination, **kwargs)
457
+
458
+ def add_lin(self, site, structype, lin, **kwargs):
459
+ """
460
+ Add Lineation to site
461
+
462
+ Args:
463
+ site (Site): site instance
464
+ structype (Structype): structype instance
465
+ lin (Lineation): lineation instance
466
+
467
+ Keyword Args:
468
+ description (str): structdata description
469
+
470
+ Returns:
471
+ Structdata
472
+
473
+ """
474
+ assert isinstance(
475
+ lin, Lineation
476
+ ), "lin argument must be instance of Lineation class"
477
+ assert not structype.planar, "structype must be linear"
478
+ azimuth, inclination = lin.geo
479
+ return self.add_structdata(site, structype, azimuth, inclination, **kwargs)
480
+
481
+ def attach(self, fol, lin):
482
+ """
483
+ Add Lineation to site
484
+
485
+ Args:
486
+ fol (Foliation): foliation instance
487
+ lin (Lineation): lineation instance
488
+
489
+ Returns:
490
+ Attached
491
+
492
+ """
493
+ pair = Attached(planar=fol, linear=lin)
494
+ self.session.add(pair)
495
+ if self.autocommit:
496
+ self.commit()
497
+ return pair
498
+
499
+ def add_pair(self, pair, foltype, lintype, **kwargs):
500
+ """
501
+ Add attached foliation and lineation to database
502
+
503
+ Args:
504
+ pair (Pair): pair instance
505
+ foltype (Structype): structype instance
506
+ lintype (Structype): structype instance
507
+
508
+ Returns:
509
+ Attached
510
+
511
+ """
512
+ assert isinstance(pair, Pair), "pair argument must be instance of Pair class"
513
+ kwargs["structype"] = foltype
514
+ fol = self.add_fol(pair.fol, **kwargs)
515
+ kwargs["structype"] = lintype
516
+ lin = self.add_lin(pair.lin, **kwargs)
517
+ return self.attach(fol, lin)
518
+
519
+ def sites(self, **kwargs):
520
+ """
521
+ Retrieve Site or list of Sites based on criteria in kwargs
522
+
523
+ Keyword arguments are passed to sqlalchemy filter_by method
524
+ """
525
+ if kwargs:
526
+ sites = self.session.query(Site).filter_by(**kwargs).all()
527
+ else:
528
+ sites = self.session.query(Site).all()
529
+ if len(sites) == 1:
530
+ return sites[0]
531
+ else:
532
+ return sites
533
+
534
+ def units(self, **kwargs):
535
+ """
536
+ Retrieve Unit or list of Units based on criteria in kwargs
537
+
538
+ Keyword arguments are passed to sqlalchemy filter_by method
539
+ """
540
+ if kwargs:
541
+ units = self.session.query(Unit).filter_by(**kwargs).all()
542
+ else:
543
+ units = self.session.query(Unit).all()
544
+ if len(units) == 1:
545
+ return units[0]
546
+ else:
547
+ return units
548
+
549
+ def structypes(self, **kwargs):
550
+ """
551
+ Retrieve Structype or list of Structypes based on criteria in kwargs
552
+
553
+ Keyword arguments are passed to sqlalchemy filter_by method
554
+ """
555
+ if kwargs:
556
+ structypes = self.session.query(Structype).filter_by(**kwargs).all()
557
+ else:
558
+ structypes = self.session.query(Structype).all()
559
+ if len(structypes) == 1:
560
+ return structypes[0]
561
+ else:
562
+ return structypes
563
+
564
+ def tags(self, **kwargs):
565
+ """
566
+ Retrieve Tag or list of Tags based on criteria in kwargs
567
+
568
+ Keyword arguments are passed to sqlalchemy filter_by method
569
+ """
570
+ if kwargs:
571
+ tags = self.session.query(Tag).filter_by(**kwargs).all()
572
+ else:
573
+ tags = self.session.query(Tag).all()
574
+ if len(tags) == 1:
575
+ return tags[0]
576
+ else:
577
+ return tags
578
+
579
+ def getset(self, structype, **kwargs):
580
+ """Method to retrieve data from SDB database to ``FeatureSet``.
581
+
582
+ Args:
583
+ structype (str, Structype): structure or list of structures to retrieve
584
+
585
+ Keyword arguments are passed to sqlalchemy filter_by method
586
+
587
+ """
588
+ if isinstance(structype, str):
589
+ structypes = (
590
+ self.session.query(Structype).filter_by(structure=structype).all()
591
+ )
592
+ assert len(structypes) == 1, f"There is no structure {structype} in db"
593
+ structype = structypes[0]
594
+ data = (
595
+ self.session.query(Structdata)
596
+ .filter_by(structype=structype, **kwargs)
597
+ .all()
598
+ )
599
+ if structype.planar:
600
+ res = FoliationSet(
601
+ [Foliation(v.azimuth, v.inclination) for v in data],
602
+ name=structype.structure,
603
+ )
604
+ else:
605
+ res = LineationSet(
606
+ [Lineation(v.azimuth, v.inclination) for v in data],
607
+ name=structype.structure,
608
+ )
609
+ return res