вторник, 20 августа 2013 г.

Python for windows. Питон для виндоуз. Winpython

Цель: Разобраться в азах языка программирования Питона
Методы: WinPython, python, numpy, scipy, matplotlib, basemap
Задачи:
1) Установить на windows Питон;
2) разобраться в основных модулях Питона, которые необходимы для построения "научного конвейера";
3) "Научный конвейер". На примере реанализа NCEP/NCAR разобрать полный цикл обработки гидрометеорологических и океанологических данных: от чтения netcdf файла до рисования карты, пригодной для презентации/публикации.

Хочу предложить читателям моего блога взглянуть на язык программирования Питон/Python. Недавно я начал активно использовать его для решения своих научных задач (построение карт различных метеорологических параметров по данным различных реанализов в формате netcdf). Python позволяет легко читать netcdf формат, что для пользователей windows очень актуально. Ncdump, конечно, хорош, но требует всё равно постобработки. К тому же большие файлы  (а таких в научных задачах большинство) он "жуёт" плохо. Установить же библиотеку netcdf на windows, как я понимаю, задача достойная эпитета "эпическая".
А вот Питон/Python работает с netcdf легко и непринуждённо! К тому же сейчас для Питона написаны и свободно распространяются модули, которые позволяют создавать графику, в том числе и географические карты, очень высокого класса. Таким образом, с помощью Питона можно создать конвейер полного цикла по производству научной продукции (чтение данных, обработка и преобразование данных, анализ данных, графическое представление результатов).
Конечно, у Питона есть и недостатки. Но о них мы поговорим как-нибудь потом. А теперь перейдём к решению поставленных задач.


I. Установка Питона. IDLE, WinPython

Для начала нам нужен интерпретатор Питона. Можно скачать его с официального сайта. У Питона есть две ветви развития: вторая (Python 2.*) и третья (Python 3.*).  Если кратко, то они отличаются синтаксисом некоторых основных функций интерпретатора Питона. Я буду писать о Python 3.*, который является Питона будущего поколения. Если интересно чем они отличаются, то вам сюда (англ.).
Скачав "Python 3.3.2 Windows X86-64 MSI Installer" вы получите стандартный интерпретатор Питона. В установочный пакет входит среда разработки IDLE, которая отличается незатейливостью и простой. На данном этапе вы получите "моторчик", основной блок, ядро, с помощью которого вы сможете создать первую тестовую программу "Hello, World!".
Для естественно-научной работы необходимы дополнительные пакеты-модули. Их несколько:  numpy, scipy, matplotlib, basemap. Это необходимый минимум. Чтобы поставить их на windows, необходимо найти собранные для windows установочные пакеты. Обратите внимание на то, что одновременно должны совпадать архитектура (32 или 64), ветка Питона (2.* или 3.*) и текущая версия  (3.2.* и 2.6.*, например) дополнительного пакета и интерпретатора. Совет: по умолчанию качайте пакеты и интерпретатор для 32-х разрядных систем. Как показывает практика, все вкусные пакеты появляются сначала для x32, а затем, может быть, выходят и для x64. А часто и не выходят. Скачать эти модули можно по этим ссылкам: Scipy, Numpy, Matplotlib, Basemap. После загрузки их нужно установить. Здесь всё просто, ошибиться невозможно. Если всё же вышла ошибка проверьте на совпадение "реквизиты" интерпретатора и устанавливаемого пакета.
Таким образом, необходимо вручную искать, скачивать дополнительные модули и устанавливать их на ядро-интерпретатор Питона. Недавно я обнаружил на сайте SciPy альтернативные сборки интерпретатора Питона. Они также есть и на python.org (Alternative Implementations). Они включают в себя много всяческих примочек (среда разработки, предустановленные модули, дополнительные возможности). Я попробовал несколько и остановился на WinPython. Скачиваем, устанавливаем и запускаем среду разработки Spyder.
В окне Console (справа внизу) появится сообщение-приглашение:

