Вопрос или проблема
Я пытаюсь построить данные (.csv) по муниципалитетам (.shp) на карте Скандинавии, используя contourf. Эти данные имеют большой диапазон значений (см. список внизу), для чего я использую логарифмическое масштабирование, чтобы показать уровень детализации в нижних значениях. Код, который я использую, работал хорошо до недавнего времени. Смотрите изображение ниже слева.
Однако сейчас, когда я запускаю тот же самый код, карты показывают слишком светлые цвета, которые не соответствуют ни значениям данных, ни цветовому бару (см. изображение ниже справа). Я предполагаю, что это связано с обновлением версии, но не могу понять, как это исправить, чтобы отобразить данные с правильными цветами на карте.
Если есть более простой способ отображения значений на логарифмической шкале, я тоже буду рад услышать!
Вот мой код:
import cartopy.crs as ccrs
import numpy as np
from shapely.geometry import Polygon
import cartopy
from cartopy.io import shapereader
import matplotlib
import pandas
import geopandas
projection = ccrs.Mercator()
transform = ccrs.PlateCarree()
extent = [4.5, 31.9, 54.7, 71.24]
cmap = matplotlib.cm.get_cmap('Reds')
# Получить границы
resolution = '10m'
category = 'cultural'
name="admin_0_countries"
shpfilename = shapereader.natural_earth(resolution, category, name)
shpfilenamemunicipalities="path/to/ScandinaviaKommuner.shp"
# Прочитать файл shapefile с использованием geopandas
df = geopandas.read_file(shpfilename)
df2 = geopandas.read_file(shpfilenamemunicipalities)
# Выбрать интересующие страны
scan3 = df[ df['ADMIN'].isin(['Norway', 'Finland', 'Sweden']) ]
scan3_dissolved = scan3.dissolve(by='LEVEL')
poly = [scan3_dissolved['geometry'].values[0]]
# Создать маску
stamen_terrain = cimgt.Stamen('terrain_background')
st_proj = stamen_terrain.crs # проекция, используемая изображениями Stamen
ll_proj = ccrs.PlateCarree()
def rect_from_bound(xmin, xmax, ymin, ymax):
"""Возвращает список (x,y)'s для прямоугольника"""
xs = [xmax, xmin, xmin, xmax, xmax]
ys = [ymax, ymax, ymin, ymin, ymax]
return [(x, y) for x, y in zip(xs, ys)]
pad1 = .1 # отступ, единицы в градусах
exts = [poly[0].bounds[0] - pad1, poly[0].bounds[2] + pad1, poly[0].bounds[1] - pad1, poly[0].bounds[3] + pad1];
msk = Polygon(rect_from_bound(*exts)).difference(poly[0].simplify(0.01))
msk_stm = st_proj.project_geometry(msk, ll_proj) # проекция геометрии на проекцию, используемую stamen
# Загрузить данные .csv
firedata = pandas.read_csv('/path/to/CountryLevel_FireData.csv', delimiter=";", decimal=".")
firedatamunicipalities = pandas.read_csv('/path/to/MunicipalityLevel_FireData.csv', delimiter=";", decimal=".")
# Подмножество данных .csv
countries = firedata['Country'].tolist()
municipalities = firedatamunicipalities['Municipality'].tolist()
fireextents = firedatamunicipalities['FireIntensity'].tolist()
# Нормализовать данные о пожаре от 0 до 1 для извлечения цвета и логарифмическое преобразование
fireextents_log = list(range(0,966))
for fireextent, log in zip(fireextents, fireextents_log):
if fireextent>0:
fireextents_log[log] = np.log(fireextent)
else:
fireextents_log[log] = np.nan
fireextents_norm = (fireextents_log-np.nanmin(fireextents_log))/(np.nanmax(fireextents_log) - np.nanmin(fireextents_log))
# Создать график
fig, axs = plt.subplots(nrows = 1, ncols = 1,
subplot_kw = {'projection': projection},
layout="constrained")
axs.set_extent(extent)
axs.add_feature(feature.BORDERS, linewidth=linewidth_countrylines, zorder=zorder_countrylines)
axs.set_extent(extent)
axs.axis('off')
axs.add_geometries(shapely.get_parts(poly[0]), crs=transform, facecolor=facecolor_polygon,
edgecolor=edgecolor_polygon, linewidth=linewidth_polygon, zorder=18)
axs.add_geometries(msk_stm, st_proj, facecolor=facecolor_landmask, edgecolor=edgecolor_landmask,
zorder=16)
for municipality, fireextent_norm in zip(municipalities, fireextents_norm):
poly3 = df2.loc[df2['Municipali'] == municipality]['geometry'].values
if fireextent_norm > 0:
rgba2 = cmap3(fireextent_norm)
else:
rgba2 = 'white'
axs.add_geometries(poly3, crs=transform, facecolor=rgba2, edgecolor="black", linewidth=linewidth_municipality, zorder=14)
dummy_scat = axs.scatter(fireextents, fireextents, c=fireextents, cmap=cmap, zorder=0,
norm=matplotlib.colors.LogNorm())
cbar = fig.colorbar(dummy_scat, ax=axs, orientation='horizontal', shrink=1, pad=0.01)
cbar.set_label(colorbarlabel1, size=fontsize, labelpad=15)
cbar.ax.tick_params(labelsize=labelsize)
plt.show()
Часть данных о границах пожара, чтобы показать большой диапазон значений ниже. Как видно, значения действительно достигают диапазона 10³ и должны отображаться соответственно:
print(fireextents)
>>> [4183.6667, 1378.0, 1065.0, 991.6, 908.0, 612.0, 548.0625, 520.0, 412.7778, 331.0, 324.0, 280.5, 259.3333, 259.0, 234.0, 222.0, 212.6667, 199.0, 169.0, 158.3333, 153.5, 148.6, 147.5, 145.0, 133.8, 130.0, 108.3333, 105.25, 103.0, 103.0, 90.0, 86.0, 84.3, 82.1667, 80.6, 80.0, 79.8571, 75.0, 74.0, 71.0, 65.3, 63.0, 59.6, 58.0, 58.0, 57.6667, 55.0, 55.0, 54.9, 54.8636, 53.25, 51.0, 50.4667, 49.0, 49.0, 49.0, 48.7143, 48.0, 47.1071, 47.0, 46.5, 46.0, 45.0, 43.0, 42.2, 42.0, 41.0, 40.0, 38.2, 35.4, 34.9, 34.9, 34.5, 34.0, 34.0, 34.0, 33.8, 33.0, 33.0, 33.0, 32.8, 32.0, 31.0, 30.3333, 30.0, 30.0, 30.0, 29.6667, 29.5, 29.4, 29.3, 29.2857, 28.1667, 28.0, 28.0, 28.0, 27.0, 27.0, 26.0, 26.0, 26.0, 26.0, 26.0, 26.0, 25.8, 25.0, 25.0, 25.0, 25.0, 24.6, 24.5, 24.0, 24.0, 23.5, 23.1429, 23.0, 23.0, 22.0, 21.3, 19.0, 19.0, 19.0, 19.0, 19.0, 18.0, 17.5, 17.0, 16.5, 15.9167, 15.8, 15.5, 15.5, 15.3, 15.0714, 15.0, 15.0, 15.0, 14.5, 14.0, 13.5, 13.3333, 13.0, 12.5, 12.5, 12.2941, 12.0588, 12.0, 12.0, 12.0, 11.6, 11.0, 10.6667, 10.0, 9.6667, 9.6667, 9.5, 9.5, 9.0, 8.5714, 8.5, 8.3, 7.1667, 7.0, 7.0, 7.0, 6.6, 6.0, 6.0, 6.0, 6.0, 5.5, 5.0, 5.0, 5.0, 5.0, 5.0, 5.0, 5.0, 5.0, 5.0, 4.5, 4.0, 4.0, 4.0, 4.0, 4.0, 4.0, 4.0, 4.0, 4.0, 4.0, 4.0, 4.0, 4.0, 4.0, 4.0, 3.75, 3.5, 3.5, 3.4, 3.0, 3.0, 3.0, 3.0, 3.0, 3.0, 3.0, 3.0, 3.0, 3.0, 3.0, 3.0, 3.0, 3.0, 2.5, 2.2, 2.0, 2.0, 2.0, 2.0, 2.0, 2.0, 2.0, 2.0, 2.0, 2.0, 2.0, 2.0, 2.0, 2.0, 2.0, 2.0, 1.5, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, ... 0.0, 0.0, 0.0]
Ответ или решение
Для решения проблемы с использованием библиотеки Cartopy для отображения данных с логнормальными значениями, которое приводит к слишком светлым цветам на вашей карте, вам стоит пересмотреть несколько моментов в вашем коде и методах визуализации.
Основные шаги для исправления:
-
Проверка логарифмического преобразования:
Убедитесь, что вы корректно обрабатываете значения с помощью логарифмического преобразования. Ваша текущая реализация используетnp.log
, что может быть неэффективным для значений, равных или меньше нуля. Вместо этого вы можете использоватьnp.log1p
, который обрабатывает случаи, когда значения равны нулю, что может помочь избежать NaN.Например:
fireextents_log = np.log1p(fireextents)
-
Обновление нормализации:
Когда вы нормализуете значения, вы можете использоватьNormalize
изmatplotlib
, чтобы обновить ваше преобразование значений. Убедитесь, что используетеLogNorm()
для векторизации логарифмических данных во время создания цветовой карты. Это также поможет в ситуации с сильно изменяющимися диапазонами данных.Например:
from matplotlib.colors import LogNorm fireextents_norm = LogNorm()(fireextents)
-
Использование подходящих параметров цветовой карты:
При создании цветовой карты с использованиемcmap
, убедитесь, что значение, отображаемое вscatter
, совпадает с нормализованными логарифмическими значениями. -
Проверка на изменения в версиях библиотек:
Если код работал раньше, проверьте свои зависимости (например, Cartopy, Matplotlib), чтобы убедиться, что обновления не изменили поведение каких-либо функций. Иногда изменения в цветовой интерполяции могут быть связаны с обновлениями библиотеки. -
Добавление цветовой карты:
Убедитесь, что ваша цветовая карта соответствует вашим данным. Пробуйте использовать другие цветовые карты изmatplotlib
, которые лучше разбивают данные на логарифмической шкале. Например, используйтеcmap='viridis'
илиcmap='plasma'
. -
Пример кода:
Вот как должен выглядеть модифицированный код:import matplotlib.pyplot as plt from matplotlib.colors import LogNorm # Ваша логика получения данных остается прежней # Вместо обычной нормализации используйте LogNorm fireextents_log = np.log1p(fireextents) # Логарифмическое преобразование fireextents_norm = (fireextents_log - np.nanmin(fireextents_log)) / (np.nanmax(fireextents_log) - np.nanmin(fireextents_log)) # Создание графика fig, ax = plt.subplots(subplot_kw={'projection': projection}) ax.set_extent(extent) # Добавляем геометрию for municipality, fireextent_norm in zip(municipalities, fireextents_norm): poly3 = df2.loc[df2['Municipali'] == municipality]['geometry'].values rgba2 = cmap(fireextent_norm) if fireextent_norm > 0 else 'white' ax.add_geometries(poly3, crs=transform, facecolor=rgba2, edgecolor='black') # Делаем scatter для colorbar scatter = ax.scatter(municipalities, fireextents, c=fireextents, cmap='Reds', norm=LogNorm()) cbar = plt.colorbar(scatter, ax=ax, orientation='horizontal') cbar.set_label('Fire Intensity') plt.show()
Заключение:
Попробуйте вышеуказанные решения, чтобы решить проблему с цветами на вашей карте. Если проблема сохраняется, возможно, вам стоит также рассмотреть визуализацию данных с помощью других библиотек, таких как Folium или Plotly, для большей интерактивности и проверки различных стилей визуализации.