-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathOregon_Geologic_Map_Demo.py
331 lines (234 loc) · 14 KB
/
Oregon_Geologic_Map_Demo.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
'''
This script builds a geologic map of Oregon using an existing shapefile
Data source website: https://mrdata.usgs.gov/geology/state/state.php?state=OR
Shapefile source: http://pubs.usgs.gov/of/2005/1305/data/ORgeol_dd.zip
Color info file (right-click and "save page as"): https://mrdata.usgs.gov/catalog/lithrgb.txt
Setup instructions:
1) To run this script as-is, you will need to change the main_dir variable to reflect your desired path.
2) You will need to create some folders at the destination, specifically a "Oregon_Geologic_Map_Demo" folder, and within it the "Methods", "Data", and "Results" folders.
You can either do that manually or run this script once from anywhere to automatically create them.
3) Once you have created them, move the unzipped shapefile folder titled ORgeol_dd to the Data folder, as well as the color info file.
4) Now, save this script to the Methods folder and run it from there, if you wish.
Data files that are generated by this script will save to the Data folder, and the resulting map will save to Results
'''
import os
import re
import geopandas as gpd
import pandas as pd
import numpy as np
import pygmt
# Controls whether the project folders data are automatically created
create_folders = True
# Controls whether the shapefile data is conditioned
condition_shp = True
# Controls whether a color palette table is made
create_cpt = True
# Controls whether a geologic unit legend postscript file is created
create_legend = True
# Controls whether the geologic map is created
plot_map = True
# Class that holds all the functions pretaining to creating a geologic map of Oregon
class Map_Maker():
# Startup that runs each time the class is called
def Create_Folders(self):
# Main directory path
self.main_dir = r'C:\Users\USER\Desktop\Oregon_Geologic_Map_Demo'
# Path to the directory holding the project data files
data_folder = os.path.join(self.main_dir, 'Data')
# Path to the directory holding the project Python scripts
methods_folder = os.path.join(self.main_dir, 'Methods')
# Path to the directory holding the map generated by the scripts
results_folder = os.path.join(self.main_dir, 'Results')
# Path to the directory holding the conditioned shapefile data
conditioned_shapefiles_folder = os.path.join(self.main_dir, 'Data', 'Conditioned_SHP')
directories = [self.main_dir, data_folder, methods_folder, results_folder, conditioned_shapefiles_folder]
# Iterates through the list of directories and creates them if they don't already exist
for directory in directories:
os.makedirs(directory, exist_ok = True)
print("Be sure to set the create_folders parameter to False in order to run the main script")
exit()
# List that holds the inital unit names
unit_names_list = []
# Conditions the shapefile
def Condition_Shapefile(self):
# Shapefile of geologic unit polygons
geo_polygons = os.path.join(self.main_dir, 'Data', 'ORgeol_dd', 'orgeol_poly_dd.shp')
# Reads the shapefiel into a DataFrame
df_geo_polygons = gpd.read_file(geo_polygons, driver = 'SHP')
# Creates a numpy aray of the unique values in the ROCKTYPE1 column
unit_names = df_geo_polygons['ROCKTYPE1'].unique()
# List of unit names as they initally appear in the shapefile
self.unit_names_list = list(unit_names)
# Copy of unit names that is to be conditioned and subsituted for the original names
conditioned_unit_names = list(unit_names)
# Index of each character as they are read
index = -1
# Keys are what need to be replaced in words, and values are what they will be replaced with
replacements = {' ':'_', '/':'_', '-':'_', '(':'_', ')':'_', 'é':'e', 'mã©lange':'melange'}
# Iterates through the list of unique geologic unit names from the ROCKTYPE1 column and conditions them to the desired format
for name in conditioned_unit_names:
index += 1
for old_value, new_value in replacements.items():
if old_value in name:
conditioned_unit_names[index] = name.lower().replace(old_value, new_value)\
# Replaces the geologic unit names of the ROCKTYPE1 column in the dataframe with the conditioned names
for name, conditioned_name in zip(unit_names, conditioned_unit_names):
df_geo_polygons['ROCKTYPE1'] = df_geo_polygons['ROCKTYPE1'].replace(name, conditioned_name)
# Save name for the conditioned shapefile
geo_polygons_conditioned = os.path.join(self.main_dir,'Data', 'Conditioned_SHP', 'OR_geology_polygons_CONDITIONED.shp')
# Saves the DataFrame as an ESRI shapefile
df_geo_polygons.to_file(geo_polygons_conditioned, driver = 'ESRI Shapefile')
# Dictionary that holds the unit names from the cpt-like file prior to conditioning and the conditioned rgb colors
cpt_data_dictionary = {}
# Creates a color palette table file
def Create_Color_Palette_Table(self):
# Path to table of geologic unit colors
geo_colors = os.path.join(self.main_dir, 'Data', 'lithrgb.txt')
# Reads the table to a DataFrame, ignoring the "code" column and skipping the column names
df_geo_colors = pd.read_csv(geo_colors, sep ='\t', usecols = [1,2,3,4], skiprows = 1, header = None)
# Moves the geologic unit names column to be first
df_geo_colors.set_index(df_geo_colors.columns[-1], inplace = True)
# Resets the index so that the new column order is respected
df_geo_colors.reset_index(inplace = True)
# Save name for the new cpt
geo_cpt = os.path.join(self.main_dir, 'Data', 'geology_color_palette.cpt')
# Writes the DataFrame as a CSV file so that the data can be conditioned
df_geo_colors.to_csv(geo_cpt, sep = '\t', header = None, index = False)
with open(geo_cpt, 'r', encoding = 'latin-1') as f:
data = f.read()
# Keys are what need to be replaced in words, and values are what they will be replaced with
replacements = {' ':'', '/':'_', '-':'_', '(':'_', ')':'_', 'é':'e', 'mã©lange':'melange'}
# Iterates over the replacments dictionary and replaces the desired characters in the text
for old_value, new_value in replacements.items():
data = data.replace(old_value, new_value)
# Converts the text to lowercase
data = data.lower()
# Regular expression that finds tab-spaces between numbers
pattern = re.compile(r'(?<=\d)(\t)(?=\d)')
# Uses the regular expression to replace tab-spaces with "/"
data = pattern.sub('/', data)
with open(geo_cpt, 'w') as f:
f.write(data)
# DataFrame used to hold the unit names from the cpt-like file prior to conditioning and the conditioned rgb colors
self.df_cpt_data = pd.DataFrame()
# Adds only the first column (unit name column) to a column in the new dataframe titled 'geo_unit'
self.df_cpt_data['geo_unit'] = df_geo_colors.iloc[:, 0]
# Reads in the conditioned cpt file using tab delimiters and no column titles
df_cpt_data = pd.read_csv(geo_cpt, sep ='\t', header = None, encoding = 'latin-1')
# Adds only the second column (unit color column) to a column in the new dataframe titled 'color'
self.df_cpt_data['color'] = df_cpt_data.iloc[:, 1]
# Converts the values of the 'geo_unit' and 'color' columns into a dictionary with 'geo_unit' as the key and 'color' as the value
self.cpt_data_dictionary = pd.Series(self.df_cpt_data.color.values, index=self.df_cpt_data.geo_unit).to_dict()
# Creates a legend postscript file
def Create_Legend(self):
# File path to the geologic unit legend postscript file
geo_legend = os.path.join(self.main_dir, 'Data', 'geologic_unit_legend.txt')
# Index counter for determining position in unit_names_list
index = -1
# Iterates through the list of unit names and capitalizes the first letter of each entry
for name in self.unit_names_list:
index += 1
self.unit_names_list[index] = name.capitalize()
# Lists of the unit names and colors to be included in the legend
selected_unit_names = []
selected_colors = []
# Iterates through the dictionary of unit names and colors, capitalizes the first letter of each unit name, and then matches it with the list of inital unit names.
# Then those unit names and colors are added to respective lists
for name, color in self.cpt_data_dictionary.items():
cap_name = name.capitalize()
if cap_name in self.unit_names_list:
selected_unit_names.append(cap_name)
selected_colors.append(color)
# Creates a postscript file and writes some explainer text and the column format
with open(geo_legend, 'w') as f:
f.write(
'# G is vertical gap, V is vertical line, N sets # of columns,\n'
'# D draws horizontal line, H is header, L is column header,\n'
'# S is symbol,\n'
'# format of: symbol position, symbol type,\n'
'# format of: symbol size, symbol color, symbol border thickness, text position, text\n'
'N 2\n'
)
# Iterates through the unit names and colors, and adds the symbol+text lines to the existing postscript file
with open(geo_legend, 'a') as f:
for color, name in zip(selected_colors, selected_unit_names):
f.write(
'S 0.1i r 0.1i {} 0.1p 0.20i {}\n'
'\n'
.format(color, name)
)
# Plots the map
def Plot_Map(self):
# Map save name
save_name = os.path.join(self.main_dir, 'Results', 'Oregon_Geologic_Map_Demo.png')
# Geologic unit polygons
geo_unit_data = os.path.join(self.main_dir, 'Data', 'Conditioned_SHP', 'OR_geology_polygons_CONDITIONED.shp')
# Geologic unit color palette
geo_unit_color = os.path.join(self.main_dir, 'Data', 'geology_color_palette.cpt')
geo_unit_legend = os.path.join(self.main_dir, 'Data', 'geologic_unit_legend.txt')
# Extent defining the area of interest (Oregon) <min lon><max lon><min<lat><max lat>
region = [-126.418246, -116.462397, 41.984295, 46.297196]
# Map projection (Mercator): <type><size><units>
projection = 'M6i'
# Frame annotations: [<frame sides with/without ticks>, <x-axis tick rate>, <y-axis tick rate>]
frame = ['SWne', 'xa', 'ya']
# Polygon outline pens: <size><color>
pens = {'geology':'0.1p,black'}
# Transparency of layer, transparency of save file background
transparency = {'geology':50, 'save_file':False}
df_geo_polygons = gpd.read_file(geo_unit_data, driver = 'SHP')
# Establishes figure to hold map layers
fig = pygmt.Figure()
# Forces ticks to display as degree decimal
pygmt.config(FORMAT_GEO_MAP = 'D')
# Forces the map frame annotation to be smaller
pygmt.config(FONT_ANNOT_PRIMARY = '8p,Helvetica,black')
# Basemap layer
fig.basemap(
region = region,
projection = projection,
frame = frame
)
# Geologic unit layer
fig.plot(
# .gmt file - automatically detects polygon coordinates if in last column
data = df_geo_polygons,
# Sets polygon outline colour
pen = pens['geology'],
# Sets polygon color map
cmap = geo_unit_color,
# Sets color to vary with selected column
color = '+z',
# Force close polygons
close = True,
# Sets the column used to map polygon colors (in this case colors polygons by name of geologic unit). Cloumn name appears to be lowercase as a product of conditioning
aspatial = 'Z=ROCKTYPE1',
# Sets layer transperancy
transparency = transparency['geology'],
# Commandline feedback for debugging
#verbose=True,
)
# Plots the coastlines and political boundaries
fig.coast(
# Displays national boundaries (1) with 0.8 point gray40 lines, and does the same for state boundaries (2)
borders = ['1/0.8p,gray40', '2/0.8p,gray40'],
# Displays coast outlines in 0.3 point black lines, and lakeshore outlines in .1 point black lines
shorelines = ['1/0.3p,black', "2/0.1p,black"],
# Sets resolution full (f) [highest setting]
resolution = 'f',
# Sets water color
water = 'lightskyblue2',
)
# Plots a legend of the geologic unit names and respective colors
fig.legend(
spec = geo_unit_legend, # pslegend file
position = 'jBL+o15.5/-4c+w10/12c', # plots text justifed bottom left (jBL) and offsets (+o) it by 15.5cm on the x-axis and -4cm on the y-axis (15.5/-4c), and establises width of columns(?)/legend area(?) (+w) as 10cm on the x-axis and 12cm on the y-axis (10/12c)
)
# Saves a copy of the generated figure
fig.savefig(save_name, transparent = transparency['save_file'])
data = Map_Maker()
function_controls = [create_folders, condition_shp, create_cpt, create_legend, plot_map]
functions = [data.Create_Folders, data.Condition_Shapefile, data.Create_Color_Palette_Table, data.Create_Legend, data.Plot_Map]
for control, function in zip(function_controls, functions):
if control == True:
function()