Imported NumPy 1.7.1, SciPy 0.12.0, Matplotlib 1.3.0 + guidata 1.6.1, guiqwt 2.3.1
Type "scientific" for more details.

Пишем scientific:

>>> scientific
This is a standard Python interpreter with preloaded tools for scientific
computing and visualization:

>>> import numpy as np  # NumPy (multidimensional arrays, linear algebra, ...)
>>> import scipy as sp  # SciPy (signal and image processing library)

>>> import matplotlib as mpl         # Matplotlib (2D/3D plotting library)
>>> import matplotlib.pyplot as plt  # Matplotlib's pyplot: MATLAB-like syntax
>>> from pylab import *              # Matplotlib's pylab interface
>>> ion()                            # Turned on Matplotlib's interactive mode

>>> import guidata  # GUI generation for easy dataset editing and display

>>> import guiqwt                 # Efficient 2D data-plotting features
>>> import guiqwt.pyplot as plt_  # guiqwt's pyplot: MATLAB-like syntax
>>> plt_.ion()                    # Turned on guiqwt's interactive mode

Within Spyder, this interpreter also provides:
    * special commands (e.g. %ls, %pwd, %clear)
    * system commands, i.e. all commands starting with '!' are subprocessed
      (e.g. !dir on Windows or !ls on Linux, and so on)

То есть мало того, что нам не нужно искать и скачивать три пакета из четырёх, так они ещё и автоматически импортированы для удобства написания кода! Здорово! Однако последний пакет, который отвечает за рисование географических данных, не входит в эту сборку. Значит нужно скачать его и установить самостоятельно. Идём в Basemap и скачиваем установочный пакет для windows. Теперь его нужно установить в WinPython.

Установка пакетов для WinPython

Если просто запустить исполняемый файл, то возникнет ошибка - он будет искать стандартный интерпретатор Питона, установку которого мы рассмотрели выше. Для корректной установки модуля Basemap в WinPython, нужно запустить утилиту wpcp, которая находится в папке *\WinPython-64bit-3.3.2.2\scripts. Запустится оконное приложение, где нажав кнопку "add package" нужно выбрать скачанный установочный пакет. Завершите установку, нажав "Install package". Так можно устанавливать собранные для windows пакеты-модули в WinPython.


II. Обзор основных пакетов-модулей python для научной работы

Установив WinPython-64bit-3.3.2.2, например, и модуль Basemap, мы получили следующее:
1) интерпретатор питона - python.exe (*\WinPython-64bit-3.3.2.2\python-3.3.2.amd64\)
2) пакеты ScipyNumpyMatplotlibBasemap
3) редактор Spyder

Интерпретатор даёт нам базовые возможности, с их помощью мы программируем.
Пакеты-модули дают доступ к специализированным функциям. Разберём их подробнее.

1. Numpy. Хорошее введение с примерами синтаксиса дано здесь. А если кратко, то numpy вводит в python класс массивов, позволяет осуществлять матричные операции, преобразование Фурье, работу с случайными числами. Чаще всего от numpy требуется завести массив. Например так:

>>> import numpy as np
>>> a = np.arange(-90,90,30)
>>> a
array([-90, -60, -30,   0,  30,  60])
>>> type(a)
<class 'numpy.ndarray'>
>>> a.ndim
1
>>> len(a)
6
>>> a.shape
(6,)

В результате будет создан объект "a" типа "numpy.ndarray". Это массив из 6 элементов, от [-90,90) c шагом 30. Его форма - одномерный массив, число "осей", соответственно, 1. Чтобы изменить форму массива нужно применить метод reshape.

>>> a.reshape(2,3)
array([[-90, -60, -30],
       [  0,  30,  60]])

Хорошую шпаргалку по осям numpy можно найти здесь.

