geovisio 2.11.0__py3-none-any.whl → 2.12.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.
Files changed (209) hide show
  1. geovisio/__init__.py +30 -2
  2. geovisio/admin_cli/db.py +1 -1
  3. geovisio/db_migrations.py +2 -2
  4. geovisio/migrations/20221201_01_wpCGc-initial-schema.rollback.sql +9 -0
  5. geovisio/migrations/20221201_01_wpCGc-initial-schema.sql +71 -0
  6. geovisio/migrations/20221201_02_ZG8AR-camera-information.rollback.sql +5 -0
  7. geovisio/migrations/20221201_02_ZG8AR-camera-information.sql +10 -0
  8. geovisio/migrations/20221222_01_fsB6f-add-account.rollback.sql +7 -0
  9. geovisio/migrations/20221222_01_fsB6f-add-account.sql +33 -0
  10. geovisio/migrations/20230113_01_0co97-rm-metadata-duplicates.rollback.sql +5 -0
  11. geovisio/migrations/20230113_01_0co97-rm-metadata-duplicates.sql +5 -0
  12. geovisio/migrations/20230116_01_9PkjZ-add-oauth-provider.rollback.sql +12 -0
  13. geovisio/migrations/20230116_01_9PkjZ-add-oauth-provider.sql +10 -0
  14. geovisio/migrations/20230117_01_K71Pd-pictures-ts-index.rollback.sql +4 -0
  15. geovisio/migrations/20230117_01_K71Pd-pictures-ts-index.sql +4 -0
  16. geovisio/migrations/20230130_01_VRIv2-sequences-account.rollback.sql +5 -0
  17. geovisio/migrations/20230130_01_VRIv2-sequences-account.sql +25 -0
  18. geovisio/migrations/20230324_01_ba9WA-status.rollback.sql +35 -0
  19. geovisio/migrations/20230324_01_ba9WA-status.sql +11 -0
  20. geovisio/migrations/20230324_02_efgI6-picture-process.rollback.sql +20 -0
  21. geovisio/migrations/20230324_02_efgI6-picture-process.sql +44 -0
  22. geovisio/migrations/20230407_01_wofh1-computed-headings.rollback.sql +6 -0
  23. geovisio/migrations/20230407_01_wofh1-computed-headings.sql +6 -0
  24. geovisio/migrations/20230417_01_ZgLMY-add-exif-metadata-column-for-pictures.rollback.sql +4 -0
  25. geovisio/migrations/20230417_01_ZgLMY-add-exif-metadata-column-for-pictures.sql +4 -0
  26. geovisio/migrations/20230420_01_elaN3-remove-picture-and-sequence-file-paths.rollback.sql +5 -0
  27. geovisio/migrations/20230420_01_elaN3-remove-picture-and-sequence-file-paths.sql +5 -0
  28. geovisio/migrations/20230425_01_gYP77-pictures-edits-triggers.rollback.sql +5 -0
  29. geovisio/migrations/20230425_01_gYP77-pictures-edits-triggers.sql +64 -0
  30. geovisio/migrations/20230427_01_k5e5w-timestamps.rollback.sql +10 -0
  31. geovisio/migrations/20230427_01_k5e5w-timestamps.sql +19 -0
  32. geovisio/migrations/20230511_01_TdpKo-tokens.rollback.sql +7 -0
  33. geovisio/migrations/20230511_01_TdpKo-tokens.sql +34 -0
  34. geovisio/migrations/20230615_01_u7aRf-pic-delete-cascade.rollback.sql +10 -0
  35. geovisio/migrations/20230615_01_u7aRf-pic-delete-cascade.sql +12 -0
  36. geovisio/migrations/20230623_01_y1SiQ-pic-deletion-task.rollback.sql +39 -0
  37. geovisio/migrations/20230623_01_y1SiQ-pic-deletion-task.sql +36 -0
  38. geovisio/migrations/20230629_01_ZdB3i-compute-heading-0.sql +27 -0
  39. geovisio/migrations/20230711_01_JGSPB-inserted-at-index.rollback.sql +4 -0
  40. geovisio/migrations/20230711_01_JGSPB-inserted-at-index.sql +4 -0
  41. geovisio/migrations/20230720_01_EyQ0e-sequences-summary.rollback.sql +7 -0
  42. geovisio/migrations/20230720_01_EyQ0e-sequences-summary.sql +26 -0
  43. geovisio/migrations/20230803_01_aXusm-fix-sequence-computed.rollback.sql +4 -0
  44. geovisio/migrations/20230803_01_aXusm-fix-sequence-computed.sql +25 -0
  45. geovisio/migrations/20231018_01_4G3YE-pictures-exiv2.rollback.sql +165 -0
  46. geovisio/migrations/20231018_01_4G3YE-pictures-exiv2.sql +206 -0
  47. geovisio/migrations/20231103_01_ZVKEm-update-seq-on-pic-change.rollback.sql +7 -0
  48. geovisio/migrations/20231103_01_ZVKEm-update-seq-on-pic-change.sql +28 -0
  49. geovisio/migrations/20231110_01_3p070-jobs-error.rollback.sql +7 -0
  50. geovisio/migrations/20231110_01_3p070-jobs-error.sql +93 -0
  51. geovisio/migrations/20231121_01_v6oBF-more-specific-triggers.rollback.sql +36 -0
  52. geovisio/migrations/20231121_01_v6oBF-more-specific-triggers.sql +40 -0
  53. geovisio/migrations/20231121_02_1uZXT-deleted-tag.rollback.sql +19 -0
  54. geovisio/migrations/20231121_02_1uZXT-deleted-tag.sql +13 -0
  55. geovisio/migrations/20240115_01_FatLR-token-delete-cascade.rollback.sql +6 -0
  56. geovisio/migrations/20240115_01_FatLR-token-delete-cascade.sql +7 -0
  57. geovisio/migrations/20240220_01_9wZs0-sequence-current-sort.rollback.sql +6 -0
  58. geovisio/migrations/20240220_01_9wZs0-sequence-current-sort.sql +15 -0
  59. geovisio/migrations/20240223_01_LsMHB-remove-binary-fields.sql +80 -0
  60. geovisio/migrations/20240226_01_8iXl1-track-changes.rollback.sql +13 -0
  61. geovisio/migrations/20240226_01_8iXl1-track-changes.sql +123 -0
  62. geovisio/migrations/20240229_01_SgfQY-sequence-geom-multi-linestring.rollback.sql +67 -0
  63. geovisio/migrations/20240229_01_SgfQY-sequence-geom-multi-linestring.sql +101 -0
  64. geovisio/migrations/20240308_01_aF0Jb-migrate-sequence-geom-multi-linestring.sql +52 -0
  65. geovisio/migrations/20240409_01_jnhra-pictures-grid.rollback.sql +3 -0
  66. geovisio/migrations/20240409_01_jnhra-pictures-grid.sql +21 -0
  67. geovisio/migrations/20240416_01_FpyGs-pictures-stats-on-sequences.rollback.sql +7 -0
  68. geovisio/migrations/20240416_01_FpyGs-pictures-stats-on-sequences.sql +9 -0
  69. geovisio/migrations/20240416_02_A5KzC-fill-pictures-stats-on-sequences.rollback.sql +5 -0
  70. geovisio/migrations/20240416_02_A5KzC-fill-pictures-stats-on-sequences.sql +84 -0
  71. geovisio/migrations/20240507_01_eBfqZ-refresh-table.rollback.sql +5 -0
  72. geovisio/migrations/20240507_01_eBfqZ-refresh-table.sql +9 -0
  73. geovisio/migrations/20240507_02_dzVET-picture-grid-public.rollback.sql +21 -0
  74. geovisio/migrations/20240507_02_dzVET-picture-grid-public.sql +26 -0
  75. geovisio/migrations/20240514_01_IT7DD-picture-delete-cascade.rollback.sql +41 -0
  76. geovisio/migrations/20240514_01_IT7DD-picture-delete-cascade.sql +42 -0
  77. geovisio/migrations/20240611_01_jftHn-content-md5.rollback.sql +5 -0
  78. geovisio/migrations/20240611_01_jftHn-content-md5.sql +7 -0
  79. geovisio/migrations/20240612_01_yNcuE-upload-set.rollback.sql +6 -0
  80. geovisio/migrations/20240612_01_yNcuE-upload-set.sql +46 -0
  81. geovisio/migrations/20240617_01_tKtlx-md5-concurrent-index.rollback.sql +4 -0
  82. geovisio/migrations/20240617_01_tKtlx-md5-concurrent-index.sql +8 -0
  83. geovisio/migrations/20240625_01_XMZ24-fix-sequence-stat-on-pic-insertion.rollback.sql +30 -0
  84. geovisio/migrations/20240625_01_XMZ24-fix-sequence-stat-on-pic-insertion.sql +31 -0
  85. geovisio/migrations/20240708_01_Xn7IH-job-queue.rollback.sql +31 -0
  86. geovisio/migrations/20240708_01_Xn7IH-job-queue.sql +104 -0
  87. geovisio/migrations/20240715_01_Hca9V-upload-set-metadata.rollback.sql +4 -0
  88. geovisio/migrations/20240715_01_Hca9V-upload-set-metadata.sql +5 -0
  89. geovisio/migrations/20240723_01_ePGFe-upload-set-files.rollback.sql +7 -0
  90. geovisio/migrations/20240723_01_ePGFe-upload-set-files.sql +29 -0
  91. geovisio/migrations/20240729_01_HALjj-upload-set-sort.rollback.sql +18 -0
  92. geovisio/migrations/20240729_01_HALjj-upload-set-sort.sql +18 -0
  93. geovisio/migrations/20240730_01_2BaCy-improve-deletion-triggers.rollback.sql +34 -0
  94. geovisio/migrations/20240730_01_2BaCy-improve-deletion-triggers.sql +53 -0
  95. geovisio/migrations/20240730_02_aRymN-rejection-status.rollback.sql +10 -0
  96. geovisio/migrations/20240730_02_aRymN-rejection-status.sql +16 -0
  97. geovisio/migrations/20240801_01_DOqmf-reports.rollback.sql +11 -0
  98. geovisio/migrations/20240801_01_DOqmf-reports.sql +100 -0
  99. geovisio/migrations/20240801_01_uKqPo-remove-files-delete-cascade.rollback.sql +13 -0
  100. geovisio/migrations/20240801_01_uKqPo-remove-files-delete-cascade.sql +13 -0
  101. geovisio/migrations/20240813_01_T1XkO-sequences-geom-splits.rollback.sql +42 -0
  102. geovisio/migrations/20240813_01_T1XkO-sequences-geom-splits.sql +56 -0
  103. geovisio/migrations/20240820_01_aB2ZK-exclusion-zones.rollback.sql +6 -0
  104. geovisio/migrations/20240820_01_aB2ZK-exclusion-zones.sql +49 -0
  105. geovisio/migrations/20240902_01_MDqSj-user-agent.rollback.sql +5 -0
  106. geovisio/migrations/20240902_01_MDqSj-user-agent.sql +10 -0
  107. geovisio/migrations/20240904_01_gFjlV-files-rejection-msg.rollback.sql +4 -0
  108. geovisio/migrations/20240904_01_gFjlV-files-rejection-msg.sql +4 -0
  109. geovisio/migrations/20240905_01_C8F6U-conflicts.rollback.sql +13 -0
  110. geovisio/migrations/20240905_01_C8F6U-conflicts.sql +6 -0
  111. geovisio/migrations/20240905_01_E5Ki0-upload-set-delete.rollback.sql +5 -0
  112. geovisio/migrations/20240905_01_E5Ki0-upload-set-delete.sql +30 -0
  113. geovisio/migrations/20240909_01_Muc22-unique-grid-index.rollback.sql +4 -0
  114. geovisio/migrations/20240909_01_Muc22-unique-grid-index.sql +5 -0
  115. geovisio/migrations/20240912_01_dAALm-account-index.rollback.sql +4 -0
  116. geovisio/migrations/20240912_01_dAALm-account-index.sql +4 -0
  117. geovisio/migrations/20241004_01_d1zfe-pictures-grid-360.rollback.sql +22 -0
  118. geovisio/migrations/20241004_01_d1zfe-pictures-grid-360.sql +24 -0
  119. geovisio/migrations/20241011_01_e1j5C-pic-quality.rollback.sql +21 -0
  120. geovisio/migrations/20241011_01_e1j5C-pic-quality.sql +249 -0
  121. geovisio/migrations/20241017_01_GuOjF-pic-quality-update.rollback.sql +4 -0
  122. geovisio/migrations/20241017_01_GuOjF-pic-quality-update.sql +61 -0
  123. geovisio/migrations/20241017_01_RiFlm-pictures-to-delete.rollback.sql +43 -0
  124. geovisio/migrations/20241017_01_RiFlm-pictures-to-delete.sql +75 -0
  125. geovisio/migrations/20241104_01_yhRVu-rejection-details.rollback.sql +4 -0
  126. geovisio/migrations/20241104_01_yhRVu-rejection-details.sql +4 -0
  127. geovisio/migrations/20241128_01_ugthx-job-queue-args.rollback.sql +25 -0
  128. geovisio/migrations/20241128_01_ugthx-job-queue-args.sql +31 -0
  129. geovisio/migrations/20241224_01_xuN6n-delete-upload-set-on-last-picture-trg-statement.rollback.sql +27 -0
  130. geovisio/migrations/20241224_01_xuN6n-delete-upload-set-on-last-picture-trg-statement.sql +28 -0
  131. geovisio/migrations/20250102_01_EElhA-rm-cameras.rollback.sql +126 -0
  132. geovisio/migrations/20250102_01_EElhA-rm-cameras.sql +30 -0
  133. geovisio/migrations/20250107_01_EQN9v-tags-tables.rollback.sql +10 -0
  134. geovisio/migrations/20250107_01_EQN9v-tags-tables.sql +58 -0
  135. geovisio/migrations/20250109_01_4OOP4-pages.rollback.sql +4 -0
  136. geovisio/migrations/20250109_01_4OOP4-pages.sql +10 -0
  137. geovisio/migrations/20250114_01_ABaaL-collaborative-metadata-editing.rollback.sql +7 -0
  138. geovisio/migrations/20250114_01_ABaaL-collaborative-metadata-editing.sql +16 -0
  139. geovisio/migrations/20250123_01_Ececu-tos-acceptance.rollback.sql +5 -0
  140. geovisio/migrations/20250123_01_Ececu-tos-acceptance.sql +5 -0
  141. geovisio/migrations/20250206_01_PjrEL-annotation-semantics.rollback.sql +14 -0
  142. geovisio/migrations/20250206_01_PjrEL-annotation-semantics.sql +15 -0
  143. geovisio/migrations/20250306_01_58oju-semantics-views.rollback.sql +5 -0
  144. geovisio/migrations/20250306_01_58oju-semantics-views.sql +52 -0
  145. geovisio/migrations/20250318_01_pANl1-semantics-functions.rollback.sql +5 -0
  146. geovisio/migrations/20250318_01_pANl1-semantics-functions.sql +41 -0
  147. geovisio/migrations/20250331_01_kRKjo-store-detections-id.rollback.sql +7 -0
  148. geovisio/migrations/20250331_01_kRKjo-store-detections-id.sql +25 -0
  149. geovisio/migrations/20250424_01_RBGXC-semantics-indexes.rollback.sql +6 -0
  150. geovisio/migrations/20250424_01_RBGXC-semantics-indexes.sql +6 -0
  151. geovisio/migrations/20250502_01_ZNmkU-job-task-read-metadata.rollback.sql +24 -0
  152. geovisio/migrations/20250502_01_ZNmkU-job-task-read-metadata.sql +7 -0
  153. geovisio/migrations/20250509_01_kMatW-deactivate-upload-set-split-dedup.rollback.sql +12 -0
  154. geovisio/migrations/20250509_01_kMatW-deactivate-upload-set-split-dedup.sql +12 -0
  155. geovisio/migrations/20250509_01_s3hYk-semantic-delete-cascade.rollback.sql +11 -0
  156. geovisio/migrations/20250509_01_s3hYk-semantic-delete-cascade.sql +11 -0
  157. geovisio/migrations/20250513_01_8WkZC-upload-sets-default-config.rollback.sql +7 -0
  158. geovisio/migrations/20250513_01_8WkZC-upload-sets-default-config.sql +15 -0
  159. geovisio/migrations/20250523_01_b11eW-picture-update-index.rollback.sql +85 -0
  160. geovisio/migrations/20250523_01_b11eW-picture-update-index.sql +166 -0
  161. geovisio/migrations/20250523_02_5ZXXh-update-picture-updated-at.rollback.sql +4 -0
  162. geovisio/migrations/20250523_02_5ZXXh-update-picture-updated-at.sql +7 -0
  163. geovisio/migrations/20250624_01_SETp6-job-warnings.rollback.sql +4 -0
  164. geovisio/migrations/20250624_01_SETp6-job-warnings.sql +4 -0
  165. geovisio/migrations/20250701_01_kr371-upload-set-semantics.rollback.sql +6 -0
  166. geovisio/migrations/20250701_01_kr371-upload-set-semantics.sql +15 -0
  167. geovisio/migrations/20250703_01_p2WVV-sequence-upload-set-link.rollback.sql +4 -0
  168. geovisio/migrations/20250703_01_p2WVV-sequence-upload-set-link.sql +4 -0
  169. geovisio/migrations/20250703_02_q0s3D-sequence-upload-set-fill.rollback.sql +4 -0
  170. geovisio/migrations/20250703_02_q0s3D-sequence-upload-set-fill.sql +26 -0
  171. geovisio/migrations/20250716_01_f8tcJ-check-empty-annotation-late.rollback.sql +24 -0
  172. geovisio/migrations/20250716_01_f8tcJ-check-empty-annotation-late.sql +10 -0
  173. geovisio/migrations/20250728_01_2zgur-upload-set-relative-heading.rollback.sql +4 -0
  174. geovisio/migrations/20250728_01_2zgur-upload-set-relative-heading.sql +4 -0
  175. geovisio/migrations/20250825_01_nCgkN-file-deletion-after-delete-no-dup.rollback.sql +20 -0
  176. geovisio/migrations/20250825_01_nCgkN-file-deletion-after-delete-no-dup.sql +26 -0
  177. geovisio/migrations/20250902_01_k5UTq-visibility-status.rollback.sql +11 -0
  178. geovisio/migrations/20250902_01_k5UTq-visibility-status.sql +19 -0
  179. geovisio/migrations/20250904_01_3uVKX-visibility-functions.rollback.sql +20 -0
  180. geovisio/migrations/20250904_01_3uVKX-visibility-functions.sql +43 -0
  181. geovisio/migrations/20251110_01_bWEXo-report-visibility.rollback.sql +30 -0
  182. geovisio/migrations/20251110_01_bWEXo-report-visibility.sql +30 -0
  183. geovisio/migrations/20251124_01_0xdqi-page-updates.rollback.sql +6 -0
  184. geovisio/migrations/20251124_01_0xdqi-page-updates.sql +8 -0
  185. geovisio/migrations/20251127_01_S6Ci8-pictures-grid-visibility.rollback.sql +24 -0
  186. geovisio/migrations/20251127_01_S6Ci8-pictures-grid-visibility.sql +36 -0
  187. geovisio/migrations/20251202_01_Q3g59-pictures-grid-logged-only.rollback.sql +32 -0
  188. geovisio/migrations/20251202_01_Q3g59-pictures-grid-logged-only.sql +34 -0
  189. geovisio/templates/main.html +7 -58
  190. geovisio/templates/viewer.html +8 -15
  191. geovisio/translations/cy/LC_MESSAGES/messages.mo +0 -0
  192. geovisio/translations/cy/LC_MESSAGES/messages.po +839 -0
  193. geovisio/translations/en/LC_MESSAGES/messages.mo +0 -0
  194. geovisio/translations/en/LC_MESSAGES/messages.po +155 -149
  195. geovisio/translations/messages.pot +131 -150
  196. geovisio/utils/cql2.py +22 -17
  197. geovisio/utils/link.py +13 -3
  198. geovisio/utils/upload_set.py +42 -2
  199. geovisio/web/collections.py +80 -40
  200. geovisio/web/docs.py +1 -1
  201. geovisio/web/items.py +54 -4
  202. geovisio/web/upload_set.py +2 -3
  203. geovisio/web/users.py +1 -1
  204. {geovisio-2.11.0.dist-info → geovisio-2.12.0.dist-info}/METADATA +6 -26
  205. geovisio-2.12.0.dist-info/RECORD +305 -0
  206. geovisio-2.12.0.dist-info/entry_points.txt +3 -0
  207. geovisio-2.11.0.dist-info/RECORD +0 -117
  208. {geovisio-2.11.0.dist-info → geovisio-2.12.0.dist-info}/WHEEL +0 -0
  209. {geovisio-2.11.0.dist-info → geovisio-2.12.0.dist-info}/licenses/LICENSE +0 -0
