/
create_db_model.py
1243 lines (1091 loc) · 51.9 KB
/
create_db_model.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
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
# -*- coding: utf-8 -*-
# -----------------------------------------------------------------------------
# Name: create_db_model
#
# Purpose: Script to create and update an ArcGIS data model based on a JSON input file.
# The implemented functions (e.g. "add_field()") include all parameters of the corresponding
# ArcGIS functions (e.g. "AddField()") and possibly additional parameters starting with "slu_".
# The implemented functions follow the naming convention "lowercase_underscore" in contrast to
# ArcGIS functions which have the naming convention "CapitalizedWord".
#
# Author: Timo Wicki, City of Lucerne
#
# Created: 02.01.2023
# -----------------------------------------------------------------------------
import sys, os, logging, json, time
import arcpy
def init_logging(file) -> None:
"""Initialises logging to a file and on the console.
Required:
file -- The path to the log file.
"""
global logger
logger = logging.getLogger('myapp')
# logging to file
hdlr = logging.FileHandler(file, mode='w')
formatter = logging.Formatter('%(asctime)s %(levelname)s %(message)s')
hdlr.setFormatter(formatter)
logger.addHandler(hdlr)
# logging to console
consoleHandler = logging.StreamHandler()
consoleHandler.setFormatter(formatter)
logger.addHandler(consoleHandler)
logger.setLevel(logging.INFO)
def search(file, text) -> int:
"""Search for a specific string in a file.
Required:
file -- The path to the file (e.g. log file).
text -- The string which is to be searched for.
Return:
cnt -- The number of occurrences of the string in the file.
"""
cnt = 0
with open(file) as f:
for line in f:
if text in line.lower():
cnt=cnt+1
return cnt
def field_exists(in_table, field_name) -> bool:
"""Checks whether the field exists.
Required:
in_table -- The Name of the table or the feature-class.
field_name -- The Name of the field.
"""
field_names = [field.name for field in arcpy.ListFields(in_table)]
if field_name in field_names:
return True
else:
return False
def contains_umlaut(name: str) -> bool:
"""Checks if a string contains a umlaut (ö, ä, ü).
Required:
name -- A string.
Return:
True: If there is a umlaut in the string.
False: If there is no umlaut in the string.
"""
umlauts = ['ü', 'ä', 'ö']
for char in name:
if char.lower() in umlauts:
return True
return False
def filter_dict(dic, dic_type = None) -> dict:
"""Filters a dictionary so that it contains only relevant data.
Required:
dic -- The dictionary that is to be filtered.
Optional:
dic_type -- "None": Removes all dictionary keys which have the value "" or None.
"feature": Filters the dictionary additionally so that it can be used for the function create_feature_class.
"table": Filters the dictionary additionally so it can be used for the function create_table.
Return:
dic_filtered -- The filtered dictionary.
"""
dic = {k: v for k, v in dic.items() if v is not None and len(str(v))>0}
dic_filtered = dic.copy()
if dic_type == "feature":
parameters_create_feature = ["out_path", "out_name", "geometry_type", "template",
"has_m", "has_z", "spatial_reference", "config_keyword", "spatial_grid_1",
"spatial_grid_2", "spatial_grid_3", "out_alias", "out_dataset"]
for parameter in dic:
if parameter not in parameters_create_feature:
dic_filtered.pop(parameter)
elif dic_type == "table":
parameters_create_table = ["out_path", "out_name", "template","config_keyword"]
for parameter in dic:
if parameter not in parameters_create_table:
dic_filtered.pop(parameter)
return dic_filtered
def get_spatial_reference(spatial_reference, vcs = None):
"""Returns the spatial reference system if it is valid.
Required:
spatial_reference -- The name of the coordinate system (e.g. "CH1903+ LV95").
Optional:
vcs -- The name of the vertical coordinate system.
Raises:
ValueError: If the given spatial_reference is invalid.
Return:
sr: The ArcGIS spatial reference.
"""
try:
if vcs:
sr = arcpy.SpatialReference(spatial_reference, vcs)
else:
sr = arcpy.SpatialReference(spatial_reference)
return sr
except Exception:
e = sys.exc_info()[1]
raise ValueError(f'spatial_reference "{spatial_reference}" is invalid: {e.args[0]}')
def delete_all(in_workspace)-> None:
"""Delete all existing features, tables, datasets and domains.
Required:
in_workspace -- The path to the workspace (gdb, sde connection file).
"""
#arcpy.env.workspace = in_workspace
#arcpy.env.overwriteOutput = True
fc_list = arcpy.ListFeatureClasses()
tables = arcpy.ListTables()
ds_list = arcpy.ListDatasets()
all_domains = arcpy.da.ListDomains(in_workspace)
# delete feature classes
for fc in fc_list:
try:
logger.info(f'The feature class "{fc}" will be deleted')
arcpy.management.Delete(fc)
except Exception:
e = sys.exc_info()[1]
logger.error(f'The existing feature class "{fc}" could not '
f'be deletd: {e.args[0]}')
# delete tables
for table in tables:
try:
logger.info(f'The table "{table}" will be deleted')
arcpy.management.Delete(table)
except Exception:
e = sys.exc_info()[1]
logger.error(f'The existing table "{table}" could not '
f'be deleted: {e.args[0]}')
# delete datasets
for ds in ds_list:
try:
logger.info(f'The datasets "{ds}" will be deleted')
arcpy.management.Delete(ds)
except Exception:
e = sys.exc_info()[1]
logger.error(f'The existing dataset "{ds}" could not '
f'be deleted: {e.args[0]}')
# delete domain
for domain in all_domains:
try:
logger.info(f'The domain "{domain.name}" will be deleted')
arcpy.management.DeleteDomain(in_workspace, domain.name)
except Exception:
e = sys.exc_info()[1]
logger.error(f'The existing domain "{domain.name}" could not '
f'be deleted: {e.args[0]}')
def delete_all_domain(in_workspace)-> None:
"""Delete all existing domains.
Required:
in_workspace -- The path to workspace (gdb, sde connection file).
"""
all_domains = arcpy.da.ListDomains(in_workspace)
# delete domain
for domain in all_domains:
try:
logger.info(f'The domain "{domain.name}" will be deleted')
remove_domain_from_fields(domain.name)
arcpy.management.DeleteDomain(in_workspace, domain.name)
except Exception:
e = sys.exc_info()[1]
logger.error(f'The existing domain "{domain.name}" could not '
f'be deleted: {e.args[0]}')
def delete_domain(in_workspace, domain_name):
"""Delete a domain (see Esri arcpy.management.DeleteDomain).
Required:
in_workspace -- The path to workspace (gdb, sde connection file).
domain_name -- The name of the domain.
"""
# delete domain
try:
logger.info(f'The domain "{domain_name}" will be deleted')
remove_domain_from_fields(domain_name)
arcpy.management.DeleteDomain(in_workspace, domain_name)
except Exception:
e = sys.exc_info()[1]
logger.error(f'The existing domain "{domain_name}" could not '
f'be deleted: {e.args[0]}')
def delete_item(in_data, data_type=None)-> None:
"""Delete item, tables, datasets (see Esri arcpy.management.Delete).
Required:
in_data -- The input data to be deleted.
Optional:
data_type -- The type of data to be deleted (e.g. "FeatureClass" oder "FeatureDataset").
"""
# delete item
try:
logger.info(f'Item "{in_data}" will be deleted')
arcpy.management.Delete(in_data, data_type)
except Exception:
e = sys.exc_info()[1]
logger.error(f'The existing Item "{in_data}" could not be '
f'deleted: {e.args[0]}')
def create_domain(in_workspace, domain_name, domain_description = None, field_type = "SHORT",
domain_type = "CODED", **kwargs)-> None:
"""Create a domain (see Esri arcpy.management.CreateDomain).
Required:
in_workspace -- The path to the workspace( gdb, sde connection file).
domain_name -- The name of the domain.
Optional:
domain_description -- The description of the domain.
field_type -- Specifies the type of attribute domain that will be created.
domain_type -- Specifies the domain type that will be created.
**kwargs -- Additional parameters for the function arcpy.management.CreateDomain.
"""
# check naming convention
if contains_umlaut(domain_name):
logger.warning(f'"{domain_name}" contains an Umlaut!')
# if not domain_name.isupper():
# logger.warning(f'"{domain_name}" is not in capital letters!')
# check if the domain already exists
all_domains = arcpy.da.ListDomains(in_workspace)
all_domain_names = []
for domain in all_domains:
all_domain_names.append(domain.name)
if domain_name in all_domain_names:
try:
logger.info(f'The existing domain "{domain_name}" will be deleted')
remove_domain_from_fields(domain_name)
arcpy.management.DeleteDomain(in_workspace, domain_name)
except Exception:
e = sys.exc_info()[1]
logger.error(f'The existing domain "{domain_name}" could not '
f'be deleted: {e.args[0]}')
return
# create domain
try:
logger.info(f'The domain "{domain_name}" will be created')
arcpy.management.CreateDomain(in_workspace, domain_name, domain_description,
field_type, domain_type, **kwargs)
except Exception:
e = sys.exc_info()[1]
logger.error(f'The domain "{domain_name}" could not be created: {e.args[0]}')
return
def add_coded_value_to_domain(in_workspace, domain_name, slu_domain_dict = None, **kwargs)-> None:
"""Add coded value to domain (see Esri arcpy.management.AddCodedValueToDomain).
In contrast to the ESRI function, a dictionary with the codes as keys and the code_descriptions as values
can be used as input parameter, which allows to add several values to the domain at the same time.
Required:
in_workspace -- The path to the workspace (gdb, sde connection file).
domain_name -- The name of the domain.
Optional:
slu_domain_dict -- Dictionary with the codes as the keys and the descriptions as the values.
**kwargs -- Additional parameters for the function arcpy.management.AddCodedValueToDomain.
Instead of a dictionary, the parameter "code" and "code_description" can
be used as input.
"""
if slu_domain_dict:
# adding codes and values contained in the dictionary
try:
logger.info(f'Coded values are added to the domain "{domain_name}"')
for code in slu_domain_dict:
arcpy.management.AddCodedValueToDomain(in_workspace, domain_name,
code, slu_domain_dict[code])
except Exception:
e = sys.exc_info()[1]
logger.error(f'No values could be added to the domain "{domain_name}": {e.args[0]}')
return
else:
# adding code and code_description
try:
logger.info(f'Coded value is added to the domain "{domain_name}"')
arcpy.management.AddCodedValueToDomain(in_workspace, domain_name, **kwargs)
except Exception:
e = sys.exc_info()[1]
logger.error(f'No values could be added to the domain "{domain_name}": {e.args[0]}')
return
def set_value_for_range_domain(in_workspace, domain_name, min_value, max_value)-> None:
"""Add values to a domain of the type Range (see Esri arcpy.management.SetValueForRangeDomain).
Required:
in_workspace -- The path to the workspace (gdb, sde connection file).
domain_name -- The name of the domain.
min_value -- The minimum value of the range domain.
max_value -- The maximum value of the range domain.
"""
# add values
try:
logger.info(f'Adding values to the domain "{domain_name}"')
arcpy.management.SetValueForRangeDomain(in_workspace, domain_name, min_value, max_value)
except Exception:
e = sys.exc_info()[1]
logger.error(f'No values could be added to the domain "{domain_name}": {e.args[0]}')
return
def remove_domain_from_fields(domain_name):
"""Removes domain assignment from all fields that use the domain (see Esri arcpy.management.RemoveDomainFromField).
Required:
domain_name -- The name of the domain
"""
datasets = arcpy.ListDatasets(feature_type = "Feature")
datasets = [''] + datasets if datasets is not None else []
for ds in datasets:
for fc in arcpy.ListFeatureClasses(feature_dataset = ds):
for field in arcpy.ListFields(fc):
if field.domain and field.domain == domain_name:
try:
logger.info(f'Removing domain "{domain_name}" from the field "{field.name}" '
f'in the feature class "{fc}"')
arcpy.management.RemoveDomainFromField(fc, field.name)
except Exception:
e = sys.exc_info()[1]
logger.error(f'Domain "{domain_name}" could not be removed from the field "{field.name}" '
f'in the feature class "{fc}" : {e.args[0]}')
if not ds:
for tb in arcpy.ListTables():
for field in arcpy.ListFields(tb):
if field.domain and field.domain == domain_name:
try:
logger.info(f'Removing domain "{domain_name}" from the "{field.name}" '
f'in the table "{tb}"')
arcpy.management.RemoveDomainFromField(tb, field.name)
except Exception:
e = sys.exc_info()[1]
logger.error(f'Domain "{domain_name}" could not be removed from the field "{field.name}" '
f'in the table "{tb}": {e.args[0]}')
def create_feature_dataset(out_dataset_path, out_name, spatial_reference = 'CH1903+ LV95',
slu_overwrite = True)-> None:
"""Create a feature dataset (see Esri arcpy.management.CreateFeatureDataset).
Required:
out_dataset_path -- The path to the workspace (gdb, sde connection file).
out_name -- The name of the dataset.
Optional:
spatial_reference -- The name of the spatial reference system.
slu_overwrite -- If an existing dataset is to be overwritten (True or False).
"""
# check naming convention
if contains_umlaut(out_name):
logger.warning(f'"The dataset {out_name}" contains an Umlaut!')
# if not out_name.isupper():
# logger.warning(f'"The dataset {out_name}" is not in capital letters!')
# check if the dataset already exists (not working!?)
out_dataset = os.path.join(out_dataset_path, out_name)
if arcpy.Exists(out_dataset):
if slu_overwrite:
logger.info(f'Existing dataset "{out_name}" will be deleted')
arcpy.management.Delete(out_dataset)
else:
logger.warning(f'Existing dataset "{out_name}" will not be deleted')
return
# create a dataset
try:
logger.info(f'The dataset "{out_name}" will be created')
arcpy.management.CreateFeatureDataset(out_dataset_path, out_name, spatial_reference)
except Exception:
e = sys.exc_info()[1]
logger.error(f'The dataset "{out_name}" could not be created: {e.args[0]}')
def create_feature_class(out_path, out_name, geometry_type, spatial_reference = 'CH1903+ LV95',
out_dataset = None, slu_overwrite = True, **kwargs) -> None:
"""Create a feature class (see documentation of Esri).
Required:
out_path -- The path to the workspace (gdb, sde connection file).
out_name -- The name of the output feature class.
geometry_type -- The geometry type.
Optional:
spatial_reference -- The name of the spatial reference system.
out_dataset -- The name of the dataset in which the feature class will be created.
slu_overwrite -- If an existing feature class is to be overwritten (True or False).
**kwargs -- Additional parameters for the function arcpy.management.CreateFeatureclass (e.g. "has_z").
"""
# check naming convention
if contains_umlaut(out_name):
logger.warning(f'The feature class name "{out_name}" contains an Umlaut!')
# if not out_name.isupper():
# logger.warning(f'The feature class name "{out_name}" is not in capital letters!')
# check if feature class is to be created in a dataset
if out_dataset:
out_path = os.path.join(out_path, out_dataset)
else:
out_path = out_path
# check if the feature class already exists
out_feature = os.path.join(out_path, out_name)
if arcpy.Exists(out_feature):
if slu_overwrite:
logger.info(f'Existing feature class "{out_name}" will be deleted')
arcpy.management.Delete(out_feature)
else:
logger.warning(f'Existing feature class "{out_name}" will not be overwritten')
return
# create feature class
try:
logger.info(f'The feature class "{out_name}" will be created')
arcpy.management.CreateFeatureclass(
out_path = out_path, out_name = out_name, geometry_type = geometry_type,
spatial_reference = spatial_reference, **kwargs
)
except Exception:
e = sys.exc_info()[1]
logger.error(f'The feature class "{out_name}" could not be created: {e.args[0]}')
def create_table(out_path, out_name, slu_overwrite = True, **kwargs) -> None:
"""create a table (see documentation of Esri).
Required:
out_path -- The path to the workspace (gdb, sde connection file).
out_name -- The name of the output table.
Optional:
slu_overwrite -- If an existing table is to be overwritten (True or False).
**kwargs -- Additional parameters for the function arcpy.management.CreateTable.
"""
# check naming convention
if contains_umlaut(out_name):
logger.warning(f'The table name "{out_name}" contains an Umlaut!')
# if not out_name.isupper():
# logger.warning(f'The table name "{out_name}" is not in capital letters!')
# check if the table already exists (not working!?)
out_table = os.path.join(out_path, out_name)
if arcpy.Exists(out_table):
if slu_overwrite:
logger.info(f'The existing table "{out_name}" will be deleted')
arcpy.management.Delete(out_table)
else:
logger.warning(f'The existing table "{out_table}" will not be overwritten')
return
# create table
try:
logger.info(f'The table "{out_name}" will be created')
arcpy.management.CreateTable(out_path = out_path, out_name = out_name, **kwargs)
except Exception:
e = sys.exc_info()[1]
logger.error(f'The table "{out_name}" could not be created: {e.args[0]}')
def add_field(in_table, field_name, field_type, **kwargs):
"""Adding a field to a table or a feature class (see documentation of Esri)
Required:
in_table -- The name of the table or the feature class.
field_name -- The name of the field.
field_type -- The type of the field.
Optional:
field_domain -- The name of the domain to be assigned to the field.
**kwargs -- Additonal parameters for the function arcpy.management.AddField.
"""
# check naming convention
if contains_umlaut(field_name):
logger.warning(f'The field name "{field_name}" contains an Umlaut!')
# if not field_name.isupper():
# logger.warning(f'The field name "{field_name}" is not in capital letters!')
# Add field
try:
logger.info(f'Adding the field "{field_name}"')
arcpy.management.AddField(in_table = in_table, field_name = field_name,
field_type = field_type, **kwargs)
except Exception:
e = sys.exc_info()[1]
logger.error(f'Error when creating the field "{field_name}": {e.args[0]}')
def delete_field(in_table, field_name):
"""Deleting a field from a table or a feature class (see documentation of Esri).
Required:
in_table -- The name of the table or the feature class.
field_name -- The name of the field to be deleted.
"""
try:
logger.info(f'Feld "{field_name}" will be deleted')
arcpy.management.DeleteField(in_table, field_name)
except Exception:
e = sys.exc_info()[1]
logger.error(f'Error when deleting the field "{field_name}": {e.args[0]}')
def calculate_field(in_table, field, expression, **kwargs):
""" Calculates the values of a field for a feature class, feature layer,
or raster (see documentation of Esri).
Required:
in_table -- The name of the table or feature class.
field -- The name of the field.
expression -- The simple calculation expression used to create a value that will populate the selected rows.
Optional:
**kwargs -- Additional parameters for the function arcpy.management.CalculateField.
"""
# Get a list of fields in the feature class or table
if not field_exists(in_table, field):
logger.warning(f'The field "{field}" does not exist and will be created.')
try:
logger.info(f'Calculate field "{field}"')
arcpy.management.CalculateField(in_table = in_table, field = field,
expression = expression, **kwargs)
except Exception:
e = sys.exc_info()[1]
logger.error(f'Error when calculating the field "{field}": {e.args[0]}')
def assign_domain_to_field(in_table, field_name, domain_name, subtype_code = None):
"""Assign a domain to a field (see documentation of Esri).
Required:
in_table -- The name of the table or the feature class.
field_name -- The name of the field.
field_domain -- The name of the domain.
Optional:
subtype_code -- Subtypes for which the domain applies e.g. "1: Event;2: Boulevard".
"""
# assign domain
try:
if subtype_code:
logger.info(f'The domain "{domain_name}" will be assigned to the field "{field_name}" '
f'for the subtype "{subtype_code}"')
arcpy.management.AssignDomainToField(in_table, field_name, domain_name, subtype_code)
else:
logger.info(f'The domain "{domain_name}" will be assigned to the field "{field_name}"')
arcpy.management.AssignDomainToField(in_table, field_name, domain_name)
except Exception:
e = sys.exc_info()[1]
logger.error(f'The domain"{domain_name}" could not be assigned to the field "{field_name}": '
f'{e.args[0]}')
def create_subtype_field(in_table, field_name):
"""Create a subtype field. In contrast to the function arcpy.management.SetSubtypeField,
the field is newly created, if it does not exist already.
Required:
in_table -- The name of the table or feature class.
field_name -- The field name of the subtype field to be created.
"""
# add a field of type "SHORT"
if not field_exists(in_table, field_name):
add_field(in_table, field_name, "SHORT")
# create subtype field
try:
logger.info(f'The field "{field_name}" will be defined as subtype field')
arcpy.management.SetSubtypeField(in_table = in_table, field = field_name)
except Exception:
e = sys.exc_info()[1]
logger.error(f'The field "{field_name}" could not be defined as subtype field: '
f'{e.args[0]}')
def add_subtype(in_table, subtype_code, subtype_description):
"""Adding a subtype to a feature class or a table (see documentation of Esri).
Required:
in_table -- The name of the table or feature class.
subtype_code -- Unique integer value.
subtype_description -- The subtype description.
"""
# create subtype
try:
logger.info(f'The subtype "{subtype_code}":"{subtype_description}" will be created')
arcpy.management.AddSubtype(in_table = in_table, subtype_code = subtype_code,
subtype_description = subtype_description)
except Exception:
e = sys.exc_info()[1]
logger.error(f'The subtype "{subtype_code}":"{subtype_description}" could not be created: '
f'{e.args[0]}')
def add_global_id(in_dataset):
"""Adding a GlobalId to a feature class or table (see documentation of Esri)
Required:
in_dataset -- The name of the table or feature class
"""
try:
logger.info(f'Adding GlobalID to "{in_dataset}"')
arcpy.management.AddGlobalIDs(in_dataset)
except Exception:
e = sys.exc_info()[1]
logger.error(f'GlobalID could not be added to "{in_dataset}": '
f'{e.args[0]}')
def set_default_subtype(in_table, subtype_code):
"""Set a subtype as default (see documentation of Esri).
Required:
in_table -- The name of the table or feature class.
subtype_code -- The subtype code to be used as a default (e.g. "1").
"""
try:
logger.info(f'Set subtype "{subtype_code}" in "{in_table}" as default')
arcpy.management.SetDefaultSubtype(in_table, subtype_code)
except Exception:
e = sys.exc_info()[1]
logger.error(f'The subtpye "{subtype_code}" could not be set as default in the table "{in_table}": '
f'{e.args[0]}')
def enable_editor_tracking(in_dataset, creator_field = "CREATED_USER", creation_date_field = "CREATED_DATE",
last_editor_field = "LAST_EDITED_USER", last_edit_date_field = "LAST_EDITED_DATE",
add_fields = "NO_ADD_FIELDS", record_dates_in = "UTC"):
"""Activating editor tracking (see documentation of Esri).
Required:
in_dataset -- The name of the table or feature class.
Optional:
Additional parameters for the function arcpy.management.EnableEditorTracking.
"""
try:
logger.info(f'Activate editor tracking in "{in_dataset}"')
arcpy.management.EnableEditorTracking(
in_dataset, creator_field, creation_date_field, last_editor_field,
last_edit_date_field, add_fields, record_dates_in
)
except Exception:
e = sys.exc_info()[1]
logger.error(f'Editor tracking could not be activated in "{in_dataset}": '
f'{e.args[0]}')
def enable_attachments(in_dataset):
"""Activate attachments (see documentation of Esri).
Required:
in_dataset -- The name of the table or feature class.
"""
try:
logger.info(f'Activate attachments in "{in_dataset}"')
arcpy.management.EnableAttachments(in_dataset)
except Exception:
e = sys.exc_info()[1]
logger.error(f'Attachments could not be activated in "{in_dataset}": '
f'{e.args[0]}')
def add_attribute_rule(in_table, name, type, script_expression, **kwargs):
"""Adding attribute rule (see documentation of Esri).
Required:
in_table -- The name of the table or feature class.
name -- The name of the rule.
type -- The type of the rule.
script_expression -- The arcade expression to define the rule.
Optional:
**kwargs -- Additional parameters for the function arcpy.management.AddAttributeRule
"""
try:
logger.info(f'Adding attribute rule "{name}" to "{in_table}"')
arcpy.management.AddAttributeRule(in_table, name, type, script_expression, **kwargs)
except Exception:
e = sys.exc_info()[1]
logger.error(f'Attribute rule "{name}" could not be added to "{in_table}": '
f'{e.args[0]}')
def create_relationship_class(origin_table, destination_table, out_relationship_class,
slu_overwrite = True, **kwargs) -> None:
"""Create a realationship class (see documentation of Esri)
Required:
origin_table -- The table or feature class that is assigned to the target table.
destination_table -- The table that is assigned to the source table.
out_relationship_class -- The output relationship class.
Optional:
slu_overwrite -- If an existing table is to be overwritten (True or False).
**kwargs -- Additional parameters of the function arcpy.management.CreateRelationshipClass
"""
# check naming convention
if contains_umlaut(out_relationship_class):
logger.warning(f'The name of the relationship class "{out_relationship_class}" contains an Umlaut!')
#if not out_relationship_class.isupper():
# logger.warning(f'The name of the relationship class "{out_relationship_class}" is in capital letters!')
# check if the relationship class already exists (not working!?)
if arcpy.Exists(out_relationship_class):
if slu_overwrite:
logger.info(f'Existing relationship class "{out_relationship_class}" will be deleted')
arcpy.management.Delete(out_relationship_class)
else:
logger.warning(f'Existing relationship class "{out_relationship_class}" will not be overwritten')
return
# Feature erstellen
try:
logger.info(f'The relationship class "{out_relationship_class}" will be created')
arcpy.management.CreateRelationshipClass(origin_table = origin_table, destination_table = destination_table,
out_relationship_class = out_relationship_class, **kwargs)
except Exception:
e = sys.exc_info()[1]
logger.error(f'The realtionship class "{out_relationship_class}" could not be created: {e.args[0]}')
def add_rule_to_relationship_class(in_rel_class, **kwargs):
"""Add a rule to a realtionship class (see Esri arcpy.management.AddRuleToRelationshipClass)
For example, it is possible to add a rule to specify that the relationship is only set for a certain subtype.
Required:
in_rel_class -- The name of the relationship class.
Optional:
**kwargs -- Additional parameters for the function arcpy.management.AddAttributeRule.
"""
try:
logger.info(f'Adding a rule to the relationship class "{in_rel_class}"')
arcpy.management.AddRuleToRelationshipClass(in_rel_class, **kwargs)
except Exception:
e = sys.exc_info()[1]
logger.error(f'The rule could not be added to the relationship class "{in_rel_class}": '
f'{e.args[0]}')
# Main module: Check input parameters and call functions
def main(conpath, db_name, overwrite, spatial_reference_name, environment_settings,
delete_existing, domains, datasets, features, tables, relations, update_features, update_tables,
update_domains, delete_features, delete_datasets, delete_domains, delete_all_domains, stage) -> None:
"""Check input parameters and call functions
"""
# define the path to the workspace (sde connection file oder gdb)
if stage:
if "." in db_name:
# Assumption: db_name has the file ending included
db_fullname = db_name
else:
# Assumption: db_name ends as following, depending on the server stage (default at city of Lucerne)
if stage == 'TEST':
db_fullname = db_name + '.owner.test.sde'
elif stage == 'INTE':
db_fullname = db_name + '.owner.inte.sde'
elif stage == 'PROD':
db_fullname = db_name + '.owner.prod.sde'
else:
# Assumption: Esri file geodatabse (.gdb)
db_fullname = db_name + '.gdb'
else:
db_fullname = db_name
workspace = os.path.join(conpath, db_fullname)
# check if path exists
# Assumption: if it does not end with ".gdb" it's a file and not a folder
if ".gdb" in db_fullname:
if not os.path.isdir(workspace):
logger.error(f'workspace "{workspace}" does not exist!')
raise ValueError(f'Workspace "{workspace}" does not exist!')
else:
if not os.path.isfile(workspace):
logger.error(f'Workspace "{workspace}" does not exist!')
raise ValueError(f'Workspace "{workspace}" does not exist!')
# set workspace
arcpy.env.workspace = workspace
# check spatial reference system
spatial_reference = get_spatial_reference(spatial_reference_name)
# set env setting overwrite
if overwrite == 'True':
overwrite = True
arcpy.env.overwriteOutput = True
else:
overwrite = False
arcpy.env.overwriteOutput = False
# set additional env settings
if environment_settings:
logger.info("Adjust default environment settings")
if 'xy_tolerance' in environment_settings:
arcpy.env.XYTolerance = environment_settings['xy_tolerance']
if 'xy_resolution' in environment_settings:
arcpy.env.XYResolution = environment_settings['xy_resolution']
if 'xy_domain' in environment_settings:
arcpy.env.XYDomain = environment_settings['xy_domain']
if 'output_z_flag' in environment_settings:
arcpy.env.outputZFlag = environment_settings['output_z_flag']
if 'z_tolerance' in environment_settings:
arcpy.env.ZTolerance = environment_settings['z_tolerance']
if 'z_resolution' in environment_settings:
arcpy.env.ZResolution = environment_settings['z_resolution']
if 'z_domain' in environment_settings:
arcpy.env.ZDomain = environment_settings['z_domain']
if 'output_z_value' in environment_settings:
arcpy.env.outputZValue = environment_settings['output_z_value']
if 'output_m_flag' in environment_settings:
arcpy.env.outputMFlag = environment_settings['output_m_flag']
if 'm_tolerance' in environment_settings:
arcpy.env.MTolerance = environment_settings['m_tolerance']
if 'm_resolution' in environment_settings:
arcpy.env.MResolution = environment_settings['m_resolution']
if 'm_domain' in environment_settings:
arcpy.env.MDomain = environment_settings['m_domain']
# remove existing feature datasets, feature classes, tables and domains
if delete_existing == 'True':
logger.info("Delete all existing data")
delete_all(workspace)
# create domains
if domains:
for dic in domains:
# filter dictionary (if "" oder None)
dic_filtered = filter_dict(dic)
# call functions with correct parameters
if dic_filtered['domain_type'] == "CODED":
# extract DomainValues from dictionary
domain_values = dic_filtered.pop('DomainValues')
# create domain
create_domain(workspace, **dic_filtered)
# adding domain values
add_coded_value_to_domain(workspace, dic_filtered['domain_name'], domain_values)
elif dic_filtered['domain_type'] == "RANGE":
domain_range = dic_filtered.pop('DomainRange')
# creating domain
create_domain(workspace, **dic_filtered)
# adding domain values
set_value_for_range_domain(workspace, dic_filtered['domain_name'],
domain_range["min_value"], domain_range["max_value"])
# creating dataset
if datasets:
for dic in datasets:
# filter dictionary
dic_filtered = filter_dict(dic)
# adding additional parameters
dic_filtered['spatial_reference'] = spatial_reference
dic_filtered['slu_overwrite'] = overwrite
# creating dataset
create_feature_dataset(workspace, **dic_filtered)
# creating feature classes
if features:
for dic in features:
# filter dictionary
dic_filtered = filter_dict(dic)
# filter dictionary for create_feature_class
dic_create_feature = filter_dict(dic_filtered, "feature")
# adding additional parameters
dic_create_feature['spatial_reference'] = spatial_reference
dic_create_feature['slu_overwrite'] = overwrite
# creating feature class
create_feature_class(workspace, **dic_create_feature)
# add GlobalIDs
if 'GlobalID' in dic_filtered and dic_filtered['GlobalID'] == 'True':
add_global_id(dic_filtered['out_name'])
# add subtypes
if 'Subtypes' in dic_filtered:
dic_subtype = dic_filtered['Subtypes']
create_subtype_field(dic_filtered['out_name'], dic_subtype['field_name'])
for code in dic_subtype['SubtypeValues']:
add_subtype(dic_filtered['out_name'], code, dic_subtype['SubtypeValues'][code])
if 'DefaultSubtypeCode' in dic_subtype:
set_default_subtype(dic_filtered['out_name'], dic_subtype['DefaultSubtypeCode'])
# add fields
if 'Fields' in dic_filtered:
for dic_field in dic_filtered['Fields']:
field_domain_subtypes = None
if "FieldDomainSubtype" in dic_field:
field_domain_subtypes = dic_field.pop('FieldDomainSubtype')
# add Fields
add_field(dic_filtered['out_name'], **dic_field)
# add domains to subtypes
if field_domain_subtypes:
for dic_domain in field_domain_subtypes:
assign_domain_to_field(dic_filtered['out_name'], dic_field['field_name'],
dic_domain['field_domain'], dic_domain['subtype_code'])
# add editor tracking including editor tracking fields
if 'EditorTracking' in dic_filtered and dic_filtered['EditorTracking'] == 'True':
enable_editor_tracking(in_dataset = dic_filtered['out_name'], add_fields = "ADD_FIELDS")
# enable attachments
if 'EnableAttachments' in dic_filtered and dic_filtered['EnableAttachments'] == 'True':
enable_attachments(dic_filtered['out_name'])
# add attribute rules
if 'AttributeRules' in dic_filtered:
for dic_rule in dic_filtered['AttributeRules']:
add_attribute_rule(dic_filtered['out_name'], **dic_rule)
# creating table
if tables:
for dic in tables:
# filter dictionary
dic_filtered = filter_dict(dic)
# filter dictionary for create_table
dic_create_table = filter_dict(dic_filtered, "table")
# create table
create_table(workspace, **dic_create_table)
# add GlobalIDs
if 'GlobalID' in dic_filtered and dic_filtered['GlobalID'] == 'True':
add_global_id(dic_filtered['out_name'])
# add subtypes
if 'Subtypes' in dic_filtered:
dic_subtype = dic_filtered['Subtypes']
create_subtype_field(dic_filtered['out_name'], dic_subtype['field_name'])
for code in dic_subtype['SubtypeValues']:
add_subtype(dic_filtered['out_name'], code, dic_subtype['SubtypeValues'][code])
if 'DefaultSubtypeCode' in dic_subtype:
set_default_subtype(dic_filtered['out_name'], dic_subtype['DefaultSubtypeCode'])
# add fields
if 'Fields' in dic_filtered:
for dic_field in dic_filtered['Fields']:
field_domain_subtypes = None
if "FieldDomainSubtype" in dic_field:
field_domain_subtypes = dic_field.pop('FieldDomainSubtype')
# add fields
add_field(dic_filtered['out_name'], **dic_field)
# add domains to subtypes
if field_domain_subtypes:
for dic_domain in field_domain_subtypes:
assign_domain_to_field(dic_filtered['out_name'], dic_field['field_name'],
dic_domain['field_domain'], dic_domain['subtype_code'])
# add editor tracking including editor tracking Felder
if 'EditorTracking' in dic_filtered and dic_filtered['EditorTracking'] == 'True':
enable_editor_tracking(in_dataset = dic_filtered['out_name'], add_fields = "ADD_FIELDS")
# enable attachments
if 'EnableAttachments' in dic_filtered and dic_filtered['EnableAttachments'] == 'True':
enable_attachments(dic_filtered['out_name'])
# add attribute rules
if 'AttributeRules' in dic_filtered:
for dic_rule in dic_filtered['AttributeRules']:
add_attribute_rule(dic_filtered['out_name'], **dic_rule)
# creating realationship class
if relations:
for dic in relations:
# filter dictionary