Для новичков
Функция len(имяобъекта) независима от имяобъекта и стоит выше него. Она заключает его в скобки. А вот метод имяобъекта.shape "проявляется" только после указания объекта (в нашем случае это массив "a"). Shape - это метод класса numpy.ndarray, то есть способность массива. Поэтому метод указывается после точки. Пример: машина - это класс. Цвет - метод машины. Оттенок - метод цвета. Чтобы получить название оттенка цвета машины, которую мы сейчас представили, согласно логике ООП и питона написать следующее: 
то_что_я_представляю = машина.цвет.оттенок

Если нужно что-то поменять в массиве по условию, то это легко сделать с помощью where (numpy.where):

>>> a = np.arange(10)
>>> a
array([0, 1, 2, 3, 4, 5, 6, 7, 8, 9])
>>> ii = np.where((a >= 2) & (a < 8))
>>> ii
(array([2, 3, 4, 5, 6, 7], dtype=int64),)
>>> a[ii] = a[ii] - 273
>>> a
array([   0,    1, -271, -270, -269, -268, -267, -266,    8,    9])

ii - массив индексов, которые удовлетворили логическому условию в where.



2. Scipy. Это библиотека (модуль с большим числом функций) математических алгоритмов и удобных функций, базирующихся на модуле Numpy. Я уже кратко показывал возможности scipy для определения квантилей. Именно через модуль scipy можно работать с файлами формата netcdf. Покажу на простом примере. Скачаем данные реанализа NCEP/NCAR. Пусть это будет файл с данные многолетнего среднемесячного приземного давления (pres.mon.ltm.nc). Загружаем файл в папку, где мы сохраним файл питоновского кода. 

Для новичков:
 # - однострочный комментарий
'''
Всё что между тройными кавычками - многострочный комментарий
'''

Итак, код с комментариями.

# ------------------ Scipy 1 ---------------------------------------
from scipy.io import netcdf as nc # подключаем выбранный модуль netcdf из модуля scipy.io