@@ -0,0 +1,249 @@
1
+ -- pic_quality
2
+ -- depends: 20240912_01_dAALm-account-index
3
+
4
+ -- NOTE : this migration is split in two files
5
+ -- this one, and next migration pic_quality_update
6
+ -- this migration is transactional, the next one is not
7
+
8
+
9
+ -- Add common cameras focal length (for FOV + HPixelDensity)
10
+ -- NOTE : cameras / sensor_data is not used anymore in versions > 2.7.1
11
+ -- For updating cameras metadata, please see GeoPicture Tag Reader repository
12
+ INSERT INTO cameras(model, sensor_width) VALUES
13
+ ('samsung SM-A336B', 6.4),
14
+ ('Viofo A119 Mini 2', 5.2),
15
+ ('GoPro HERO9 Black', 6.17),
16
+ ('GoPro HERO5 Black', 6.17),
17
+ ('HUAWEI EML-L29', 6.29),
18
+ ('Panasonic DC-LX100M2', 17.3),
19
+ ('SONY FDR-X1000V', 6.19),
20
+ ('GoPro Max', 6.17),
21
+ ('samsung SM-G950F',7.05),
22
+ ('GoPro HERO7 Black',6.17),
23
+ ('Xiaomi M2101K6G',8.4),
24
+ ('HUAWEI VOG-L29',7.3),
25
+ ('samsung SM-A546B', 12),
26
+ ('GoPro HERO10 Black', 6.17),
27
+ ('Xiaomi 2107113SG',7.4),
28
+ ('Xiaomi Redmi Note 9 Pro',7.2),
29
+ ('GoPro HERO11 Black', 6.74),
30
+ ('OnePlus A5000', 5.22),
31
+ ('samsung SM-A500FU', 4.69),
32
+ ('XIAOYI YDXJ 2', 10.2),
33
+ ('Apple iPhone 6s', 4.8),
34
+ ('samsung SM-G901F', 5.95),
35
+ ('OnePlus A3003', 6.4),
36
+ ('GoPro HERO8 Black', 6.17),
37
+ ('XIAOYI YDXJ 1', 10.2);
38
+
39
+
40
+ -- Complete FOV for imported pictures
41
+ CREATE OR REPLACE FUNCTION missing_fov(metadata JSONB) RETURNS JSONB AS $$
42
+ DECLARE
43
+ sensor_width FLOAT;
44
+ BEGIN
45
+ IF metadata->>'field_of_view' IS NOT NULL OR metadata->>'focal_length' IS NULL OR (metadata->>'focal_length')::float = 0 THEN
46
+ RETURN metadata;
47
+ END IF;
48
+
49
+ -- Find appropriate sensor width
50
+ IF metadata->>'make' = 'samsung' AND metadata->>'model' = 'SM-A336B' THEN
51
+ sensor_width := 6.4;
52
+ ELSIF metadata->>'make' = 'Viofo' AND metadata->>'model' = 'A119 Mini 2' THEN
53
+ sensor_width := 5.2;
54
+ ELSIF metadata->>'make' = 'GoPro' AND metadata->>'model' = 'HERO9 Black' THEN
55
+ sensor_width := 6.17;
56
+ ELSIF metadata->>'make' = 'GoPro' AND metadata->>'model' = 'HERO5 Black' THEN
57
+ sensor_width := 6.17;
58
+ ELSIF metadata->>'make' = 'HUAWEI' AND metadata->>'model' = 'EML-L29' THEN
59
+ sensor_width := 6.29;
60
+ ELSIF metadata->>'make' = 'Panasonic' AND metadata->>'model' = 'DC-LX100M2' THEN
61
+ sensor_width := 17.3;
62
+ ELSIF metadata->>'make' = 'SONY' AND metadata->>'model' = 'FDR-X1000V' THEN
63
+ sensor_width := 6.19;
64
+ ELSIF metadata->>'make' = 'GoPro' AND metadata->>'model' = 'Max' THEN
65
+ sensor_width := 6.17;
66
+ ELSIF metadata->>'make' = 'samsung' AND metadata->>'model' = 'SM-G950F' THEN
67
+ sensor_width := 7.05;
68
+ ELSIF metadata->>'make' = 'GoPro' AND metadata->>'model' = 'HERO7 Black' THEN
69
+ sensor_width := 6.17;
70
+ ELSIF metadata->>'make' = 'Xiaomi' AND metadata->>'model' = 'M2101K6G' THEN
71
+ sensor_width := 8.4;
72
+ ELSIF metadata->>'make' = 'HUAWEI' AND metadata->>'model' = 'VOG-L29' THEN
73
+ sensor_width := 7.3;
74
+ ELSIF metadata->>'make' = 'samsung' AND metadata->>'model' = 'SM-A546B' THEN
75
+ sensor_width := 12;
76
+ ELSIF metadata->>'make' = 'GoPro' AND metadata->>'model' = 'HERO10 Black' THEN
77
+ sensor_width := 6.17;
78
+ ELSIF metadata->>'make' = 'Xiaomi' AND metadata->>'model' = '2107113SG' THEN
79
+ sensor_width := 7.4;
80
+ ELSIF metadata->>'make' = 'Xiaomi' AND metadata->>'model' = 'Redmi Note 9 Pro' THEN
81
+ sensor_width := 7.2;
82
+ ELSIF metadata->>'make' = 'GoPro' AND metadata->>'model' = 'HERO11 Black' THEN
83
+ sensor_width := 6.74;
84
+ ELSIF metadata->>'make' = 'OnePlus' AND metadata->>'model' = 'A5000' THEN
85
+ sensor_width := 5.22;
86
+ ELSIF metadata->>'make' = 'samsung' AND metadata->>'model' = 'SM-A500FU' THEN
87
+ sensor_width := 4.69;
88
+ ELSIF metadata->>'make' = 'XIAOYI' AND metadata->>'model' = 'YDXJ 2' THEN
89
+ sensor_width := 10.2;
90
+ ELSIF metadata->>'make' = 'Apple' AND metadata->>'model' = 'iPhone 6s' THEN
91
+ sensor_width := 4.8;
92
+ ELSIF metadata->>'make' = 'samsung' AND metadata->>'model' = 'SM-G901F' THEN
93
+ sensor_width := 5.95;
94
+ ELSIF metadata->>'make' = 'OnePlus' AND metadata->>'model' = 'A3003' THEN
95
+ sensor_width := 6.4;
96
+ ELSIF metadata->>'make' = 'GoPro' AND metadata->>'model' = 'HERO8 Black' THEN
97
+ sensor_width := 6.17;
98
+ ELSIF metadata->>'make' = 'XIAOYI' AND metadata->>'model' = 'YDXJ 1' THEN
99
+ sensor_width := 10.2;
100
+ -- If not found, just send back original metadata
101
+ ELSE
102
+ RETURN metadata;
103
+ END IF;
104
+
105
+ -- Update metadata
106
+ RETURN jsonb_set(
107
+ metadata,
108
+ '{field_of_view}'::text[],
109
+ ((ROUND(DEGREES(2 * ATAN(sensor_width / (2 * (metadata->>'focal_length')::float)))))::varchar)::jsonb
110
+ );
111
+ END;
112
+ $$ LANGUAGE plpgsql IMMUTABLE PARALLEL SAFE;
113
+
114
+
115
+ -- Read float values from EXIF
116
+ CREATE OR REPLACE FUNCTION get_float(val VARCHAR) RETURNS FLOAT AS $$
117
+ DECLARE
118
+ list VARCHAR[];
119
+ BEGIN
120
+ IF val ~ '^\d+\/\d+$' THEN
121
+ list := regexp_split_to_array(val, '/');
122
+ RETURN list[1]::float / list[2]::float;
123
+ ELSIF val ~ '^\d+(\.\d+)?$' THEN
124
+ RETURN val::float;
125
+ ELSE
126
+ RETURN NULL;
127
+ END IF;
128
+ END;
129
+ $$ LANGUAGE plpgsql IMMUTABLE PARALLEL SAFE;
130
+
131
+
132
+ -- GPS accuracy function
133
+ CREATE OR REPLACE FUNCTION gps_accuracy(metadata JSONB, exif JSONB) RETURNS FLOAT AS $$
134
+ DECLARE
135
+ gps_dop FLOAT;
136
+ gps_diff INT;
137
+ gps_hpos_err FLOAT;
138
+ BEGIN
139
+ -- Parse GPS DOP, either float or fraction
140
+ gps_dop := get_float(COALESCE(exif->>'Exif.GPSInfo.GPSDOP', exif->>'Xmp.exif.GPSDOP', ''));
141
+ gps_diff := (COALESCE(exif->>'Exif.GPSInfo.GPSDifferential', exif->>'Xmp.exif.GPSDifferential'))::INT;
142
+ gps_hpos_err := get_float(COALESCE(exif->>'Exif.GPSInfo.GPSHPositioningError', exif->>'Xmp.exif.GPSHPositioningError'));
143
+
144
+ -- Direct horizontal positioning error in meters -> return as is
145
+ IF gps_hpos_err IS NOT NULL AND gps_hpos_err > 0 THEN
146
+ RETURN gps_hpos_err;
147
+
148
+ -- GPS DOP available
149
+ ELSIF gps_dop IS NOT NULL AND gps_dop > 0 THEN
150
+ -- With a DGPS -> consider GPS nominal error as 1 meter
151
+ IF gps_diff = 1 THEN
152
+ RETURN gps_dop;
153
+
154
+ -- Without DGPS -> consider GPS nominal error as 3 meters in average
155
+ ELSE
156
+ RETURN 3 * gps_dop;
157
+ END IF;
158
+
159
+ -- DGPS -> return 2 meters precision
160
+ ELSIF gps_diff = 1 THEN
161
+ RETURN 2;
162
+
163
+ -- Approximate guesses based on model
164
+ ELSIF metadata->>'make' IS NOT NULL OR metadata->>'model' IS NOT NULL THEN
165
+ -- Good non-diff GPS devices (best case is 3m, so setting 4 for tolerance)
166
+ IF lower(metadata->>'make') IN (
167
+ 'gopro', 'insta360', 'garmin', 'viofo', 'xiaoyi', 'blackvue', 'tectectec',
168
+ 'arashi vision'
169
+ )
170
+ OR metadata->>'model' IN ('LG-R105', 'FDR-X1000V')
171
+ OR metadata->>'make' ILIKE '%xiaoyi%' THEN
172
+ RETURN 4;
173
+
174
+ -- Diff GPS devices
175
+ ELSIF lower(metadata->>'make') IN ('stfmani', 'trimble', 'imajing')
176
+ OR metadata->>'model' IN ('LB5') OR metadata->>'model' ILIKE '%ladybug%' THEN
177
+ RETURN 2;
178
+
179
+ -- Smartphones or not-so-good non-diff GPS devices
180
+ ELSIF lower(metadata->>'make') IN (
181
+ 'samsung', 'xiaomi', 'huawei', 'ricoh', 'lenovo', 'motorola', 'oneplus',
182
+ 'apple', 'google', 'sony', 'wiko', 'asus', 'cubot', 'lge', 'fairphone',
183
+ 'realme', 'symphony', 'crosscall', 'htc', 'homtom', 'hmd global', 'oppo',
184
+ 'ulefone'
185
+ ) THEN
186
+ RETURN 5;
187
+
188
+ -- Fallback for unknown make/model
189
+ ELSE
190
+ RETURN NULL;
191
+ END IF;
192
+ -- Fallback : no value
193
+ ELSE
194
+ RETURN NULL;
195
+ END IF;
196
+ END;
197
+ $$ LANGUAGE plpgsql IMMUTABLE PARALLEL SAFE;
198
+
199
+
200
+ -- Number of pixels on horizon per FOV degree
201
+ CREATE OR REPLACE FUNCTION h_pixel_density(metadata JSONB) RETURNS INT AS $$
202
+ BEGIN
203
+ RETURN round((metadata->>'width')::float / (metadata->>'field_of_view')::float);
204
+ END;
205
+ $$ LANGUAGE plpgsql IMMUTABLE PARALLEL SAFE;
206
+
207
+
208
+ -- Clean-up EXIF, as we're here to rewrite all pictures...
209
+ -- Removes all proprietary keys ending in .0x1234 (hex key)
210
+ CREATE OR REPLACE FUNCTION clean_exif(exif JSONB) RETURNS JSONB AS $$
211
+ SELECT jsonb_object_agg(key, value)
212
+ FROM (
213
+ SELECT key, value
214
+ FROM jsonb_each(exif)
215
+ WHERE key !~ '\.0x[0-9a-fA-F]+$'
216
+ ) a;
217
+ $$ LANGUAGE sql IMMUTABLE PARALLEL SAFE;
218
+
219
+
220
+ -- Add columns for pictures
221
+ ALTER TABLE pictures
222
+ ADD COLUMN gps_accuracy_m FLOAT,
223
+ ADD COLUMN h_pixel_density INT;
224
+
225
+
226
+ -- Auto-insert pixel density/GPS accuracy for new pictures
227
+ CREATE OR REPLACE FUNCTION pictures_hpixdens_gpsacc() RETURNS TRIGGER AS $$
228
+ BEGIN
229
+ NEW.h_pixel_density := h_pixel_density(NEW.metadata);
230
+ NEW.gps_accuracy_m := gps_accuracy(NEW.metadata, NEW.exif);
231
+ RETURN NEW;
232
+ END;
233
+ $$ LANGUAGE plpgsql;
234
+
235
+ CREATE TRIGGER trg_pictures_hpixdens_gpsacc
236
+ BEFORE INSERT ON pictures
237
+ FOR EACH ROW
238
+ EXECUTE FUNCTION pictures_hpixdens_gpsacc();
239
+
240
+ CREATE TRIGGER trg_pictures_hpixdens_gpsacc_upd
241
+ BEFORE UPDATE OF metadata, exif ON pictures
242
+ FOR EACH ROW
243
+ EXECUTE FUNCTION pictures_hpixdens_gpsacc();
244
+
245
+
246
+ -- Add info on sequences too
247
+ ALTER TABLE sequences
248
+ ADD COLUMN computed_h_pixel_density INT,
249
+ ADD COLUMN computed_gps_accuracy FLOAT;
@@ -0,0 +1,4 @@
1
+ -- pic_quality_update
2
+ -- depends: 20241011_01_e1j5C-pic-quality
3
+
4
+ -- Does nothing
@@ -0,0 +1,61 @@
1
+ -- pic_quality_update
2
+ -- depends: 20241011_01_e1j5C-pic-quality
3
+ -- transactional: false
4
+
5
+ -- Update pictures in batch
6
+ CREATE OR REPLACE PROCEDURE update_pictures_gps_accuracy() AS $$
7
+ DECLARE
8
+ last_inserted_at TIMESTAMPTZ;
9
+ BEGIN
10
+ SELECT min(inserted_at) - INTERVAL '1 minute' FROM pictures INTO last_inserted_at;
11
+
12
+ WHILE last_inserted_at IS NOT NULL LOOP
13
+
14
+ WITH
15
+ -- get a batch of 100 000 pictures to update
16
+ pic_to_update AS (
17
+ SELECT id, inserted_at from pictures where inserted_at > last_inserted_at ORDER BY inserted_at ASC LIMIT 100000
18
+ )
19
+ , updated_pic AS (
20
+ UPDATE pictures
21
+ SET gps_accuracy_m = gps_accuracy(metadata, exif),
22
+ h_pixel_density = h_pixel_density(missing_fov(metadata)),
23
+ exif = clean_exif(exif),
24
+ metadata = missing_fov(metadata)
25
+ WHERE id in (SELECT id FROM pic_to_update)
26
+ )
27
+ SELECT MAX(inserted_at) FROM pic_to_update INTO last_inserted_at;
28
+
29
+ RAISE NOTICE 'max insertion date is now %', last_inserted_at;
30
+
31
+ -- commit transaction (as a procedure is in an implicit transaction, it will start a new transaction after this)
32
+ COMMIT;
33
+
34
+ END LOOP;
35
+ RAISE NOTICE 'update finished';
36
+ END
37
+ $$ LANGUAGE plpgsql;
38
+
39
+ -- Perform pictures update
40
+ SET session_replication_role = replica;
41
+ CALL update_pictures_gps_accuracy();
42
+
43
+ -- Update sequences as well
44
+ UPDATE sequences s
45
+ SET
46
+ computed_h_pixel_density = CASE WHEN array_length(reshpd, 1) = 1 THEN reshpd[1] ELSE NULL END,
47
+ computed_gps_accuracy = gpsacc
48
+ FROM (
49
+ SELECT
50
+ sp.seq_id,
51
+ ARRAY_AGG(DISTINCT p.h_pixel_density) AS reshpd,
52
+ PERCENTILE_CONT(0.9) WITHIN GROUP(ORDER BY p.gps_accuracy_m) AS gpsacc
53
+ FROM sequences_pictures sp
54
+ JOIN pictures p ON sp.pic_id = p.id
55
+ GROUP BY sp.seq_id
56
+ ) p
57
+ WHERE s.id = p.seq_id;
58
+
59
+ -- Put back triggers && cleanup
60
+ SET session_replication_role = DEFAULT;
61
+ DROP PROCEDURE update_pictures_gps_accuracy;
@@ -0,0 +1,43 @@
1
+ -- pictures_to_delete
2
+ -- depends: 20240912_01_dAALm-account-index
3
+
4
+ ALTER TABLE job_queue DROP COLUMN IF EXISTS picture_to_delete_id;
5
+ -- ALTER TABLE job_queue DROP CONSTRAINT one_external_id;
6
+ ALTER TABLE job_queue ADD CONSTRAINT one_external_id CHECK (num_nonnulls(picture_id, upload_set_id, sequence_id) = 1);
7
+
8
+ ALTER TABLE job_history DROP COLUMN IF EXISTS picture_to_delete_id;
9
+ -- ALTER TABLE job_history DROP CONSTRAINT one_external_id;
10
+ ALTER TABLE job_history ADD CONSTRAINT one_external_id CHECK (num_nonnulls(picture_id, upload_set_id, sequence_id) = 1);
11
+
12
+ ALTER TABLE pictures
13
+ DROP CONSTRAINT upload_set_fk_id ,
14
+ ADD CONSTRAINT upload_set_fk_id FOREIGN KEY (upload_set_id) REFERENCES upload_sets(id);
15
+
16
+ ALTER TABLE upload_sets
17
+ ADD COLUMN IF NOT EXISTS deleted BOOLEAN DEFAULT FALSE;
18
+
19
+ DROP TRIGGER ask_for_all_file_deletion_after_delete_trg ON pictures;
20
+
21
+ DROP FUNCTION IF EXISTS delete_upload_set_on_last_picture CASCADE;
22
+ CREATE FUNCTION delete_upload_set_on_last_picture() RETURNS trigger AS $$
23
+ BEGIN
24
+ IF OLD.upload_set_id IS NOT NULL AND NOT EXISTS (
25
+ SELECT 1
26
+ FROM pictures
27
+ WHERE upload_set_id = OLD.upload_set_id AND id != OLD.id
28
+ AND status != 'waiting-for-delete'
29
+ LIMIT 1
30
+ ) THEN
31
+ -- if it's the last picture of an upload set, we delete the upload set
32
+ UPDATE upload_sets SET deleted = true WHERE id = OLD.upload_set_id AND not deleted;
33
+ INSERT INTO job_queue AS j (upload_set_id, task) VALUES (OLD.upload_set_id, 'delete') ON CONFLICT (upload_set_id) DO UPDATE SET task = 'delete' WHERE j.task != 'delete';
34
+ END IF;
35
+ RETURN NULL;
36
+
37
+ END $$ LANGUAGE plpgsql;
38
+
39
+ CREATE TRIGGER delete_upload_set_on_last_picture_trg
40
+ AFTER DELETE ON pictures
41
+ REFERENCING OLD TABLE AS deleted_pictures
42
+ FOR EACH ROW -- a picture is always deleted by the workers, one by one, so it's useless to run this trigger for each statement
43
+ EXECUTE FUNCTION delete_upload_set_on_last_picture();
@@ -0,0 +1,75 @@
1
+ -- pictures_to_delete
2
+ -- depends: 20240912_01_dAALm-account-index
3
+
4
+ -- Add another column to the job_queue tabel to store the pictures to delete without using a foreign key,
5
+ -- so that we can keep them even after their deletion
6
+ ALTER TABLE job_queue DROP CONSTRAINT one_external_id;
7
+
8
+ ALTER TABLE job_queue ADD COLUMN IF NOT EXISTS picture_to_delete_id UUID;
9
+
10
+ ALTER TABLE job_queue ADD CONSTRAINT one_external_id CHECK (num_nonnulls(picture_id, upload_set_id, sequence_id, picture_to_delete_id) = 1);
11
+
12
+ ALTER TABLE job_history ADD COLUMN IF NOT EXISTS picture_to_delete_id UUID;
13
+ ALTER TABLE job_history DROP CONSTRAINT one_external_id;
14
+ ALTER TABLE job_history ADD CONSTRAINT one_external_id CHECK (num_nonnulls(picture_id, upload_set_id, sequence_id, picture_to_delete_id) = 1);
15
+
16
+ -- when an upload set is deleted, all it's pictures are added to the deletion queue, and deleted from the database (since there is a `ON DELETE CASCADE`)
17
+
18
+ ALTER TABLE pictures
19
+ DROP CONSTRAINT upload_set_fk_id ,
20
+ ADD CONSTRAINT upload_set_fk_id FOREIGN KEY (upload_set_id) REFERENCES upload_sets(id) ON DELETE CASCADE;
21
+
22
+ -- also update how the last picture trigger the deletion of its upload set
23
+ DROP FUNCTION IF EXISTS delete_upload_set_on_last_picture CASCADE;
24
+ CREATE FUNCTION delete_upload_set_on_last_picture() RETURNS trigger AS $$
25
+ BEGIN
26
+ IF OLD.upload_set_id IS NOT NULL AND NOT EXISTS (
27
+ SELECT 1
28
+ FROM pictures
29
+ WHERE upload_set_id = OLD.upload_set_id AND id != OLD.id
30
+ AND status != 'waiting-for-delete' -- Note: this status is deprecated, but we need to consider it anyway since it can remain some pictures with this status in the database for the migration
31
+ LIMIT 1
32
+ ) THEN
33
+ -- if it's the last picture of an upload set, we delete the upload set
34
+ DELETE FROM upload_sets WHERE id = OLD.upload_set_id;
35
+ END IF;
36
+ RETURN NULL;
37
+
38
+ END $$ LANGUAGE plpgsql;
39
+
40
+ CREATE TRIGGER delete_upload_set_on_last_picture_trg
41
+ AFTER DELETE ON pictures
42
+ REFERENCING OLD TABLE AS deleted_pictures
43
+ FOR EACH ROW -- a picture is always deleted by the workers, one by one, so it's useless to run this trigger for each statement
44
+ EXECUTE FUNCTION delete_upload_set_on_last_picture();
45
+
46
+ -- Add to the new queue all pictures marked and waiting for delete and all pictures from deleted upload sets
47
+ INSERT INTO job_queue(picture_to_delete_id, task)
48
+ SELECT p.id, 'delete'
49
+ FROM pictures p
50
+ LEFT JOIN upload_sets us ON us.id = p.upload_set_id
51
+ WHERE p.status = 'waiting-for-delete' OR us.deleted;
52
+
53
+ -- cleanup all pictures/uploadset, as we don't need them anymore since they are added to the new deletion queue
54
+ DELETE FROM pictures WHERE status = 'waiting-for-delete';
55
+ DELETE FROM upload_sets WHERE deleted;
56
+
57
+ -- the deleted column of upload_sets is now useless, we can delete them right away
58
+ ALTER TABLE upload_sets DROP COLUMN deleted;
59
+
60
+ DROP FUNCTION IF EXISTS ask_for_all_file_deletion_after_delete;
61
+ CREATE FUNCTION ask_for_all_file_deletion_after_delete() RETURNS trigger AS $$
62
+ BEGIN
63
+ INSERT INTO job_queue(picture_to_delete_id, task)
64
+ SELECT p.id, 'delete' FROM deleted_pictures p;
65
+ RETURN NULL;
66
+
67
+ END $$ LANGUAGE plpgsql;
68
+
69
+ CREATE TRIGGER ask_for_all_file_deletion_after_delete_trg
70
+ AFTER DELETE ON pictures
71
+ REFERENCING OLD TABLE AS deleted_pictures
72
+ FOR EACH STATEMENT
73
+ EXECUTE FUNCTION ask_for_all_file_deletion_after_delete();
74
+
75
+
@@ -0,0 +1,4 @@
1
+ -- rejection_details
2
+ -- depends: 20241004_01_d1zfe-pictures-grid-360 20241017_01_RiFlm-pictures-to-delete
3
+
4
+ ALTER TABLE files DROP COLUMN rejection_details;
@@ -0,0 +1,4 @@
1
+ -- rejection_details
2
+ -- depends: 20241004_01_d1zfe-pictures-grid-360 20241017_01_RiFlm-pictures-to-delete
3
+
4
+ ALTER TABLE files ADD COLUMN rejection_details JSONB;
@@ -0,0 +1,25 @@
1
+ -- job_queue_args
2
+ -- depends: 20241017_01_GuOjF-pic-quality-update 20241104_01_yhRVu-rejection-details
3
+
4
+ ALTER TABLE job_queue DROP COLUMN args;
5
+ ALTER TABLE job_history DROP COLUMN args;
6
+
7
+ -- put trigger as old value
8
+ DROP TRIGGER trigger_process_picture_insertion ON pictures;
9
+
10
+ CREATE OR REPLACE FUNCTION picture_insertion() RETURNS TRIGGER AS
11
+ $BODY$
12
+ BEGIN
13
+ INSERT INTO
14
+ pictures_to_process(picture_id)
15
+ VALUES
16
+ (new.id);
17
+ RETURN new;
18
+ END;
19
+ $BODY$
20
+ language plpgsql;
21
+
22
+ CREATE TRIGGER trigger_process_picture_insertion
23
+ AFTER INSERT ON pictures
24
+ FOR EACH ROW
25
+ EXECUTE PROCEDURE picture_insertion();
@@ -0,0 +1,31 @@
1
+ -- job_queue_args
2
+ -- depends: 20241017_01_GuOjF-pic-quality-update 20241104_01_yhRVu-rejection-details
3
+
4
+ ALTER TABLE job_queue ADD COLUMN IF NOT EXISTS args JSONB;
5
+ ALTER TABLE job_history ADD COLUMN IF NOT EXISTS args JSONB;
6
+
7
+ -- Update the trigger that insert each new picture into pictures_to_process
8
+ -- Now we check if the picture has been blurred before upload, to skip the blurring process
9
+ DROP TRIGGER trigger_process_picture_insertion ON pictures;
10
+ CREATE OR REPLACE FUNCTION picture_insertion() RETURNS TRIGGER AS
11
+ $BODY$
12
+ DECLARE
13
+ args JSONB;
14
+ BEGIN
15
+ IF new.metadata->>'blurredByAuthor' THEN
16
+ args := jsonb_build_object('skip_blurring', true);
17
+ END IF;
18
+
19
+ INSERT INTO
20
+ job_queue(picture_id, task, args)
21
+ VALUES
22
+ (new.id, 'prepare', args);
23
+ RETURN new;
24
+ END;
25
+ $BODY$
26
+ language plpgsql;
27
+
28
+ CREATE TRIGGER trigger_process_picture_insertion
29
+ AFTER INSERT ON pictures
30
+ FOR EACH ROW
31
+ EXECUTE PROCEDURE picture_insertion();
@@ -0,0 +1,27 @@
1
+ -- delete_upload_set_on_last_picture_trg_statement
2
+ -- depends: 20241128_01_ugthx-job-queue-args
3
+
4
+ --put back old trigger
5
+
6
+ DROP FUNCTION IF EXISTS delete_upload_set_on_last_picture CASCADE;
7
+ CREATE FUNCTION delete_upload_set_on_last_picture() RETURNS trigger AS $$
8
+ BEGIN
9
+ IF OLD.upload_set_id IS NOT NULL AND NOT EXISTS (
10
+ SELECT 1
11
+ FROM pictures
12
+ WHERE upload_set_id = OLD.upload_set_id AND id != OLD.id
13
+ AND status != 'waiting-for-delete'
14
+ LIMIT 1
15
+ ) THEN
16
+ -- if it's the last picture of an upload set, we delete the upload set
17
+ DELETE FROM upload_sets WHERE id = OLD.upload_set_id;
18
+ END IF;
19
+ RETURN NULL;
20
+
21
+ END $$ LANGUAGE plpgsql;
22
+
23
+ CREATE TRIGGER delete_upload_set_on_last_picture_trg
24
+ AFTER DELETE ON pictures
25
+ REFERENCING OLD TABLE AS deleted_pictures
26
+ FOR EACH ROW
27
+ EXECUTE FUNCTION delete_upload_set_on_last_picture();
@@ -0,0 +1,28 @@
1
+ -- delete_upload_set_on_last_picture_trg_statement
2
+ -- depends: 20241128_01_ugthx-job-queue-args
3
+
4
+ -- Change the trigger to run for each statement, so that we run it once when deleting an uploadset
5
+
6
+ DROP FUNCTION IF EXISTS delete_upload_set_on_last_picture CASCADE;
7
+ CREATE FUNCTION delete_upload_set_on_last_picture() RETURNS trigger AS $$
8
+ BEGIN
9
+ WITH upload_sets AS (
10
+ SELECT distinct(upload_set_id)
11
+ FROM deleted_pictures
12
+ WHERE NOT EXISTS (
13
+ SELECT 1
14
+ FROM pictures p
15
+ WHERE p.upload_set_id = upload_set_id AND p.id NOT in (SELECT id FROM deleted_pictures)
16
+ LIMIT 1
17
+ )
18
+ )
19
+ DELETE FROM upload_sets WHERE id IN (SELECT upload_set_id FROM upload_sets);
20
+ RETURN NULL;
21
+
22
+ END $$ LANGUAGE plpgsql;
23
+
24
+ CREATE TRIGGER delete_upload_set_on_last_picture_trg
25
+ AFTER DELETE ON pictures
26
+ REFERENCING OLD TABLE AS deleted_pictures
27
+ FOR EACH STATEMENT
28
+ EXECUTE FUNCTION delete_upload_set_on_last_picture();
@@ -0,0 +1,126 @@
1
+ -- rm-cameras
2
+ -- depends: 20241128_01_ugthx-job-queue-args
3
+
4
+ DROP TRIGGER trg_pictures_hpixdens_upd ON pictures;
5
+ DROP TRIGGER trg_pictures_hpixdens ON pictures;
6
+ DROP FUNCTION pictures_hpixdens;
7
+
8
+ -- Read float values from EXIF
9
+ CREATE OR REPLACE FUNCTION get_float(val VARCHAR) RETURNS FLOAT AS $$
10
+ DECLARE
11
+ list VARCHAR[];
12
+ BEGIN
13
+ IF val ~ '^\d+\/\d+$' THEN
14
+ list := regexp_split_to_array(val, '/');
15
+ RETURN list[1]::float / list[2]::float;
16
+ ELSIF val ~ '^\d+(\.\d+)?$' THEN
17
+ RETURN val::float;
18
+ ELSE
19
+ RETURN NULL;
20
+ END IF;
21
+ END;
22
+ $$ LANGUAGE plpgsql IMMUTABLE PARALLEL SAFE;
23
+
24
+
25
+ -- GPS accuracy function
26
+ CREATE OR REPLACE FUNCTION gps_accuracy(metadata JSONB, exif JSONB) RETURNS FLOAT AS $$
27
+ DECLARE
28
+ gps_dop FLOAT;
29
+ gps_diff INT;
30
+ gps_hpos_err FLOAT;
31
+ BEGIN
32
+ -- Parse GPS DOP, either float or fraction
33
+ gps_dop := get_float(COALESCE(exif->>'Exif.GPSInfo.GPSDOP', exif->>'Xmp.exif.GPSDOP', ''));
34
+ gps_diff := (COALESCE(exif->>'Exif.GPSInfo.GPSDifferential', exif->>'Xmp.exif.GPSDifferential'))::INT;
35
+ gps_hpos_err := get_float(COALESCE(exif->>'Exif.GPSInfo.GPSHPositioningError', exif->>'Xmp.exif.GPSHPositioningError'));
36
+
37
+ -- Direct horizontal positioning error in meters -> return as is
38
+ IF gps_hpos_err IS NOT NULL AND gps_hpos_err > 0 THEN
39
+ RETURN gps_hpos_err;
40
+
41
+ -- GPS DOP available
42
+ ELSIF gps_dop IS NOT NULL AND gps_dop > 0 THEN
43
+ -- With a DGPS -> consider GPS nominal error as 1 meter
44
+ IF gps_diff = 1 THEN
45
+ RETURN gps_dop;
46
+
47
+ -- Without DGPS -> consider GPS nominal error as 3 meters in average
48
+ ELSE
49
+ RETURN 3 * gps_dop;
50
+ END IF;
51
+
52
+ -- DGPS -> return 2 meters precision
53
+ ELSIF gps_diff = 1 THEN
54
+ RETURN 2;
55
+
56
+ -- Approximate guesses based on model
57
+ ELSIF metadata->>'make' IS NOT NULL OR metadata->>'model' IS NOT NULL THEN
58
+ -- Good non-diff GPS devices (best case is 3m, so setting 4 for tolerance)
59
+ IF lower(metadata->>'make') IN (
60
+ 'gopro', 'insta360', 'garmin', 'viofo', 'xiaoyi', 'blackvue', 'tectectec',
61
+ 'arashi vision'
62
+ )
63
+ OR metadata->>'model' IN ('LG-R105', 'FDR-X1000V')
64
+ OR metadata->>'make' ILIKE '%xiaoyi%' THEN
65
+ RETURN 4;
66
+
67
+ -- Diff GPS devices
68
+ ELSIF lower(metadata->>'make') IN ('stfmani', 'trimble', 'imajing')
69
+ OR metadata->>'model' IN ('LB5') OR metadata->>'model' ILIKE '%ladybug%' THEN
70
+ RETURN 2;
71
+
72
+ -- Smartphones or not-so-good non-diff GPS devices
73
+ ELSIF lower(metadata->>'make') IN (
74
+ 'samsung', 'xiaomi', 'huawei', 'ricoh', 'lenovo', 'motorola', 'oneplus',
75
+ 'apple', 'google', 'sony', 'wiko', 'asus', 'cubot', 'lge', 'fairphone',
76
+ 'realme', 'symphony', 'crosscall', 'htc', 'homtom', 'hmd global', 'oppo',
77
+ 'ulefone'
78
+ ) THEN
79
+ RETURN 5;
80
+
81
+ -- Fallback for unknown make/model
82
+ ELSE
83
+ RETURN NULL;
84
+ END IF;
85
+ -- Fallback : no value
86
+ ELSE
87
+ RETURN NULL;
88
+ END IF;
89
+ END;
90
+ $$ LANGUAGE plpgsql IMMUTABLE PARALLEL SAFE;
91
+
92
+ -- Function should not be used in rollback, created a mock one for db_migration tests
93
+ CREATE OR REPLACE FUNCTION missing_fov(metadata JSONB) RETURNS JSONB AS $$
94
+ DECLARE
95
+ sensor_width FLOAT;
96
+ BEGIN
97
+ RETURN metadata;
98
+ END;
99
+ $$ LANGUAGE plpgsql IMMUTABLE PARALLEL SAFE;
100
+
101
+ -- Auto-insert pixel density/GPS accuracy for new pictures
102
+ CREATE OR REPLACE FUNCTION pictures_hpixdens_gpsacc() RETURNS TRIGGER AS $$
103
+ BEGIN
104
+ NEW.h_pixel_density := h_pixel_density(NEW.metadata);
105
+ NEW.gps_accuracy_m := gps_accuracy(NEW.metadata, NEW.exif);
106
+ RETURN NEW;
107
+ END;
108
+ $$ LANGUAGE plpgsql;
109
+
110
+ CREATE TRIGGER trg_pictures_hpixdens_gpsacc
111
+ BEFORE INSERT ON pictures
112
+ FOR EACH ROW
113
+ EXECUTE FUNCTION pictures_hpixdens_gpsacc();
114
+
115
+ CREATE TRIGGER trg_pictures_hpixdens_gpsacc_upd
116
+ BEFORE UPDATE OF metadata, exif ON pictures
117
+ FOR EACH ROW
118
+ EXECUTE FUNCTION pictures_hpixdens_gpsacc();
119
+
120
+ -- Mock table for db_migration tests
121
+ CREATE TABLE cameras(
122
+ model VARCHAR PRIMARY KEY,
123
+ sensor_width FLOAT NOT NULL
124
+ );
125
+
126
+ CREATE INDEX cameras_model_idx ON cameras USING GIST(model gist_trgm_ops);