-
Notifications
You must be signed in to change notification settings - Fork 1
/
Copy pathplotMap.py
303 lines (222 loc) · 10.9 KB
/
plotMap.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
# -*- coding: utf-8 -*-
"""
******************************************************************************
plotMap.py - Módulo para auxiliar na plotagem de mapas com dados
Autor : Nelson Rossi Bittencourt
Versão : 0.132
Licença : MIT
Dependências: matplotlib e cartopy
******************************************************************************
"""
import matplotlib as mpl
import matplotlib.pyplot as plt
import cartopy
import cartopy.crs as ccrs
from cartopy.io.shapereader import Reader
from cartopy.feature import ShapelyFeature
# TODO: implementar as demais características do mapa.
# Classes
class Mapa:
"""
Classe Mapa - Define o layout do mapa a plotar.
É utilizado nas rotinas 'plotarMapa' e 'loadMapTemplate'.
Também poderá ser instanciada pelo usuário para criar modelos de mapas próprios ou modelos de mapas lidos de arquivos.
"""
def __init__(self):
#self.mapa_nome = ''
#self.mapa_tipo = ''
self.mapa_coordenadas = []
self.mapa_tipo = ''
self.barraCores_titulo = ''
self.barraCores_orientacao = ''
self.barraCores_valores = []
self.barraCores_codigos = []
self.barraCores_posicao = ''
self.barraCores_corMinimo = ''
self.barraCores_corMaximo = ''
self.barraCores_dist=0
self.barraCores_tam=0
class ArquivoShape:
"""
Classe ArquivoShape - Representa as características de um arquivo tipo 'shape file' para inserção em um mapa.
Para criar uma nova instância dessa classe é preciso fornecer o nome completo do arquivo 'shp'.
Esta classe permite que um mesmo arquivo shape seja utilizado em diversos mapas, sem o overhead causado
pela leitura do arquivo diversas vezes.
"""
def __init__(self,nomeArquivo, corFace='none', corLinha='gray',espLinha=0.5):
self.shape_feature = ShapelyFeature(Reader(nomeArquivo).geometries(), ccrs.PlateCarree(),
facecolor=corFace, edgecolor=corLinha, linewidth=espLinha,)
# Funções
def plotarMapa(titulo, lons, lats, dados, modeloMapa, destino='', shapeFile=-1):
"""
Plota um mapa considerando os dados fornecidos.
Argumentos
----------
titulo : Título do mapa;
lons : Lista com as longitutes;
lats : Lista com as latitudes;
dados : Lista com os dados a plotar;
modeloMapa : string ou objeto tipo 'Mapa'.
A string deve conter um nome de arquivo de template válido.
Mapa deve conter uma instância do tipo 'Mapa' válida.
destino : (Opcional) Nome do arquivo de saída para a figura. Se não declarado, exibe mapa na tela.
shapeFile : (Opcional) Deve ser fornecido:
O nome do arquivo tipo 'shp' para leitura do disco
ou um objeto tipo 'ArquivoShape' instanciado previamente
ou uma lista contendo uma combinação de strings e objetos tipo 'ArquivoShape'
Retorno
-------
Nenhum.
"""
# Verifica o tipo de argumento passado em 'modeloMapa'.
tipoModelo = type(modeloMapa)
if tipoModelo is str:
myMap = loadMapTemplate(modeloMapa)
elif tipoModelo is Mapa:
myMap = modeloMapa
else:
raise NameError("O argumento 'modeloMapa' deve ser uma string ou um tipo 'Mapa'!")
# Fecha uma figura anterior, se houver.
plt.close()
# Determina o tamanho do gráfico no console do Python.
fig = plt.figure(figsize=(5,5))
# Detemina tipo de projeção.
ax = fig.add_subplot(1,1,1,projection=ccrs.PlateCarree())
# Delimita o mapa.
ax.set_extent(myMap.mapa_coordenadas,ccrs.PlateCarree())
# Adiciona algumas caracteristica no mapa.
# Talvez, no primeiro uso, o 'matplotlib'/'cartopy' execute o download de mapas com as características requeridas.
ax.add_feature(cartopy.feature.LAND)
ax.add_feature(cartopy.feature.COASTLINE)
ax.add_feature(cartopy.feature.BORDERS)
# Adiciona arquivos tipo 'shape' ao mapa.
# Lista que conterá (ou não) strings, 'ArquivosShape' ou uma combinação.
newList = []
# Caso 'shapeFile' não seja uma lista, cria uma lista.
if (type(shapeFile)) is not list:
newList.append(shapeFile)
else:
newList = shapeFile
# Para cada item da lista 'newList', verifica o tipo e adiciona a característica no mapa
for item in newList:
tipoItem = type(item)
if tipoItem is ArquivoShape:
ax.add_feature(item.shape_feature)
elif tipoItem is str:
tmp = ArquivoShape(item)
ax.add_feature(tmp.shape_feature)
# Variáveis auxiliares para ajuste do mapa de cores
infbound = None
supbound = None
extend = 'neither'
# Ajusta código de cores se necessário.
if (myMap.barraCores_corMinimo!='-1') and (myMap.barraCores_corMaximo!='-1'):
extend = 'both'
infbound = myMap.barraCores_corMinimo
supbound = myMap.barraCores_corMaximo
elif (myMap.barraCores_corMinimo!='-1'):
extend = 'min'
infbound = myMap.barraCores_corMinimo
else:
extend = 'max'
supbound = myMap.barraCores_corMaximo
# Cria mapa de cores.
cmap = (mpl.colors.ListedColormap(myMap.barraCores_codigos).with_extremes(over=supbound, under=infbound))
# Cria o índice de cores do mapa
norm = mpl.colors.BoundaryNorm(myMap.barraCores_valores, cmap.N)
# Cria o gráfico de acordo com o tipo selecionado.
if (myMap.mapa_tipo=='contornos'):
filled = ax.contourf(lons, lats, dados, levels=myMap.barraCores_valores,cmap=cmap, norm=norm, extend=extend, transform=ccrs.PlateCarree())
elif (myMap.mapa_tipo=='xy'):
filled = ax.scatter(lons, lats, c=dados, s=50,alpha=1,cmap=cmap, norm=norm,edgecolors='black', transform=ccrs.PlateCarree())
else:
raise NameError("Tipo de mapa (mapa_tipo) inválido! Verifique o arquivo de template.")
# Ajusta a barra de cores, se houver.
if myMap.barraCores_orientacao!="none":
cbar = fig.colorbar(
filled,
orientation= myMap.barraCores_orientacao,
label= myMap.barraCores_titulo,
spacing = 'uniform',
pad=myMap.barraCores_dist,
fraction=myMap.barraCores_tam,
location=myMap.barraCores_posicao,
extendfrac='auto',
)
cbar.set_ticks(myMap.barraCores_valores)
# Define título do gráfico.
plt.title(titulo)
# Adiciona grid.
g1=ax.gridlines(crs=ccrs.PlateCarree(), draw_labels=True,linewidth=1, color='gray', alpha=0.5, linestyle='--')
g1.top_labels = False
g1.right_labels = False
g1.xlabel_style = {'size': 9, 'color': 'blue', 'weight': 'bold'}
g1.ylabel_style = {'size': 9, 'color': 'red', 'weight': 'bold'}
# Mostra na tela ou salva em arquivo.
if destino == '':
plt.show(block=True)
else:
fig.savefig(destino)
def loadMapTemplate(arquivoTemplateMapa):
"""
Lê as características de um modelo de mapa a partir de um arquivo de texto formatado.
Argumentos
----------
arquivoTemplateMapa : nome do arquivo contendo template do Mapa.
Este arquivo deve seguir, estritamente, o formato estabelecido.
Retorno
-------
objeto Mapa (ver definição da classe 'Mapa' neste arquivo).
"""
# Lista com as linhas lidas do arquivo de template de um mapa.
lines = []
# Número de linhas válidas esperado.
check_valid_lines = 11
# Contador de linhas válidas.
valid_lines = 0
# Número da linha atual que está sendo lida
num_line = 0
# Dicionário com dados lidos.
map_dict = {}
# Abre arquivo e lê as linhas válidas.
# O caracter '#' é utilizado para comentários no arquivo de template.
# Se o número de linhas válidas for diferente do esperado, lança uma exceção.
try:
with open(arquivoTemplateMapa, 'r') as f:
for line in f:
line = line.rstrip()
num_line = num_line + 1
prefix = line.strip()
if len(prefix) > 0 and prefix[0] != '#':
if (':' in line):
lines.append(line)
valid_lines = valid_lines + 1
listValues = line.split(':')
map_dict[listValues[0]]= listValues[1]
else:
raise NameError("Linha inválida no arquivo de template '{}'. Verique a linha {}.".format(arquivoTemplateMapa, num_line))
except:
raise NameError("Erro ao tentar abrir o arquivo de template para o mapa [{}]!\nVerifique o caminho completo do arquivo e tente novamente.".format(arquivoTemplateMapa))
# Verifica se o arquivo contem o número de linhas válidas.
if valid_lines!=check_valid_lines:
raise NameError("O arquivo de template '{}' contêm {} linhas válidas, quando o esperado são {} linhas.".format(arquivoTemplateMapa,valid_lines,check_valid_lines))
# Aloca os valores em um objeto do tipo 'Mapa'
# TODO: Comentar alocações abaixo, se necessário
local_map = Mapa()
try:
local_map.mapa_tipo = map_dict['mapa_tipo']
local_map.barraCores_orientacao = map_dict['barra_cores_orientacao']
local_map.barraCores_titulo = map_dict['barra_cores_titulo']
local_map.barraCores_corMinimo = map_dict['barra_cores_corMinimo']
local_map.barraCores_corMaximo = map_dict['barra_cores_corMaximo']
local_map.barraCores_posicao = map_dict['barra_cores_posicao']
local_map.barraCores_codigos = map_dict['barra_cores_codigos'].split(',')
local_map.barraCores_dist = float(map_dict['barra_cores_dist'])
local_map.barraCores_tam = float(map_dict['barra_cores_tam'])
tmp = map_dict['barra_cores_valores'].split(',')
local_map.barraCores_valores = [float(i) for i in tmp]
tmp = map_dict['mapa_coordenadas'].split(',')
local_map.mapa_coordenadas = [float(i) for i in tmp]
except:
raise NameError("Erro ao tentar interpretar o arquivo de template [{}] para o mapa!\nVerifique a sintaxe do arquivo e tente novamente.".format(arquivoTemplateMapa))
return(local_map)