fname = 'pres.mon.ltm' # имя файла
f = nc.netcdf_file(fname+'.nc') # открываем файл и получим f - идентификатор открытого файла
fkeys = list(f.variables.keys())   # Преобразуем в список кортеж имён переменных, содержащихся в файле
print(fkeys) # печатаем список переменных (для ветки python 2. скобки не нужны)
['time', 'valid_yr_count', 'lat', 'pres', 'lon', 'climatology_bounds']
'''
Определяем массивы времени, долгот, широт, давления.
Причём широты и данные по широтам считываем задом наперёд, то есть в обратном направлении: от -90 до 90 (изначально в реанализе они уложены от 90 до -90). 
Это нужно для беспроблемного рисования карт с помощью модуля basemap.
'''
t = f.variables['time'][:]
x = f.variables['lon'][:]
y = f.variables['lat'][:]
v = f.variables['pres'][:]
v1 = f.variables['pres']
'''
Массив v и объект v разные. V - это данные, а v1 позволяет узнать коэффициент масштаба и добавочный коэффициент. Преобразовать массив v в нормальные значения можно так
"''
v = v*v1.scale_factor + v1.add_offset

f.close() # Закрываем файл

# -----------------------------------------------------------------

Итоговый код:

# ------------------ Scipy 2 ---------------------------------------

from scipy.io import netcdf as nc

fname = 'pres.mon.ltm'
f = nc.netcdf_file(fname+'.nc')
fkeys = list(f.variables.keys())
print(fkeys)

t = f.variables['time'][:]
x = f.variables['lon'][:]
y = f.variables['lat'][:]
z = f.variables['climatology_bounds'][:]
v = f.variables['pres'][:]
v1 = f.variables['pres']

v = v*v1.scale_factor + v1.add_offset

f.close()


# -----------------------------------------------------------------




3. Matplotlib. Это графический модуль, который позволяет строить как простые двумерных графики, так и трёхмерные изображение и даже географические карты.
Введение в matplotlib на русском разобрано здесь и у Колдунова. Для примера нарисуем график "роза ветров" (wind rose). Будем рисовать его в полярной проекции.

#----------- Matplotlib 1 -------------------------------------

from math import pi
import numpy as np
import matplotlib.pyplot as plt

angles = np.arange(0,2*pi,pi/4.0) # Задаём массив направлений 
r = np.random.random(len(angles)) # ... и случайный массив значений

fig = plt.figure() # Создаём новое полотно, на котором будем рисовать
ax = fig.add_subplot(111, projection='polar') # ax - полотно в полярной проекции, занимающее
# всё пространство полотно fig

# Рисуем график и его название 
ax.plot(angles, r, color='r', linewidth=1.)
ax.set_title("Wind rose", va='bottom')
plt.show()

Получаем следующее изображение:


В глаза сразу бросается 90 градусов вверху графика. Также наша роза не является замкнутой. Необходимо привести это изображение в порядок. Для этого добавим следующие строчки в код:

ax.set_theta_direction(-1) # теперь увеличение угла theta будет по часовой (-1 - увеличение угла 
# идёт против часовой стрелки)
ax.set_theta_offset(pi/2.0) # изменим положение нуля (ставим его на положение 'N', которое
# сейчас равно 90 градусам)

ax.plot((angles[-1],angles[0]),(r[-1],r[0]), color='r', linewidth=1.) # Добавляем ещё один plot-линию,
# которая соединяет последнюю и первую точки.

Сохраняем получившийся рисунок в файл с расширением 'png' (можно и другой) с помощью

plt.savefig('rose.png')


Вот так намного лучше! А теперь выведем сразу две розы:

#----------- Matplotlib 2-------------------------------------
from math import pi
import numpy as np
import matplotlib.pyplot as plt

angles = np.arange(0, 2*pi, pi/4.)

fig = plt.figure(figsize=(10,5)) # зададим явно размер полотна, чтобы графики не перекрывались

for i in range(1,3,1):

    r = np.random.random(len(angles)) # для каждой розы значения будут разные

    ax = fig.add_subplot(120+i, projection='polar')
    ax.plot(angles, r, color='r', linewidth=1.)
    ax.plot((angles[-1],angles[0]),(r[-1],r[0]), color='r', linewidth=1.)

    ax.set_theta_direction(-1)
    ax.set_theta_offset(pi/2.0)

    ax.set_title("Wind roses "+str(i), loc = 'center')

    plt.show()  
 
plt.savefig('rose_subplot.png')

#--------------------------------------------------------------

Здесь мы использовали subplot - разбили наше полотно на таблицу. Вписывать график мы будем уже в ячейку таблицы. Схема таблицы следующая:

SUBPLOT

ax = fig.add_subplot(121, projection='polar')
121 - rcn: r = 1, c = 2, n = 1
r - число строк в таблице
c - число столбцов в таблице
n - номер ячейки в получившейся таблице

Тогда, чтобы нарисовать графики друг под другом нужно будет задать значения для subplot 211 и 212. В приведенном выше коде этот номер изменяется с помощью переменной цикла i.



4. Basemap. Это дополнение к matplotlib, которое позволяет рисовать географические карты. Основу работы basemap составляет картографическая проекция, на которой будет изображаться то или иное поле. Разберём основные возможности Basemap на примере данных давления из реанализа NCEP/NCAR, которые мы обрабатывали в обзоре Scipy.

С помощью модуля basemap мы импортируем из 'mpl_toolkits.basemap' класс Basemap.

from mpl_toolkits.basemap import Basemap

Зададим в качестве основы проекцию Миллера. Всего на выбор доступно более 30 проекций. Нарисуем карту земного шара в цилиндрической проекции, которой является проекция Миллера.

# ------------------- Basemap 1 --------------------------------------


import numpy as np
import matplotlib.pyplot as plt


from mpl_toolkits.basemap import Basemap


m = Basemap(projection='mill')

m.drawcoastlines(linewidth=1) # Рисуем береговую линию
m.drawcountries(linewidth=0.5) # Рисуем  границы стран
m.drawmapboundary() # Рисуем рамку вокруг карты

# Рисуем географическую сетку (долготы и широты)

parallels = np.arange(-90,90+1,30)
meridians = np.arange(0,360+1,60)
m.drawparallels(parallels,labels=[True,True,True,True])
m.drawmeridians(meridians,labels=[True,False,False,True])

# -----------------------------------------------------------------------

Получится следующее:


Раскрасим карту и добавим реки на карту:

m.drawmapboundary(fill_color='aqua') # Делаем заливку океана голубым цветом
m.fillcontinents(color='orange',lake_color='green') # Красим континенты в оранжевый,
# а озера - в зелёный.

m.drawrivers() # Добавляем реки


За дополнительными возможностями по рисованию картографической основы - сюда. Разобравшись с основами, перейдём к отображению информации на карте.

# ------------------- Basemap 2 --------------------------------------

from scipy.io import netcdf as nc
import numpy as np
import matplotlib.pyplot as plt

from mpl_toolkits.basemap import Basemap

fname = 'pres.mon.ltm.nc'
f = nc.netcdf_file(fname)
fkeys = list(f.variables.keys())
print(fkeys)

t = f.variables['time'][:]
x = f.variables['lon'][:]
y = f.variables['lat'][:]
v = f.variables['pres'][:]
v1 = f.variables['pres']

f.close()

v = v*v1.scale_factor + v1.add_offset

m = Basemap(projection='mill')

m.drawcoastlines(linewidth=1)
m.drawcountries(linewidth=0.5)
m.drawmapboundary()

parallels = np.arange(-90,90+1,30)
meridians = np.arange(0,360+1,60)
m.drawparallels(parallels,labels=[True,True,True,True])
m.drawmeridians(meridians,labels=[True,False,False,True])

# Данные по широтам и долготам, взятые из netcdf файла необходимо перевести из векторов
# в матрицы. Делается это с помощью функции meshgrid
x1,y1 = np.meshgrid(x,y)
lons,lats = m(x1,y1) # задаем на картографической основе преобразованные в матрицы
# координаты

'''
Другой синтаксис перевода координат x и y в координаты, понятные карт. основе
lons,lats = m(*np.meshgrid(x,y))
'''

m.contourf(lons,lats,v[1,:,:],40) # Рисуем на картографической основе m данные за февраль
# contourf рисует изолинии с послойной окраской через 40 единиц по сеточным данным
# Массив v[1,:,:] - срез от всего массива v, представляющий данные за один месяц февраль
plt.title('Test map N') # Название карты

plt.savefig('Test_basemap_map.png')
plt.show()

# ------------------------------------------------------------------------------

Запускаем скрипт и получаем ... вот это!
Что-то явно было сделано с ошибками. По порядку:

1) Нарисовано только половина карты!
2) Непонятные белые полосы сверху и снизу карты!

Начнём с главного. Код рисует половину карты потому что данная проекция рассчитана на изменение долгот в пределах от -180 до 180, а в наших данных реанализа долготы изменяются в других пределах, а именно от 0 до 357.5:

>>> y,x


[ 90.   87.5  85.   82.5  80.   77.5  75.   72.5  70.   67.5  65.   62.5
  60.   57.5  55.   52.5  50.   47.5  45.   42.5  40.   37.5  35.   32.5
  30.   27.5  25.   22.5  20.   17.5  15.   12.5  10.    7.5   5.    2.5
   0.   -2.5  -5.   -7.5 -10.  -12.5 -15.  -17.5 -20.  -22.5 -25.  -27.5
 -30.  -32.5 -35.  -37.5 -40.  -42.5 -45.  -47.5 -50.  -52.5 -55.  -57.5
 -60.  -62.5 -65.  -67.5 -70.  -72.5 -75.  -77.5 -80.  -82.5 -85.  -87.5
 -90. ] [   0.     2.5    5.     7.5   10.    12.5   15.    17.5   20.    22.5
   25.    27.5   30.    32.5   35.    37.5   40.    42.5   45.    47.5
   50.    52.5   55.    57.5   60.    62.5   65.    67.5   70.    72.5
   75.    77.5   80.    82.5   85.    87.5   90.    92.5   95.    97.5
  100.   102.5  105.   107.5  110.   112.5  115.   117.5  120.   122.5
  125.   127.5  130.   132.5  135.   137.5  140.   142.5  145.   147.5
  150.   152.5  155.   157.5  160.   162.5  165.   167.5  170.   172.5
  175.   177.5  180.   182.5  185.   187.5  190.   192.5  195.   197.5
  200.   202.5  205.   207.5  210.   212.5  215.   217.5  220.   222.5
  225.   227.5  230.   232.5  235.   237.5  240.   242.5  245.   247.5
  250.   252.5  255.   257.5  260.   262.5  265.   267.5  270.   272.5
  275.   277.5  280.   282.5  285.   287.5  290.   292.5  295.   297.5
  300.   302.5  305.   307.5  310.   312.5  315.   317.5  320.   322.5
  325.   327.5  330.   332.5  335.   337.5  340.   342.5  345.   347.5
  350.   352.5  355.   357.5]

Чтобы корректно отобразить данные необходимо перевести координаты. Это делается с помощью функции shiftgrid:

import mpl_toolkits.basemap as mtb
v[1,:,:],x = mtb.shiftgrid(180,v[1,:,:],x,start=False)

lon0 = 180 - конец или начало нового вектора долгот (см. start).
strat = False - заданная долгота lon0 (здесь равна 180) является концом нового вектора. Если True, то, соответственно, первым значением.


Карта выглядит неплохо, но что за белые линии (white stripes) вверху и снизу? Это следствие того, что вектор широт у наших данных идёт от 90 до -90, то есть с севера на юг. Проекция же корректно отображает данные, когда значение широты идут наоборот: от -90 до 90, то есть возрастают.
Чтобы наиболее безболезненно убрать этот дефект прочитаем данные задом наперед:


y = f.variables['lat'][::-1]
v = f.variables['pres'][:,::-1,:]

Для переменной v широты являются второй осью, поэтому конструкцию '::-1' нужно ставить туда.

Если приглядеться, то справа, в районе 180 градуса восточной долготы, заметна белая полосочка. Этот эффект есть разрыв данных на границе -180 и 180, что на самом деле есть одна долгота. На цилиндрической проекции это не очень заметно, но вот на другой:

m = Basemap(projection='ortho',lon_0=180,lat_0=30,resolution='l')




Здесь, в новой проекции, есть отличие от цилиндрических проекций, где задаются границы прямоугольника, lon_0 и lat_0 ( они задают координату центральной точки обзора).

Чтобы убрать этот разрыв есть специальная функция addcyclic:

from mpl_toolkits.basemap import addcyclic
v,x = addcyclic(v,x)

НО! В текущей версии эта функция работает только, если массив v - матрица, то есть имеет две оси. Так если вектор x имеет форму x.shape = (144,), то v.shape должен быть (n, 144).
В нашем случае это не так, у массива v есть третья ось. v.shape = (n, 144, 73).
На форуме я нашёл, как расширить возможности функции addcyclic.

Необходимо заменить строчки 4977-4998 в файле, который содержит код функции addcyclic. Пусть к файлу выглядит примерно так:
File "C:\WinPython-64bit-3.3.2.2\python-3.3.2.amd64\lib\site-ackages\mpl_toolkits\basemap\__init__.py"
Spyder при запуске кода с базовой версией addcyclic выдаст ошибку, и можно будет щелкнуть по гиперссылке, чтобы этот файл открылся прям в Spyder. Ошибка будет такая:

File "C:\WinPython-64bit-3.3.2.2\python-3.3.2.amd64\lib\site-ackages\mpl_toolkits\basemap\__init__.py", line 4997, in addcyclic

Нужно заменить функцию addcyclic на  новую:

# ------------- new addcyclic -------------------------------


def addcyclic(arrin,lonsin):

    """
    ``arrout, lonsout = addcyclic(arrin, lonsin)``
    adds cyclic (wraparound) point in longitude to ``arrin`` and ``lonsin``,
    assumes longitude is the right-most dimension of ``arrin``.
    """

    nlons = arrin.shape[-1]
    newshape = list(arrin.shape)
    newshape[-1] += 1

    if ma.isMA(arrin):
        arrout  = ma.zeros(newshape,arrin.dtype)
    else:
        arrout  = np.zeros(newshape,arrin.dtype)

    arrout[...,0:nlons] = arrin[:]
    arrout[...,nlons] = arrin[...,0]

    if ma.isMA(lonsin):
        lonsout = ma.zeros(nlons+1,lonsin.dtype)
    else:
        lonsout = np.zeros(nlons+1,lonsin.dtype)

    lonsout[0:nlons] = lonsin[:]
    lonsout[nlons]  = lonsin[-1] + lonsin[1]-lonsin[0]
    return arrout,lonsout

# -----------------------------------------------------------


Теперь addcyclic будет работать с n-мерными массивами.  Правда ограничения на форму массивов всё же остались. Вектор, который будет замыкаться (x здесь) должен быть ПОСЛЕДНЕЙ осью вектора v. То есть (12,73,144) можно, а (12,144,73) нельзя замыкать с вектором x(144,). В нашем случае привнесенных изменений достаточно. Рисуем:
Осталось добавить цветовую шкалу.

cs = m.contourf(lons,lats,v[1,:,:],50)
cbar = plt.colorbar(cs,orientation = 'horizontal')
cbar.set_label('SLP, hPa')
или так, задав пользовательские границы изолиний и шкалы:

levels = (500,700,800,900,925,950,960,970,980,990,1000,1010,1020,1030,1040,1050,1100)
cs = m.contourf(lons,lats,v[1,:,:],levels)
cbar = plt.colorbar(cs,ticks=levels,orientation = 'horizontal')
cbar.set_label('SLP, hPa')
И весь код целиком:

# ------------------- Basemap 3 --------------------------------------

import mpl_toolkits.basemap as mtb
from scipy.io import netcdf as nc
import numpy as np
import matplotlib.pyplot as plt

from mpl_toolkits.basemap import Basemap,addcyclic

fname = 'pres.mon.ltm.nc'
f = nc.netcdf_file(fname)
fkeys = list(f.variables.keys())
print(fkeys)

t = f.variables['time'][:]
x = f.variables['lon'][:]
y = f.variables['lat'][::-1]
v = f.variables['pres'][:,::-1,:]
v1 = f.variables['pres']

f.close()

v = v*v1.scale_factor + v1.add_offset
v,x = addcyclic(v,x)

plt.figure(figsize=(15,15))

m = Basemap(projection='mill')
v[1,:,:],x = mtb.shiftgrid(180,v[1,:,:],x,start=False)

m.drawcoastlines(linewidth=1)
m.drawcountries(linewidth=0.5)
m.drawmapboundary()

parallels = np.arange(-90,90+1,30)
meridians = np.arange(0,360+1,60)
m.drawparallels(parallels,labels=[True,True,True,True])
m.drawmeridians(meridians,labels=[True,False,False,True])

x1,y1 = np.meshgrid(x,y)
lons,lats = m(x1,y1)

cs = m.contourf(lons,lats,v[1,:,:],50)
cbar = plt.colorbar(cs,orientation = 'horizontal')
cbar.set_label('SLP, hPa')

plt.title('Test pressure [hPa] map')
plt.savefig('Test_basemap_map.png')

plt.show()

# ----------------------------------------------------------------------







Как перевести UV в направление и скорость ветра? How to convert wind UV-components to direction and velocity?

 Всё просто.  def uv2dir(u, v):     '''     Источник:     https://github.com/blaylockbk/Ute_WRF/blob/master/functions/wind_calc...