-
Notifications
You must be signed in to change notification settings - Fork 1
/
CA_Model_Code.py
4299 lines (3962 loc) · 293 KB
/
CA_Model_Code.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
# Script Name: Cellular_Automata
# Author: Joshua (Jay) Wimhurst
# Date created: 2/27/2023
# Date Last Edited: 4/30/2023
############################### DESCRIPTION ###################################
# This script constructs a cellular automaton for grid cell states predicted
# by a logistic regression model of wind farm site suitability across the CONUS
# (and individual states). The cellular automaton steps forward the grid cells
# in time (5-year timesteps from 2020 to 2050), in order to project grid cells
# that could gain a commercial wind farm over the next few decades. The fitted
# median coefficients and intercept from running the logistic regression model
# are used as a starting point, with the cellular automaton providing the
# option to modify coefficients for different scenarios of future wind farm
# development (e.g., wind speed increasing, more positive attitude toward
# wind energy technologies). The primary output is a map showing the location
# of current and future wind farm locations in a given state or across the
# CONUS. Four different versions of the model can be executed: all predictors,
# all predictors but wind speed, wind speed only, and a refined predictor set.
###############################################################################
import arcpy
import matplotlib.pyplot as plt
import numpy as np
import os
import pandas as pd
import sys
from arcpy.da import TableToNumPyArray, UpdateCursor, SearchCursor
from fpdf import FPDF
from math import sin, radians, ceil, e
from numpy import sqrt
from pandas import DataFrame
from tqdm import tqdm
from warnings import filterwarnings
filterwarnings("ignore")
######################### DIRECTORY CONSTRUCTION ##########################
# IMPORTANT - set up a file directory to place the Console Output,
# generated figures, and constructed WiFSS surfaces BEFORE attempting to
# run this model
directory = # Specify directory here
# IMPORTANT - make sure to also create a folder for the WiFSS surfaces
# created from applying the trained and tested model to all grid cells
directoryPlusSurfaces = directory + "\WiFSS_Surfaces"
# IMPORTANT - make sure to also create a folder for the WiFSS surfaces
# created from projecting the gridded surface out to the year 2050
directoryPlusFutureSurfaces = directory + "\WiFSS_Future_Surfaces"
# IMPORTANT - make sure to also create a folder for the QADI tables created
# from quantifying differences between projected grid cell surfaces (and a
# sub-folder named after the state selected or CONUS)
directoryPlusQADI = directory + "\QADI_Tables"
# IMPORTANT - make sure to also create a folder for the coefficients
# obtained from fitting the model (and a sub-folder named after the state
# selected or CONUS), which will be needed for users running
# the cellular automaton portion of the model
directoryPlusCoefficients = directory + "\Coefficients"
# IMPORTANT - make sure to also create a folder for the intercepts
# obtained from fitting the model (and a sub-folder named after the state
# selected or CONUS), which will be needed for users running
# the cellular automaton portion of the model
directoryPlusIntercepts = directory + "\Intercepts"
# IMPORTANT - make sure to also create a folder for the constraints and
# neighborhood effects that are defined by the model user
directoryPlusConstraints = directory + "\Defined_Constraints"
directoryPlusNeighborhoods = directory + "\Defined_Neighborhoods"
# IMPORTANT - make sure to also create a folder that contains the results of
# applying constraints and neighborhood effects to all grid cells in a
# given study domain
directoryPlusConstraintsAndNeighborhoods = directory + "\Constraints_and_Neighborhood_Effects"
# The script prints the console output to a text file, with a previous
# version deleted prior to the model run
if os.path.exists(directory + "\Cellular_Automata_Console_Output.txt"):
os.remove(directory + "\Cellular_Automata_Console_Output.txt")
####################### DATASET SELECTION AND SETUP ###########################
# PDF file containing the console output is initiated and caveat is added
pdf = FPDF()
pdf.add_page()
pdf.set_xy(4, 4)
pdf.set_font(family = 'arial', size = 13.0)
pdf.multi_cell(w=0, h=5.0, align='R', txt="Console output", border = 0)
pdf.multi_cell(w=0, h=5.0, align='L',
txt="------------------ DATASET SELECTION AND SETUP ------------------"
+'\n\n'+ "NOTE: The Logistic_Regression_Model.py script MUST be executed "
+'\n'+ "prior to running the Cellular_Automata_Model.py script."
+'\n'+ "If one wishes to execute the model over states that "
+'\n'+ "contain zero commercial wind farms (Louisiana, Mississippi "
+'\n'+ "Alabama, Georgia, South Carolina, Kentucky), states that possess "
+'\n'+ "wind farms in only one grid cell at all but the highest spatial "
+'\n'+ "resolutions (Arkansas, Florida, Virginia, Delaware, Connecticut,"
+'\n'+ "New Jersey, Tennessee), or states at low spatial resolutions at which "
+'\n'+ "too many predictors were removed due to collinearity (Rhode Island "
+'\n'+ "at the 100th or 80th percentile), the Logistic_Regression_Model.py "
+'\n'+ "script must be executed for the CONUS.\n", border=0)
# Select the study region for which the cellular automaton will be constructed,
# CONUS or a single state.
def studyRegion(values, message):
while True:
x = input(message)
if x in values:
studyRegion.region = x
break
else:
print("Invalid value; options are " + str(values))
studyRegion(["CONUS","Alabama","Arizona","Arkansas","California","Colorado",
"Connecticut","Delaware","Florida","Georgia","Idaho","Illinois",
"Indiana","Iowa","Kansas","Kentucky","Louisiana","Maine","Maryland",
"Massachusetts","Michigan","Minnesota","Mississippi","Missouri",
"Montana","Nebraska","Nevada","New_Hampshire","New_Mexico",
"New_Jersey","New_York","North_Carolina","North_Dakota","Ohio",
"Oklahoma","Oregon","Pennsylvania","Rhode_Island","South_Carolina",
"South_Dakota","Tennessee","Texas","Utah","Vermont","Virginia",
"Washington","West_Virginia","Wisconsin","Wyoming"],
'''Enter desired study region. \n(CONUS, Alabama, Arizona, Arkansas, '''
'''California, Colorado, Connecticut, Delaware, Florida, Georgia, '''
'''Idaho, Illinois, Indiana, Iowa, Kansas, Kentucky, Louisiana, '''
'''Maine, Maryland, Massachusetts, Michigan, Minnesota, Mississippi, '''
'''Missouri, Montana, Nebraska, Nevada, New_Hampshire, New_Jersey, '''
'''New_Mexico, New_York, North_Carolina, North_Dakota, Ohio, '''
'''Oklahoma, Oregon, Pennsylvania, Rhode_Island, South_Carolina, '''
'''South_Dakota, Tennessee, Texas, Utah, Virginia, Vermont, '''
'''Washington, West_Virginia, Wisconsin, Wyoming):\n''')
# User input for wind farm density in acres per Megawatt: 25, 45, 65, or 85
def farmDensity(values,message):
while True:
x = input(message)
if x in values:
farmDensity.density = x
break
else:
print("Invalid value; options are " + str(values))
farmDensity(["25","45","65","85"], "\nEnter desired wind farm density (25, 45, 65, or 85 acres/MW):\n")
# User input for wind power capacity as a percentile: 20, 40, 60, 80, or 100
def farmCapacity(values,message):
while True:
x = input(message)
if x in values:
farmCapacity.capacity = x
break
else:
print("Invalid value; options are " + str(values))
farmCapacity(["20","40","60","80","100"], "\nEnter desired wind power capacity (20, 40, 60, 80, or 100 percentile):\n")
# NOTE: 20th percentile = 30MW, 40th percentile = 90MW, 60th percentile = 150 MW
# 80th percentile = 201.5 MW, 20th percentile = 525 MW
# Based on selected percentile, the print-out to the console output changes
if farmCapacity.capacity == "100":
power = "525 MW"
elif farmCapacity.capacity == "80":
power = "202 MW"
elif farmCapacity.capacity == "60":
power = "150 MW"
elif farmCapacity.capacity == "40":
power = "90 MW"
elif farmCapacity.capacity == "20":
power = "30 MW"
# User inputs are added to the console output
pdf.multi_cell(w=0, h=5.0, align='L',
txt="\nSpecified study region: " + str(studyRegion.region)
+'\n'+"Specified wind farm density: " + str(farmDensity.density) + " acres/MW"
+'\n'+"Specified wind power capacity: " + str(farmCapacity.capacity) + "th percentile (" + power + ")", border=0)
############### SETTING OF CONSTRAINTS AND NEIGHBORHOOD EFFECTS ###############
# Function call for the computation of grid cells' constraints and
# neighborhood effects
def ConstraintNeighborhood():
####################### SETTING UP THE GEODATABASE ########################
# The study area, capacity, and density selected by the end-user are now
# defined as global variables to be used in this section of the code
ConstraintNeighborhood.studyArea = studyRegion.region
ConstraintNeighborhood.capacity = farmCapacity.capacity
ConstraintNeighborhood.density = farmDensity.density
# The computation of constraints and neighborhood effects may not be necessary
# if the user does not wish to redefine those that have already been made.
# This condition gives the user the option of skipping this function.
if os.path.exists("".join([directoryPlusConstraintsAndNeighborhoods + "/Hexagon_Grid_", ConstraintNeighborhood.density, "_acres_per_MW_", ConstraintNeighborhood.capacity, "th_percentile_", ConstraintNeighborhood.studyArea, ".gdb"])) is True:
# If the filepath exists, then constraints and neighborhood effects
# have been previously computed. The user is notified of what they are.
dfConstraints = pd.read_csv("".join([directoryPlusConstraints + "/", ConstraintNeighborhood.studyArea, "_", ConstraintNeighborhood.density, "_acres_per_MW_", ConstraintNeighborhood.capacity, "th_percentile.csv"]))
dfNeighborhoods = pd.read_csv("".join([directoryPlusNeighborhoods + "/", ConstraintNeighborhood.studyArea, "_", ConstraintNeighborhood.density, "_acres_per_MW_", ConstraintNeighborhood.capacity, "th_percentile.csv"]))
print("".join(["\nConstraints for ", ConstraintNeighborhood.studyArea, " at ", ConstraintNeighborhood.density, " acres per MW and for ", ConstraintNeighborhood.capacity, "th percentile wind farms have been defined previously:"]))
for j in range(len(dfConstraints)):
print("-", dfConstraints["Constraints"][j])
print("".join(["\nA hexagonal neighborhood range of ", str(dfNeighborhoods["Neighborhood"][0]) ," grid cell(s) has also been previously defined."]))
def remake(values, message):
while True:
x = input(message)
if x in values:
remake.YesOrNo = x
break
else:
print("Invalid value; options are " + str(values))
# This will be needed for recalculating neighborhood effects after
# each iteration of the cellular automaton
ConstraintNeighborhood.remake = remake.YesOrNo
remake(["Y","N"],"".join(["\nWould you like to redefine the constraints and/or neighborhood effects? Y or N:\n"]))
# If the user does not wish to redo these computations, this
# function is skipped
if remake.YesOrNo == "N":
ConstraintNeighborhood.neighborhoodSize = dfNeighborhoods["Neighborhood"][0]
ConstraintNeighborhood.ConstYesNo = "N"
ConstraintNeighborhood.NeighYesNo = "N"
pdf.multi_cell(w=0, h=5.0, align='L',
txt="\nThe constraints and neighborhood effects from the previous model run "
+'\n'+"have not been changed. A hexagonal neighborhood range of " + str(dfNeighborhoods["Neighborhood"][0])
+'\n'+"grid cell(s) is thus retained, along with the following constraints: "
+'\n'+ str(dfConstraints["Constraints"].tolist()), border=0)
return
# If constraints and neighborhood effects have not been made before
# then the remake variable defaults to "Y" (yes)
else:
ConstraintNeighborhood.remake = "Y"
pdf.multi_cell(w=0, h=5.0, align='L',
txt="\nThe constraints and neighborhood effects will be modified by the user.", border=0)
# If the geodatabase does not exist yet, then the desire to reset the
# constraints and neighborhood effects is automatically set to "N" (no)
else:
ConstraintNeighborhood.remake = "N"
# Calculation of constraints and neighborhood effects if the user
# selected CONUS
if ConstraintNeighborhood.studyArea == "CONUS":
# Filepath to the map containing predicted and actual wind farm locations,
# used to check whether a logistic regression model run for the CONUS
# has been done for at least one predictor configuration. Also acts as
# a dummy file for separating the predictors to be used as constraints
# for future wind farm sites
if os.path.exists("".join([directoryPlusSurfaces + "\Hexagon_Grid_", ConstraintNeighborhood.density, "_acres_per_MW_", ConstraintNeighborhood.capacity, "th_percentile_CONUS_Full.gdb"])) is True:
presentLocations = "".join([directoryPlusSurfaces + "\Hexagon_Grid_", ConstraintNeighborhood.density, "_acres_per_MW_", ConstraintNeighborhood.capacity, "th_percentile_CONUS_Full.gdb\Hexagon_Grid_", ConstraintNeighborhood.density, "_acres_per_MW_", ConstraintNeighborhood.capacity, "th_percentile_CONUS_Full_Map"])
elif os.path.exists("".join([directoryPlusSurfaces + "\Hexagon_Grid_", ConstraintNeighborhood.density, "_acres_per_MW_", ConstraintNeighborhood.capacity, "th_percentile_CONUS_No_Wind.gdb"])) is True:
presentLocations = "".join([directoryPlusSurfaces + "\Hexagon_Grid_", ConstraintNeighborhood.density, "_acres_per_MW_", ConstraintNeighborhood.capacity, "th_percentile_CONUS_No_Wind.gdb\Hexagon_Grid_", ConstraintNeighborhood.density, "_acres_per_MW_", ConstraintNeighborhood.capacity, "th_percentile_CONUS_No_Wind_Map"])
elif os.path.exists("".join([directoryPlusSurfaces + "\Hexagon_Grid_", ConstraintNeighborhood.density, "_acres_per_MW_", ConstraintNeighborhood.capacity, "th_percentile_CONUS_Wind_Only.gdb"])) is True:
presentLocations = "".join([directoryPlusSurfaces + "\Hexagon_Grid_", ConstraintNeighborhood.density, "_acres_per_MW_", ConstraintNeighborhood.capacity, "th_percentile_CONUS_Wind_Only.gdb\Hexagon_Grid_", ConstraintNeighborhood.density, "_acres_per_MW_", ConstraintNeighborhood.capacity, "th_percentile_CONUS_Wind_Only_Map"])
elif os.path.exists("".join([directoryPlusSurfaces + "\Hexagon_Grid_", ConstraintNeighborhood.density, "_acres_per_MW_", ConstraintNeighborhood.capacity, "th_percentile_CONUS_Reduced.gdb"])) is True:
presentLocations = "".join([directoryPlusSurfaces + "\Hexagon_Grid_", ConstraintNeighborhood.density, "_acres_per_MW_", ConstraintNeighborhood.capacity, "th_percentile_CONUS_Reduced.gdb\Hexagon_Grid_", ConstraintNeighborhood.density, "_acres_per_MW_", ConstraintNeighborhood.capacity, "th_percentile_CONUS_Reduced_Map"])
else:
print("\nA logistic regression model has not yet been fitted for the user's desired grid cell size and/or study area. The script is aborted.")
sys.exit()
# If the geodatabase for this particular study region and resolution exists,
# its contents are emptied first
if os.path.exists("".join([directoryPlusConstraintsAndNeighborhoods + "\Hexagon_Grid_", ConstraintNeighborhood.density, "_acres_per_MW_", ConstraintNeighborhood.capacity, "th_percentile_CONUS.gdb"])) is True:
arcpy.Delete_management("".join([directoryPlusConstraintsAndNeighborhoods + "\Hexagon_Grid_", ConstraintNeighborhood.density, "_acres_per_MW_", ConstraintNeighborhood.capacity, "th_percentile_CONUS.gdb\Hexagon_Grid_", ConstraintNeighborhood.density, "_acres_per_MW_", ConstraintNeighborhood.capacity, "th_percentile_CONUS_Constraints_Neighborhoods"]))
arcpy.Delete_management("".join([directoryPlusConstraintsAndNeighborhoods + "\Hexagon_Grid_", ConstraintNeighborhood.density, "_acres_per_MW_", ConstraintNeighborhood.capacity, "th_percentile_CONUS.gdb\Attribute_Table"]))
# If the geodatabase does not exist yet, it is made
# NOTE: Make sure a folder called "Constraints_and_Neighborhood_Effects"
# has been created in the directory before executing the model.
else:
arcpy.CreateFileGDB_management(directoryPlusConstraintsAndNeighborhoods, "".join(["Hexagon_Grid_", ConstraintNeighborhood.density, "_acres_per_MW_", ConstraintNeighborhood.capacity, "th_percentile_CONUS.gdb"]))
# A copy of the predicted and actual wind farm locations is added
# to the geodatabase
presentCopy = "".join([directoryPlusConstraintsAndNeighborhoods + "\Hexagon_Grid_", ConstraintNeighborhood.density, "_acres_per_MW_", ConstraintNeighborhood.capacity, "th_percentile_CONUS.gdb\Hexagon_Grid_", ConstraintNeighborhood.density, "_acres_per_MW_", ConstraintNeighborhood.capacity, "th_percentile_CONUS_Constraints_Neighborhoods_Interim"])
arcpy.Copy_management(presentLocations, presentCopy)
# The filepath to the aggregated data that constrain
# wind farm development is set
constraints = "".join([directory, "/", studyRegion.region, "_Gridded_Surfaces/Hexagon_Grid_", ConstraintNeighborhood.density, "_acres_per_MW_", ConstraintNeighborhood.capacity, "th_percentile_CONUS_Merged.gdb\Hexagon_Grid_", ConstraintNeighborhood.density, "_acres_per_MW_", ConstraintNeighborhood.capacity, "th_percentile_CONUS_Merged"])
# The predicted wind farm locations and aggregated data must be saved
# into the same gridded surface using a spatial join. The following
# is the filepath to the constructed gridded surface
preparedSurface = "".join([directoryPlusConstraintsAndNeighborhoods + "\Hexagon_Grid_", ConstraintNeighborhood.density, "_acres_per_MW_", ConstraintNeighborhood.capacity, "th_percentile_CONUS.gdb\Hexagon_Grid_", ConstraintNeighborhood.density, "_acres_per_MW_", ConstraintNeighborhood.capacity, "th_percentile_CONUS_Constraints_Neighborhoods"])
# Field mapping is used to select the desired fields prior
# to the spatial join
fm = arcpy.FieldMappings()
fm.addTable(presentCopy)
fm.addTable(constraints)
keepers = [# The fields acting as constraints
"Avg_Elevat","Avg_Temp","Avg_Wind","Critical","Historical",
"Military","Mining","Nat_Parks","Near_Air","Near_Hosp",
"Near_Plant","Near_Roads","Near_Sch","Near_Trans",
"Trib_Land","Wild_Refug",
# All other fields
"Avg_25","Bat_Count","Bird_Count","Cost_15_19","Dem_Wins",
"Dens_15_19","Farm_15_19","Farm_Year","Fem_15_19","Foss_Lobbs",
"Gree_Lobbs","Hisp_15_19","In_Tax_Cre","Interconn","ISO_YN",
"Net_Meter","Numb_Incen","Numb_Pols","Plant_Year","Prop_15_19",
"Prop_Rugg","Renew_Port","Renew_Targ","Rep_Wins","supp_2018",
"Tax_Prop","Tax_Sale","Type_15_19","Undev_Land","Unem_15_19",
"Whit_15_19",
# The wind turbine location field
"Wind_Turb"]
for field in fm.fields:
if field.name not in keepers:
fm.removeFieldMap(fm.findFieldMapIndex(field.name))
# The spatial join is performed
arcpy.SpatialJoin_analysis(presentCopy,constraints,preparedSurface,field_mapping = (fm),match_option = "HAVE_THEIR_CENTER_IN")
# The temporary files made can now be deleted
arcpy.Delete_management("".join([directoryPlusConstraintsAndNeighborhoods + "\Hexagon_Grid_", ConstraintNeighborhood.density, "_acres_per_MW_", ConstraintNeighborhood.capacity, "th_percentile_CONUS.gdb\Hexagon_Grid_", ConstraintNeighborhood.density, "_acres_per_MW_", ConstraintNeighborhood.capacity, "th_percentile_CONUS_Constraints_Neighborhoods_Interim"]))
# Preparation of the gridded surface if the user did not select CONUS.
# It was not possible to perform logistic regression in states containing
# fewer than two grid cells with commercial wind farms in them, due to
# the model's training and testing requiring grid cells in both classes
# of the dependent variable. Computation of constraints and neighborhood
# effects for these states must be done by clipping predicted wind farm
# locations out of the CONUS outputs.
elif (ConstraintNeighborhood.studyArea == "Alabama" or ConstraintNeighborhood.studyArea == "Arkansas" or ConstraintNeighborhood.studyArea == "Connecticut"
or ConstraintNeighborhood.studyArea == "Delaware" or ConstraintNeighborhood.studyArea == "Florida" or ConstraintNeighborhood.studyArea == "Georgia"
or ConstraintNeighborhood.studyArea == "Kentucky" or ConstraintNeighborhood.studyArea == "Louisiana" or ConstraintNeighborhood.studyArea == "Mississippi"
or ConstraintNeighborhood.studyArea == "New_Jersey" or ConstraintNeighborhood.studyArea == "Rhode_Island" or ConstraintNeighborhood.studyArea == "South_Carolina"
or ConstraintNeighborhood.studyArea == "Tennessee" or ConstraintNeighborhood.studyArea == "Virginia"):
# Filepath to the map containing predicted and actual wind farm locations,
# used to check whether a logistic regression model run for the CONUS
# has been done for at least one predictor configuration. Also acts as
# a dummy file for separating the predictors to be used as constraints
# for future wind farm sites
if os.path.exists("".join([directoryPlusSurfaces + "\Hexagon_Grid_85_acres_per_MW_100th_percentile_CONUS_Full.gdb"])) is True:
presentLocations = "".join([directoryPlusSurfaces + "\Hexagon_Grid_", ConstraintNeighborhood.density, "_acres_per_MW_", ConstraintNeighborhood.capacity, "th_percentile_CONUS_Full.gdb\Hexagon_Grid_", ConstraintNeighborhood.density, "_acres_per_MW_", ConstraintNeighborhood.capacity, "th_percentile_CONUS_Full_Map"])
elif os.path.exists("".join([directoryPlusSurfaces + "\Hexagon_Grid_", ConstraintNeighborhood.density, "_acres_per_MW_", ConstraintNeighborhood.capacity, "th_percentile_CONUS_No_Wind.gdb"])) is True:
presentLocations = "".join([directoryPlusSurfaces + "\Hexagon_Grid_", ConstraintNeighborhood.density, "_acres_per_MW_", ConstraintNeighborhood.capacity, "th_percentile_CONUS_No_Wind.gdb\Hexagon_Grid_", ConstraintNeighborhood.density, "_acres_per_MW_", ConstraintNeighborhood.capacity, "th_percentile_CONUS_No_Wind_Map"])
elif os.path.exists("".join([directoryPlusSurfaces + "\Hexagon_Grid_", ConstraintNeighborhood.density, "_acres_per_MW_", ConstraintNeighborhood.capacity, "th_percentile_CONUS_Wind_Only.gdb"])) is True:
presentLocations = "".join([directoryPlusSurfaces + "\Hexagon_Grid_", ConstraintNeighborhood.density, "_acres_per_MW_", ConstraintNeighborhood.capacity, "th_percentile_CONUS_Wind_Only.gdb\Hexagon_Grid_", ConstraintNeighborhood.density, "_acres_per_MW_", ConstraintNeighborhood.capacity, "th_percentile_CONUS_Wind_Only_Map"])
elif os.path.exists("".join([directoryPlusSurfaces + "\Hexagon_Grid_", ConstraintNeighborhood.density, "_acres_per_MW_", ConstraintNeighborhood.capacity, "th_percentile_CONUS_Reduced.gdb"])) is True:
presentLocations = "".join([directoryPlusSurfaces + "\Hexagon_Grid_", ConstraintNeighborhood.density, "_acres_per_MW_", ConstraintNeighborhood.capacity, "th_percentile_CONUS_Reduced.gdb\Hexagon_Grid_", ConstraintNeighborhood.density, "_acres_per_MW_", ConstraintNeighborhood.capacity, "th_percentile_CONUS_Reduced_Map"])
else:
print("\nA logistic regression model has not yet been fitted for the user's desired grid cell size and/or study area. The script is aborted.")
sys.exit()
# If the geodatabase for this particular study region and resolution exists,
# its contents are emptied first
if os.path.exists("".join([directoryPlusConstraintsAndNeighborhoods + "\Hexagon_Grid_", ConstraintNeighborhood.density, "_acres_per_MW_", ConstraintNeighborhood.capacity, "th_percentile_", ConstraintNeighborhood.studyArea, ".gdb"])) is True:
arcpy.Delete_management("".join([directoryPlusConstraintsAndNeighborhoods + "\Hexagon_Grid_", ConstraintNeighborhood.density, "_acres_per_MW_", ConstraintNeighborhood.capacity, "th_percentile_", ConstraintNeighborhood.studyArea, ".gdb\Hexagon_Grid_", ConstraintNeighborhood.density, "_acres_per_MW_", ConstraintNeighborhood.capacity, "th_percentile_", ConstraintNeighborhood.studyArea, "_Constraints_Neighborhoods"]))
arcpy.Delete_management("".join([directoryPlusConstraintsAndNeighborhoods + "\Hexagon_Grid_", ConstraintNeighborhood.density, "_acres_per_MW_", ConstraintNeighborhood.capacity, "th_percentile_", ConstraintNeighborhood.studyArea, ".gdb\Attribute_Table"]))
# If the geodatabase does not exist yet, it is made
# NOTE: Make sure a folder called "Constraints_and_Neighborhood_Effects"
# has been created in the directory before executing the model.
else:
arcpy.CreateFileGDB_management(directoryPlusConstraintsAndNeighborhoods, "".join(["Hexagon_Grid_", ConstraintNeighborhood.density, "_acres_per_MW_", ConstraintNeighborhood.capacity, "th_percentile_", ConstraintNeighborhood.studyArea, ".gdb"]))
# A copy of the predicted and actual wind farm locations is added
# to the geodatabase. Since state-level runs of the logistic regression
# model were not possible for these states, wind farm locations
# are derived from the CONUS as a whole
presentCopy = "".join([directoryPlusConstraintsAndNeighborhoods + "\Hexagon_Grid_", ConstraintNeighborhood.density, "_acres_per_MW_", ConstraintNeighborhood.capacity, "th_percentile_", ConstraintNeighborhood.studyArea, ".gdb\Hexagon_Grid_", ConstraintNeighborhood.density, "_acres_per_MW_", ConstraintNeighborhood.capacity, "th_percentile_", ConstraintNeighborhood.studyArea, "_Constraints_Neighborhoods_Interim"])
arcpy.Copy_management(presentLocations, presentCopy)
# The grid cells over the CONUS are transformed into points
pointsCONUS = "".join([directoryPlusConstraintsAndNeighborhoods + "\Hexagon_Grid_", ConstraintNeighborhood.density, "_acres_per_MW_", ConstraintNeighborhood.capacity, "th_percentile_", ConstraintNeighborhood.studyArea, ".gdb\Hexagon_Grid_", ConstraintNeighborhood.density, "_acres_per_MW_", ConstraintNeighborhood.capacity, "th_percentile_", ConstraintNeighborhood.studyArea, "_Constraints_Neighborhoods_Points"])
arcpy.FeatureToPoint_management(presentCopy,pointsCONUS)
# State border used to clip the CONUS points
stateBorder = "".join([directory + "\State_Borders/", ConstraintNeighborhood.studyArea, "/", ConstraintNeighborhood.studyArea])
# Filepath for the clipped points, a copy of the pointsCONUS variable
pointsCONUSClipped = "".join([directoryPlusConstraintsAndNeighborhoods + "\Hexagon_Grid_", ConstraintNeighborhood.density, "_acres_per_MW_", ConstraintNeighborhood.capacity, "th_percentile_", ConstraintNeighborhood.studyArea, ".gdb\Hexagon_Grid_", ConstraintNeighborhood.density, "_acres_per_MW_", ConstraintNeighborhood.capacity, "th_percentile_", ConstraintNeighborhood.studyArea, "_Constraints_Neighborhoods_Points_Clipped"])
# Grid cells are clipped
arcpy.Clip_analysis(pointsCONUS,stateBorder,pointsCONUSClipped)
# Hexagonal grid cells whose centroids are within the state border can
# now be identified with a feature layer selection. If the feature
# layers exist from a prior run of the script, they are deleted
arcpy.Delete_management(["present_Copy_lyr","points_CONUS_Clipped_lyr"])
arcpy.MakeFeatureLayer_management(presentCopy, "present_Copy_lyr")
arcpy.MakeFeatureLayer_management(pointsCONUSClipped, "points_CONUS_Clipped_lyr")
stateCells = arcpy.SelectLayerByLocation_management("present_Copy_lyr", "HAVE_THEIR_CENTER_IN", "points_CONUS_Clipped_lyr")
# Selected features are saved for continued use
selectedCells = arcpy.FeatureClassToFeatureClass_conversion(stateCells, "".join([directoryPlusConstraintsAndNeighborhoods + "\Hexagon_Grid_", ConstraintNeighborhood.density, "_acres_per_MW_", ConstraintNeighborhood.capacity, "th_percentile_", ConstraintNeighborhood.studyArea, ".gdb"]), "".join(["Hexagon_Grid_", ConstraintNeighborhood.density, "_acres_per_MW_", ConstraintNeighborhood.capacity, "th_percentile_", ConstraintNeighborhood.studyArea, "_Constraints_Neighborhoods_Selected"]))
# The filepath to the aggregated data that constrain
# wind farm development is set. Note that the CONUS constraints
# must still be used, rather than for the individual state
constraints = "".join([directory + "/CONUS_Gridded_Surfaces/Hexagon_Grid_", ConstraintNeighborhood.density, "_acres_per_MW_", ConstraintNeighborhood.capacity, "th_percentile_CONUS_Merged.gdb\Hexagon_Grid_", ConstraintNeighborhood.density, "_acres_per_MW_", ConstraintNeighborhood.capacity, "th_percentile_CONUS_Merged"])
# PresentCopy variable is reassigned
presentCopy = selectedCells
# The predicted wind farm locations and aggregated data must be saved
# into the same gridded surface using a spatial join. The following
# is the filepath to the constructed gridded surface
preparedSurface = "".join([directoryPlusConstraintsAndNeighborhoods + "\Hexagon_Grid_", ConstraintNeighborhood.density, "_acres_per_MW_", ConstraintNeighborhood.capacity, "th_percentile_", ConstraintNeighborhood.studyArea, ".gdb\Hexagon_Grid_", ConstraintNeighborhood.density, "_acres_per_MW_", ConstraintNeighborhood.capacity, "th_percentile_", ConstraintNeighborhood.studyArea, "_Constraints_Neighborhoods"])
# Field mapping is used to select the desired fields prior
# to the spatial join
fm = arcpy.FieldMappings()
fm.addTable(presentCopy)
fm.addTable(constraints)
keepers = [# The fields acting as constraints
"Avg_Elevat","Avg_Temp","Avg_Wind","Critical","Historical",
"Military","Mining","Nat_Parks","Near_Air","Near_Hosp",
"Near_Plant","Near_Roads","Near_Sch","Near_Trans",
"Trib_Land","Wild_Refug",
# All other fields
"Avg_25","Bat_Count","Bird_Count","Cost_15_19","Dem_Wins",
"Dens_15_19","Farm_15_19","Farm_Year","Fem_15_19","Foss_Lobbs",
"Gree_Lobbs","Hisp_15_19","In_Tax_Cre","Interconn","ISO_YN",
"Net_Meter","Numb_Incen","Numb_Pols","Plant_Year","Prop_15_19",
"Prop_Rugg","Renew_Port","Renew_Targ","Rep_Wins","supp_2018",
"Tax_Prop","Tax_Sale","Type_15_19","Undev_Land","Unem_15_19",
"Whit_15_19",
# The wind turbine location field
"Wind_Turb"]
for field in fm.fields:
if field.name not in keepers:
fm.removeFieldMap(fm.findFieldMapIndex(field.name))
# The spatial join is performed
arcpy.SpatialJoin_analysis(presentCopy,constraints,preparedSurface,field_mapping = (fm),match_option = "HAVE_THEIR_CENTER_IN")
# The temporary files made can now be deleted
arcpy.Delete_management("".join([directoryPlusConstraintsAndNeighborhoods + "\Hexagon_Grid_", ConstraintNeighborhood.density, "_acres_per_MW_", ConstraintNeighborhood.capacity, "th_percentile_CONUS.gdb\Hexagon_Grid_", ConstraintNeighborhood.density, "_acres_per_MW_", ConstraintNeighborhood.capacity, "th_percentile_CONUS_Constraints_Neighborhoods_Interim"]))
arcpy.Delete_management("".join([directoryPlusConstraintsAndNeighborhoods + "\Hexagon_Grid_", ConstraintNeighborhood.density, "_acres_per_MW_", ConstraintNeighborhood.capacity, "th_percentile_CONUS.gdb\Hexagon_Grid_", ConstraintNeighborhood.density, "_acres_per_MW_", ConstraintNeighborhood.capacity, "th_percentile_CONUS_Constraints_Neighborhoods_Points"]))
arcpy.Delete_management("".join([directoryPlusConstraintsAndNeighborhoods + "\Hexagon_Grid_", ConstraintNeighborhood.density, "_acres_per_MW_", ConstraintNeighborhood.capacity, "th_percentile_", ConstraintNeighborhood.studyArea, ".gdb\Hexagon_Grid_", ConstraintNeighborhood.density, "_acres_per_MW_", ConstraintNeighborhood.capacity, "th_percentile_", ConstraintNeighborhood.studyArea, "_Constraints_Neighborhoods_Points_Clipped"]))
arcpy.Delete_management("".join([directoryPlusConstraintsAndNeighborhoods + "\Hexagon_Grid_", ConstraintNeighborhood.density, "_acres_per_MW_", ConstraintNeighborhood.capacity, "th_percentile_", ConstraintNeighborhood.studyArea, ".gdb\Hexagon_Grid_", ConstraintNeighborhood.density, "_acres_per_MW_", ConstraintNeighborhood.capacity, "th_percentile_", ConstraintNeighborhood.studyArea, "_Constraints_Neighborhoods_Selected"]))
# For all other states, it is possible to use the predicted wind farm locations
# produced for them individually
else:
# Filepath to the map containing predicted and actual wind farm locations,
# used to check whether a logistic regression model run for the state
# has been done for at least one predictor configuration. Also acts as
# a dummy file for separating the predictors to be used as constraints
# for future wind farm sites
if os.path.exists("".join([directoryPlusSurfaces + "\Hexagon_Grid_", ConstraintNeighborhood.density, "_acres_per_MW_", ConstraintNeighborhood.capacity, "th_percentile_", ConstraintNeighborhood.studyArea, "_Full.gdb"])) is True:
presentLocations = "".join([directoryPlusSurfaces + "\Hexagon_Grid_", ConstraintNeighborhood.density, "_acres_per_MW_", ConstraintNeighborhood.capacity, "th_percentile_", ConstraintNeighborhood.studyArea, "_Full.gdb\Hexagon_Grid_", ConstraintNeighborhood.density, "_acres_per_MW_", ConstraintNeighborhood.capacity, "th_percentile_", ConstraintNeighborhood.studyArea, "_Full_Map"])
elif os.path.exists("".join([directoryPlusSurfaces + "\Hexagon_Grid_", ConstraintNeighborhood.density, "_acres_per_MW_", ConstraintNeighborhood.capacity, "th_percentile_", ConstraintNeighborhood.studyArea, "_No_Wind.gdb"])) is True:
presentLocations = "".join([directoryPlusSurfaces + "\Hexagon_Grid_", ConstraintNeighborhood.density, "_acres_per_MW_", ConstraintNeighborhood.capacity, "th_percentile_", ConstraintNeighborhood.studyArea, "_No_Wind.gdb\Hexagon_Grid_", ConstraintNeighborhood.density, "_acres_per_MW_", ConstraintNeighborhood.capacity, "th_percentile_", ConstraintNeighborhood.studyArea, "_No_Wind_Map"])
elif os.path.exists("".join([directoryPlusSurfaces + "\Hexagon_Grid_", ConstraintNeighborhood.density, "_acres_per_MW_", ConstraintNeighborhood.capacity, "th_percentile_", ConstraintNeighborhood.studyArea, "_Wind_Only.gdb"])) is True:
presentLocations = "".join([directoryPlusSurfaces + "\Hexagon_Grid_", ConstraintNeighborhood.density, "_acres_per_MW_", ConstraintNeighborhood.capacity, "th_percentile_", ConstraintNeighborhood.studyArea, "_Wind_Only.gdb\Hexagon_Grid_", ConstraintNeighborhood.density, "_acres_per_MW_", ConstraintNeighborhood.capacity, "th_percentile_", ConstraintNeighborhood.studyArea, "_Wind_Only_Map"])
elif os.path.exists("".join([directoryPlusSurfaces + "\Hexagon_Grid_", ConstraintNeighborhood.density, "_acres_per_MW_", ConstraintNeighborhood.capacity, "th_percentile_", ConstraintNeighborhood.studyArea, "_Reduced.gdb"])) is True:
presentLocations = "".join([directoryPlusSurfaces + "\Hexagon_Grid_", ConstraintNeighborhood.density, "_acres_per_MW_", ConstraintNeighborhood.capacity, "th_percentile_", ConstraintNeighborhood.studyArea, "_Reduced.gdb\Hexagon_Grid_", ConstraintNeighborhood.density, "_acres_per_MW_", ConstraintNeighborhood.capacity, "th_percentile_", ConstraintNeighborhood.studyArea, "_Reduced_Map"])
else:
print("\nA logistic regression model has not yet been fitted for the user's desired grid cell size and/or study area. The script is aborted.")
sys.exit()
# If the geodatabase for this particular study region and resolution exists,
# its contents are emptied first
if os.path.exists("".join([directoryPlusConstraintsAndNeighborhoods + "\Hexagon_Grid_", ConstraintNeighborhood.density, "_acres_per_MW_", ConstraintNeighborhood.capacity, "th_percentile_", ConstraintNeighborhood.studyArea, ".gdb"])) is True:
arcpy.Delete_management("".join([directoryPlusConstraintsAndNeighborhoods + "\Hexagon_Grid_", ConstraintNeighborhood.density, "_acres_per_MW_", ConstraintNeighborhood.capacity, "th_percentile_", ConstraintNeighborhood.studyArea, ".gdb\Hexagon_Grid_", ConstraintNeighborhood.density, "_acres_per_MW_", ConstraintNeighborhood.capacity, "th_percentile_", ConstraintNeighborhood.studyArea, "_Constraints_Neighborhoods"]))
arcpy.Delete_management("".join([directoryPlusConstraintsAndNeighborhoods + "\Hexagon_Grid_", ConstraintNeighborhood.density, "_acres_per_MW_", ConstraintNeighborhood.capacity, "th_percentile_", ConstraintNeighborhood.studyArea, ".gdb\Attribute_Table"]))
# If the geodatabase does not exist yet, it is made
# NOTE: Make sure a folder called "Constraints_and_Neighborhood_Effects"
# has been created in the directory before executing the model.
else:
arcpy.CreateFileGDB_management(directoryPlusConstraintsAndNeighborhoods, "".join(["Hexagon_Grid_", ConstraintNeighborhood.density, "_acres_per_MW_", ConstraintNeighborhood.capacity, "th_percentile_", ConstraintNeighborhood.studyArea, ".gdb"]))
# A copy of the predicted and actual wind farm locations is added to the
# same folder containing the aggregated data, for their combination
presentCopy = "".join([directoryPlusConstraintsAndNeighborhoods + "\Hexagon_Grid_", ConstraintNeighborhood.density, "_acres_per_MW_", ConstraintNeighborhood.capacity, "th_percentile_", ConstraintNeighborhood.studyArea, ".gdb\Hexagon_Grid_", ConstraintNeighborhood.density, "_acres_per_MW_", ConstraintNeighborhood.capacity, "th_percentile_", ConstraintNeighborhood.studyArea, "_Constraints_Neighborhoods_Interim"])
arcpy.Copy_management(presentLocations, presentCopy)
# The filepath to the aggregated data that constrain
# wind farm development is set
constraints = "".join([directory + "/" + studyRegion.region, "_Gridded_Surfaces/Hexagon_Grid_", ConstraintNeighborhood.density, "_acres_per_MW_", ConstraintNeighborhood.capacity, "th_percentile_", ConstraintNeighborhood.studyArea, "_Merged.gdb\Hexagon_Grid_", ConstraintNeighborhood.density, "_acres_per_MW_", ConstraintNeighborhood.capacity, "th_percentile_", ConstraintNeighborhood.studyArea, "_Merged"])
# The predicted wind farm locations and aggregated data must be saved
# into the same gridded surface using a spatial join. The following
# is the filepath to the constructed gridded surface
preparedSurface = "".join([directoryPlusConstraintsAndNeighborhoods + "\Hexagon_Grid_", ConstraintNeighborhood.density, "_acres_per_MW_", ConstraintNeighborhood.capacity, "th_percentile_", ConstraintNeighborhood.studyArea, ".gdb\Hexagon_Grid_", ConstraintNeighborhood.density, "_acres_per_MW_", ConstraintNeighborhood.capacity, "th_percentile_", ConstraintNeighborhood.studyArea, "_Constraints_Neighborhoods"])
# Field mapping is used to select the desired fields prior
# to the spatial join
fm = arcpy.FieldMappings()
fm.addTable(presentCopy)
fm.addTable(constraints)
keepers = [# The fields acting as constraints
"Avg_Elevat","Avg_Temp","Avg_Wind","Critical","Historical",
"Military","Mining","Nat_Parks","Near_Air","Near_Hosp",
"Near_Plant","Near_Roads","Near_Sch","Near_Trans",
"Trib_Land","Wild_Refug",
# All other fields
"Avg_25","Bat_Count","Bird_Count","Dem_Wins","Dens_15_19",
"Farm_Year","Fem_15_19","Hisp_15_19","ISO_YN","Plant_Year",
"Prop_Rugg","supp_2018","Type_15_19","Undev_Land",
"Unem_15_19","Whit_15_19",
# The wind turbine location field
"Wind_Turb"]
for field in fm.fields:
if field.name not in keepers:
fm.removeFieldMap(fm.findFieldMapIndex(field.name))
# The spatial join is performed
arcpy.SpatialJoin_analysis(presentCopy,constraints,preparedSurface,field_mapping = (fm),match_option = "HAVE_THEIR_CENTER_IN")
# The temporary files made can now be deleted
arcpy.Delete_management("".join([directoryPlusConstraintsAndNeighborhoods + "\Hexagon_Grid_", ConstraintNeighborhood.density, "_acres_per_MW_", ConstraintNeighborhood.capacity, "th_percentile_", ConstraintNeighborhood.studyArea, ".gdb\Hexagon_Grid_", ConstraintNeighborhood.density, "_acres_per_MW_", ConstraintNeighborhood.capacity, "th_percentile_", ConstraintNeighborhood.studyArea, "_Constraints_Neighborhoods_Interim"]))
######################## SETTING OF MODEL CONSTRAINTS #########################
# The following are the cellular automaton's default constraints for
# projection of future wind farm sites:
# No wind farms within 2500 meters of an airport.
# No wind farms within 10000 meters of an existing power plant.
# No wind farms within 2500 meters of a hospital or school.
# No wind farms within 500 meters of, or more than 10000 meters away from, a major road.
# No wind farms within 250 meters of, or more than 10000 meters away from, a major transmission line.
# No wind farms in grid cells that average more than 2000 meters above sea level.
# No wind farms in grid cells with an average wind speed 80 meters above ground less than 4 meters per second.
# No wind farms in grid cells with an average temperature below 0 degrees Celsius.
# Wind farm development is prohibited in grid cells shared by
# military bases, national parks, USFWS critical species habitats,
# historical landmarks, mining operations, USFWS wildlife refuges,
# and tribal lands.
# Many wind farms already exist in grid cells that would be prohibited from
# development based on the default constraints. The number of grid cells
# that contain wind farms that violate these constraints is counted
def constraints(airportProhibited, plantProhibited, hospProhibited, schProhibited,
roadMinProhibited, roadMaxProhibited, transMinProhibited,
transMaxProhibited, elevatProhibited, windProhibited,
tempProhibited, militProhibited, natParkProhibited,
criticalProhibited, historicProhibited, miningProhibited,
wildProhibited, tribalProhibited, windFarmCount,
allCellsProhibited, turbineCellsProhibited,
allCellsAllowed, turbineCellsAllowed):
# The user is first asked if they wish to switch off the constraints,
# meaning that nearby grid cells can gain wind farms even if the
# default constraints are violated
def constraintYesNo(values,message):
while True:
x = input(message)
if x in values:
constraintYesNo.YesOrNo = x
break
else:
print("Invalid value; options are " + str(values))
constraintYesNo(["Y","N"], "\nWould you like to switch off the constraints? Y or N:\n")
# If the user switches constraints off, this entire function is skipped
if constraintYesNo.YesOrNo == "Y":
# The user input is assigned to a global variable for later use
ConstraintNeighborhood.ConstYesNo = "Y"
# The total number of grid cells needs to be counted up, even when
# not setting any constraints
def totalCells(total):
cursor = SearchCursor(preparedSurface, "Wind_Turb")
for row in cursor:
total += 1
totalCells.total = total
# The function is called
totalCells(0)
# The total constrained grid cells are saved as a global variable
constraints.total = totalCells.total
return
# Otherwise, the constraint setting works as before
else:
ConstraintNeighborhood.ConstYesNo = "N"
# A cursor to sum these counts of prohibited grid cells is defined
cursor = SearchCursor(preparedSurface,["Wind_Turb","Near_Air","Near_Plant","Near_Hosp",
"Near_Sch","Near_Roads","Near_Trans","Avg_Elevat",
"Avg_Wind","Avg_Temp","Military","Nat_Parks",
"Critical","Historical","Mining","Wild_Refug",
"Trib_Land"])
# If a grid cell contains a wind farm and also invalidates a constraint,
# the count for that constraint is increased by 1
for row in cursor:
# Default nearest airport constraint
if row[1] <= 2500:
if row[0] == "Y":
airportProhibited += 1
# Default nearest power plant constraint
if row[2] <= 10000:
if row[0] == "Y":
plantProhibited += 1
# Default nearest hospital constraint
if row[3] <= 2500:
if row[0] == "Y":
hospProhibited += 1
# Default nearest school constraint
if row[4] <= 2500:
if row[0] == "Y":
schProhibited += 1
# Default minimum road distance constraint
if row[5] <= 500:
if row[0] == "Y":
roadMinProhibited += 1
# Default maximum road distance constraint
if row[5] >= 10000:
if row[0] == "Y":
roadMaxProhibited += 1
# Default minimum transmission line distance constraint
if row[6] <= 250:
if row[0] == "Y":
transMinProhibited += 1
# Default maximum transmission line distance constraint
if row[6] >= 10000:
if row[0] == "Y":
transMaxProhibited += 1
# Default average elevation constraint
if row[7] >= 2000:
if row[0] == "Y":
elevatProhibited += 1
# Default average wind speed constraint
if row[8] <= 4:
if row[0] == "Y":
windProhibited += 1
# Default average temperature constraint
if row[9] <= 0:
if row[0] == "Y":
tempProhibited += 1
# Default military constraint
if row[10] == "Y":
if row[0] == "Y":
militProhibited += 1
# Default national parks constraint
if row[11] == "Y":
if row[0] == "Y":
natParkProhibited += 1
# Default critical habitats constraint
if row[12] == "Y":
if row[0] == "Y":
criticalProhibited += 1
# Default historical landmarks constraint
if row[13] == "Y":
if row[0] == "Y":
historicProhibited += 1
# Default mining constraint
if row[14] == "Y":
if row[0] == "Y":
miningProhibited += 1
#Default wildlife refuge constraint
if row[15] == "Y":
if row[0] == "Y" or row[0] == "Y":
wildProhibited += 1
# Default tribal land constraint
if row[16] == "Y":
if row[0] == "Y" or row[0] == "Y":
tribalProhibited += 1
# Count of the number of grid cells containing wind farms
if row[0] == "Y":
windFarmCount += 1
# Total count of the number of grid cells containing wind farms that
# do and do not violate the default constraints
if (row[1] <= 2500 or row[2] <= 10000 or row[3] <= 2500 or row[4] <= 2500 or row[5] <= 500 or row[5] >= 10000
or row[6] <= 250 or row[6] >= 10000 or row[7] >= 2000 or row[8] <= 4 or row[9] <= 0 or row[10] == "Y"
or row[11] == "Y" or row[12] == "Y" or row[13] == "Y" or row[14] == "Y" or row[15] == "Y" or row[16] == "Y"):
# Count of grid cells that invalidate at least one constraint
allCellsProhibited += 1
# Count of grid cells that contain a wind farm that invalidate
# at least one constraint
if row[0] == "Y":
turbineCellsProhibited += 1
if (row[1] > 2500 and row[2] > 10000 and row[3] > 2500 and row[4] > 2500 and row[5] > 500 and row[5] < 10000
and row[6] > 250 and row[6] < 10000 and row[7] < 2000 and row[8] > 4 and row[9] > 0 and row[10] == "N"
and row[11] == "N" and row[12] == "N" and row[13] == "N" and row[14] == "N" and row[15] == "N" and row[16] == "N"):
# Count of grid cells that invalidate no constraints
allCellsAllowed += 1
# Count of grid cells that contain a wind farm that invalidate
# no constraints
if row[0] == "Y":
turbineCellsAllowed += 1
# Counts are assigned for use in print statements below
constraints.airport = airportProhibited
constraints.plant = plantProhibited
constraints.hospital = hospProhibited
constraints.school = schProhibited
constraints.roadMin = roadMinProhibited
constraints.roadMax = roadMaxProhibited
constraints.transMin = transMinProhibited
constraints.transMax = transMaxProhibited
constraints.elevation = elevatProhibited
constraints.wind = windProhibited
constraints.temp = tempProhibited
constraints.milit = militProhibited
constraints.natPark = natParkProhibited
constraints.critical = criticalProhibited
constraints.historic = historicProhibited
constraints.mining = miningProhibited
constraints.wild = wildProhibited
constraints.tribal = tribalProhibited
constraints.count = windFarmCount
constraints.allProhibited = allCellsProhibited
constraints.turbinesProhibited = turbineCellsProhibited
constraints.allAllowed = allCellsAllowed
constraints.turbinesAllowed = turbineCellsAllowed
# Printouts should only be done for states that contain at least one wind farm
if constraints.count > 0:
# The number and percentage of existing grid cells containing wind farms that
# violate the default constraints are printed out
print("\nNumber (percentage) of grid cells that contain wind farms but violate the default constraints:")
print("".join(["- Wind farms within 2,500 meters of an airport: ", str(constraints.airport), " (", str(round(constraints.airport/constraints.count*100,2)), "%)"]))
print("".join(["- Wind farms within 10,000 meters of a power plant: ", str(constraints.plant), " (", str(round(constraints.plant/constraints.count*100,2)), "%)"]))
print("".join(["- Wind farms within 2,500 meters of a hospital: ", str(constraints.hospital), " (", str(round(constraints.hospital/constraints.count*100,2)), "%)"]))
print("".join(["- Wind farms within 2,500 meters of a school: ", str(constraints.school), " (", str(round(constraints.school/constraints.count*100,2)), "%)"]))
print("".join(["- Wind farms within 500 meters of a major road: ", str(constraints.roadMin), " (", str(round(constraints.roadMin/constraints.count*100,2)), "%)"]))
print("".join(["- Wind farms more than 10,000 meters from a major road: ", str(constraints.roadMax), " (", str(round(constraints.roadMax/constraints.count*100,2)), "%)"]))
print("".join(["- Wind farms within 250 meters of a major transmission line: ", str(constraints.transMin), " (", str(round(constraints.transMin/constraints.count*100,2)), "%)"]))
print("".join(["- Wind farms more than 10,000 meters from a major transmission line: ", str(constraints.transMax), " (", str(round(constraints.transMax/constraints.count*100,2)), "%)"]))
print("".join(["- Wind farms in grid cells that average more than 2,000 meters above sea level: ", str(constraints.elevation), " (", str(round(constraints.elevation/constraints.count*100,2)), "%)"]))
print("".join(["- Wind farms in grid cells with an average wind speed 80 meters above ground less than 5 meters per second: ", str(constraints.wind), " (", str(round(constraints.wind/constraints.count*100,2)), "%)"]))
print("".join(["- Wind farms in grid cells with an average temperature below 0 degrees Celsius: ", str(constraints.temp), " (", str(round(constraints.temp/constraints.count*100,2)), "%)"]))
print("".join(["- Wind farms in the presence of a military base or operations: ", str(constraints.milit), " (", str(round(constraints.milit/constraints.count*100,2)), "%)"]))
print("".join(["- Wind farms in the presence of a National Park: ", str(constraints.natPark), " (", str(round(constraints.natPark/constraints.count*100,2)), "%)"]))
print("".join(["- Wind farms in the presence of a USFWS critical species habitat: ", str(constraints.critical), " (", str(round(constraints.critical/constraints.count*100,2)), "%)"]))
print("".join(["- Wind farms in the presence of a historical landmark: ", str(constraints.historic), " (", str(round(constraints.historic/constraints.count*100,2)), "%)"]))
print("".join(["- Wind farms in the presence of mining operations: ", str(constraints.mining), " (", str(round(constraints.mining/constraints.count*100,2)), "%)"]))
print("".join(["- Wind farms in the presence of a USFWS wildlife refuge: ", str(constraints.wild), " (", str(round(constraints.wild/constraints.count*100,2)), "%)"]))
print("".join(["- Wind farms in the presence of tribal land: ", str(constraints.tribal), " (", str(round(constraints.tribal/constraints.count*100,2)), "%)"]))
# These numbers and percentages are also represented cumulatively, since
# grid cells containing wind farms may violate more than one constraint
print("".join(["\nNumber of grid cells containing wind farms in ", ConstraintNeighborhood.studyArea, " that violate at least one default constraint: ", str(constraints.turbinesProhibited), " (", str(round(constraints.turbinesProhibited/constraints.count*100,2)), "%)"]))
print("".join(["Total number of grid cells in ", ConstraintNeighborhood.studyArea, " that violate at least one default constraint: ", str(constraints.allProhibited), " (", str(round(constraints.allProhibited/(constraints.allProhibited+constraints.allAllowed)*100,2)), "%)"]))
# Printout of the number of grid cells that do not violate any constraints
print("".join(["\nNumber of grid cells containing wind farms in ", ConstraintNeighborhood.studyArea, " that do not violate any default constraints: ", str(constraints.turbinesAllowed), " (", str(round(constraints.turbinesAllowed/constraints.count*100,2)), "%)"]))
print("".join(["Total number of grid cells in ", ConstraintNeighborhood.studyArea, " that do not violate any default constraints: ", str(constraints.allAllowed), " (", str(round(constraints.allAllowed/(constraints.allProhibited+constraints.allAllowed)*100,2)), "%)"]))
pdf.multi_cell(w=0, h=5.0, align='L',
txt="\nThe number (percentage) of grid cells that contain wind farms but violate the default constraints are detailed below: "
+'\n'+"- Wind farms within 2,500 meters of an airport: "+ str(constraints.airport)+ " ("+ str(round(constraints.airport/constraints.count*100,2))+ "%)"
+'\n'+"- Wind farms within 10,000 meters of a power plant: "+ str(constraints.plant)+ " ("+ str(round(constraints.plant/constraints.count*100,2))+ "%)"
+'\n'+"- Wind farms within 2,500 meters of a hospital: "+ str(constraints.hospital)+ " ("+ str(round(constraints.hospital/constraints.count*100,2))+ "%)"
+'\n'+" Wind farms within 2,500 meters of a school: "+ str(constraints.school)+ " ("+ str(round(constraints.school/constraints.count*100,2))+ "%)"
+'\n'+"- Wind farms within 500 meters of a major road: "+ str(constraints.roadMin)+ " ("+ str(round(constraints.roadMin/constraints.count*100,2))+ "%)"
+'\n'+"- Wind farms more than 10,000 meters from a major road: "+ str(constraints.roadMax)+ " ("+ str(round(constraints.roadMax/constraints.count*100,2))+ "%)"
+'\n'+"- Wind farms within 250 meters of a major transmission line: "+ str(constraints.transMin)+ " ("+ str(round(constraints.transMin/constraints.count*100,2))+ "%)"
+'\n'+"- Wind farms more than 10,000 meters from a major transmission line: "+ str(constraints.transMax)+ " ("+ str(round(constraints.transMax/constraints.count*100,2))+ "%)"
+'\n'+"- Wind farms in grid cells that average more than 2,000 meters above sea level: "+ str(constraints.elevation)+ " ("+ str(round(constraints.elevation/constraints.count*100,2))+ "%)"
+'\n'+"- Wind farms in grid cells with an average wind speed 80 meters above ground less than 5 meters per second: "+ str(constraints.wind)+ " ("+ str(round(constraints.wind/constraints.count*100,2))+ "%)"
+'\n'+"- Wind farms in grid cells with an average temperature below 0 degrees Celsius: "+ str(constraints.temp)+ " ("+ str(round(constraints.temp/constraints.count*100,2))+ "%)"
+'\n'+"- Wind farms in the presence of a military base or operations: "+ str(constraints.milit)+ " ("+ str(round(constraints.milit/constraints.count*100,2))+ "%)"
+'\n'+"- Wind farms in the presence of a National Park: "+ str(constraints.natPark)+ " ("+ str(round(constraints.natPark/constraints.count*100,2))+ "%)"
+'\n'+"- Wind farms in the presence of a USFWS critical species habitat: "+ str(constraints.critical)+ " ("+ str(round(constraints.critical/constraints.count*100,2))+ "%)"
+'\n'+"- Wind farms in the presence of a historical landmark: "+ str(constraints.historic)+ " ("+ str(round(constraints.historic/constraints.count*100,2))+ "%)"
+'\n'+"- Wind farms in the presence of mining operations: "+ str(constraints.mining)+ " ("+ str(round(constraints.mining/constraints.count*100,2))+ "%)"
+'\n'+"- Wind farms in the presence of a USFWS wildlife refuge: "+ str(constraints.wild)+ " ("+ str(round(constraints.wild/constraints.count*100,2))+ "%)"
+'\n'+"- Wind farms in the presence of tribal land: "+ str(constraints.tribal)+ " ("+ str(round(constraints.tribal/constraints.count*100,2))+ "%)", border=0)
# User is asked whether they wish to use the model's default constraints
def defaultConstraints(values,message):
while True:
x = input(message)
if x in values:
defaultConstraints.YesOrNo = x
break
else:
print("Invalid value; options are " + str(values))
defaultConstraints(["Y","N"], "\nDo you wish to use the model's default constraints? Y or N:\n")
# If the state does not contain any wind farms, the following is printed out
# instead
if constraints.count == 0:
# The total number of grid cells that do and do not violate the
# default constraints
print("".join(["\nTotal number of grid cells in ", ConstraintNeighborhood.studyArea, " that violate at least one default constraint: ", str(constraints.allProhibited), " (", str(round(constraints.allProhibited/(constraints.allProhibited+constraints.allAllowed)*100,2)), "%)"]))
print("".join(["Total number of grid cells in ", ConstraintNeighborhood.studyArea, " that do not violate any default constraints: ", str(constraints.allAllowed), " (", str(round(constraints.allAllowed/(constraints.allProhibited+constraints.allAllowed)*100,2)), "%)"]))
pdf.multi_cell(w=0, h=5.0, align='L',
txt="\nSince the study area contains no commercial wind farms, there are no constraints to violate.", border=0)
# User is asked whether they wish to use the model's default constraints
def defaultConstraints(values,message):
while True:
x = input(message)
if x in values:
defaultConstraints.YesOrNo = x
break
else:
print("Invalid value; options are " + str(values))
defaultConstraints(["Y","N"], "".join(["\n", ConstraintNeighborhood.studyArea, " does not contain any commercial wind farms. Do you wish to use the model's default constraints, Y or N? \n"]))
# The cellular automaton's constraints are then set. The first condition
# is for if the user wishes to use the default constraints.
if defaultConstraints.YesOrNo == "Y":
def airportConstraint():
airportConstraint.Value = 2500
airportConstraint()
def plantConstraint():
plantConstraint.Value = 10000
plantConstraint()
def hospConstraint():
hospConstraint.Value = 2500
hospConstraint()
def schConstraint():
schConstraint.Value = 2500
schConstraint()
def roadMinConstraint():
roadMinConstraint.Value = 500
roadMinConstraint.YesOrNo = "Y"
roadMinConstraint()
def roadMaxConstraint():
roadMaxConstraint.Value = 10000
roadMaxConstraint.YesOrNo = "Y"
roadMaxConstraint()
def transMinConstraint():
transMinConstraint.Value = 250
transMinConstraint.YesOrNo = "Y"
transMinConstraint()
def transMaxConstraint():
transMaxConstraint.Value = 10000
transMaxConstraint.YesOrNo = "Y"
transMaxConstraint()
def elevatConstraint():
elevatConstraint.Value = 2000
elevatConstraint()
def windConstraint():
windConstraint.Value = 4
windConstraint()
def tempConstraint():
tempConstraint.Value = 0
tempConstraint()
def militConstraint():
militConstraint.YesOrNo = "Y"
militConstraint()
def natParkConstraint():
natParkConstraint.YesOrNo = "Y"
natParkConstraint()
def criticalConstraint():
criticalConstraint.YesOrNo = "Y"
criticalConstraint()
def historicConstraint():
historicConstraint.YesOrNo = "Y"
historicConstraint()
def miningConstraint():
miningConstraint.YesOrNo = "Y"
miningConstraint()
def wildConstraint():
wildConstraint.YesOrNo = "Y"
wildConstraint()
def tribalConstraint():
tribalConstraint.YesOrNo = "Y"
tribalConstraint()
# All constraints will be passed into the update cursor further down
cursorList = ["Near_Air","Near_Plant","Near_Hosp","Near_Sch","Near_Roads",
"Near_Roads","Near_Trans","Near_Trans","Avg_Elevat","Avg_Wind",
"Avg_Temp","Military","Nat_Parks","Critical","Historical",
"Mining","Wild_Refug","Trib_Land"]
# The default constraints are saved to a dataframe, to inform the user
# of previously set constraints for this particular study area and
# grid cell size if selected again
dfConstraints = pd.DataFrame()
dfConstraints["Constraints"] = ["No wind farms within 2500 meters of an airport.",
"No wind farms within 10000 meters of a power plant.",
"No wind farms within 2500 meters of a hospital.",
"No wind farms within 2500 meters of a school.",
"No wind farms within 500 meters of a major road.",
"No wind farms more than 10000 meters from a major road.",
"No wind farms within 250 meters of a major transmission line.",
"No wind farms more than 10000 meters from a major transmission line.",
"No wind farms in grid cells that average more than 2000 meters above sea level.",
"No wind farms in grid cells with an average wind speed 80 meters above ground less than 5 meters per second.",
"No wind farms in grid cells with an average temperature below 0 degrees Celsius.",
"Wind farm development is prohibited in grid cells shared by military bases.",
"Wind farm development is prohibited in grid cells shared by national parks.",
"Wind farm development is prohibited in grid cells shared by USFWS critical habitats.",
"Wind farm development is prohibited in grid cells shared by historical landmarks.",
"Wind farm development is prohibited in grid cells shared by mining operations.",
"Wind farm development is prohibited in grid cells shared by USFWS wildlife refuges.",
"Wind farm development is prohibited in grid cells shared by tribal land."]
dfConstraints.to_csv("".join([directoryPlusConstraints + "/", ConstraintNeighborhood.studyArea, "_", ConstraintNeighborhood.density, "_acres_per_MW_", ConstraintNeighborhood.capacity, "th_percentile.csv"]))
# If the user does not wish to use the default constraints, their own
# constraints are set instead
elif defaultConstraints.YesOrNo == "N":
# Only the desired constraints will be passed into the update cursor
cursorList = []
cursorAppend = cursorList.append
# The constraints and their respective values are saved to these lists
constraintList = []
constraintAppend = constraintList.append
# Nearest airport constraint
def airportConstraint(values,message):
while True:
# User specifies Y or N for whether they wish to use airports
# as a constraint
x = input(message)
if x in values:
airportConstraint.YesOrNo = x
break
# Invalid text entry resets the function
else:
print("Invalid value; options are " + str(values))
while True:
if x =="Y":
# If the user wishes to use airports, its value is set and
# appended to the lists above
setValue = input("Please set the distance to nearest airport constraint (in meters) of your choice:\n")
# The user input must be convertible into an integer or
# float
try:
int(setValue)
airportConstraint.Value = setValue
cursorAppend("Near_Air")
constraintAppend("".join(["No wind farms within ", airportConstraint.Value, " meters of an airport."]))
break
except:
ValueError
try:
float(setValue)
airportConstraint.Value = setValue
cursorAppend("Near_Air")
constraintAppend("".join(["No wind farms within ", airportConstraint.Value, " meters of an airport."]))
break
except:
ValueError
# If the user input cannot be converted into an
# integer or float, the user is prompted to
# re-enter the input
print("Invalid value; please specify as an integer or float.")
else:
break
airportConstraint(["Y","N"], "\nWould you like to use airports as a constraint? Y or N:\n")
# Nearest power plant constraint
def plantConstraint(values,message):
while True:
x = input(message)
if x in values:
plantConstraint.YesOrNo = x
break
else:
print("Invalid value; options are " + str(values))
while True:
if x =="Y":
setValue = input("Please set the distance to nearest power plant constraint (in meters) of your choice:\n")
try:
int(setValue)
plantConstraint.Value = setValue
cursorAppend("Near_Plant")
constraintAppend("".join(["No wind farms within ", plantConstraint.Value, " meters of a power plant."]))
break
except:
ValueError
try:
float(setValue)
plantConstraint.Value = setValue
cursorAppend("Near_Plant")
constraintAppend("".join(["No wind farms within ", plantConstraint.Value, " meters of a power plant."]))
break
except:
ValueError
print("Invalid value; please specify as an integer or float.")
else:
break
plantConstraint(["Y","N"], "\nWould you like to use power plants as a constraint? Y or N:\n")
# Nearest hospital constraint
def hospConstraint(values,message):
while True:
x = input(message)
if x in values:
hospConstraint.YesOrNo = x
break
else:
print("Invalid value; options are " + str(values))
while True:
if x =="Y":
setValue = input("Please set the distance to nearest hospital constraint (in meters) of your choice:\n")
try:
int(setValue)
hospConstraint.Value = setValue
cursorAppend("Near_Hosp")
constraintAppend("".join(["No wind farms within ", hospConstraint.Value, " meters of a hospital."]))
break
except:
ValueError
try:
float(setValue)
hospConstraint.Value = setValue
cursorAppend("Near_Hosp")
constraintAppend("".join(["No wind farms within ", hospConstraint.Value, " meters of a hospital."]))
break
except:
ValueError
print("Invalid value; please specify as an integer or float.")