Визуализация данных всегда интересовала меня, и я давно скрываю r/dataisbeautiful Анкет 1 июля 2019 года, Пост о частоте цветов флага по континенту поймал интерес многих людей. Именно из этого поста у меня возникла идея сделать эту визуализацию.
Идея была простой (исполнение не было), рассчитайте соотношение цветов каждого флага и цвета каждой страны на карте, используя эти цвета. У меня был Предыдущий проект, в котором использовались коэффициенты цвета флага, чтобы сделать значки атома В Поэтому я знал, что смогу сделать это. К сожалению, я был неправ, и мне потребовалось три попытки визуализировать его должным образом.
Прежде чем вступить в детали каждой попытки, вот источники данных, которые я использовал.
- Источник карты: https://naturalearthdata.com/downloads/
- Флаги: https://github.com/hjnilsson/country-flags
Попытка 1 (Python + Geopandas):
В моих предыдущих визуализациях (простые карты с точки зрения) я всегда использовал геопанд. Он может очень легко экспортировать высококачественные изображения.
Первое, над чем я работал, – это рассчитать коэффициенты цвета для каждой страны, присутствующей на карте. Я изменил код из следующего Stackoverflow post Чтобы удовлетворить мои потребности.
for index,row in map.iterrows(): # map is the GeoPandas variable country_code = map.loc[index,'ISO_A2'].lower() country_data=[] try: flag_image = Image.open(FLAGS_DIR+country_code+".png") except FileNotFoundError: continue flag_image = flag_image.convert("RGB") pixels = flag_image.getcolors(flag_image.width * flag_image.height) sorted_pixels = sorted(pixels, key=lambda t: t[0]) dominant_pixels = [] for pixel in pixels: if pixel[0]*100/(flag_image.width * flag_image.height) > 5: #Top 5 colours only dominant_pixels.append(pixel) for pixel in dominant_pixels: percentage = pixel[0]*100/(flag_image.width * flag_image.height) color = "#%02x%02x%02x" % pixel[1] # HEX Conversion country_data.append({"color":color,"percentage":percentage}) data[country_code] = country_data
Проблема в этой попытке возникла при попытке раскрасить страны. Geopandas не могут заполнить многоугольник, используя несколько цветов. Некоторое время я думал о компромисс и заполнении только самым доминирующим цветом. Достижение, которое было также сложно, самое ближайшее решение, которое я обнаружил, было это GitHub выпуск Анкет
Я не смог заполнить самый доминирующий цвет, поэтому я отказался от использования геопанд.
Какое -то время сидя на нем, я вспомнил, что LeafletJS использует CSS для укладки карт. Итак, после сохранения данных о цветах флага в файл JSON я начал свою вторую попытку визуализации, теперь с LeafletJS.
Попытка 2: Leafletjs
У меня были большие надежды от Leafletjs, и это было успешным в некоторой степени. Я был почти прав насчет градиентов поддержки листовок с использованием CSS.
Листовка делает элементы SVG, которые не поддерживают градиенты CSS, а их собственные элементы градиента.
Я легко смог раскрасить самый доминирующий цвет, но сделать градиент оказался трудным.
Мне пришлось создавать элементы для каждого градиента и связывать их с каждым пути SVG.
Я добавил код страны по каждому пути, используя следующий код
onEachFeature(feature,layer){ layer.options.className = "country " + feature.properties.ISO_A2.toLowerCase() },
а затем на Добавить \
Событие карты листовок, добавлено следующий код
.on("add",function(){ for(let pathElm of $(".country")){ classes = Array.from(pathElm.classList); country = classes[classes.indexOf("country") + 1]; flag = flagData[country] console.log(flag) $("body").append(``); $(pathElm)f.attr('fill',`url(#${country})`); }
Это было в состоянии создать карту градиента, как я хотел, но после того, как я хотел добавить атрибуты, я наткнулся на следующий отказ от ответственности на Сайт данных естественной земли
Отказ от ответственности
Природная земля вектор рисует границы стран в соответствии с статусом дефакта. Мы показываем, кто на самом деле контролирует ситуацию на земле. Пожалуйста, не стесняйтесь объединить наши спорные темы, чтобы соответствовать вашим конкретным политическим взглядам.
Чтобы избежать проблем позже, я решил добавить карту спорных областей и наполнить их белым цветом.
Это потребовалось немного рефакторинга, но я смог легко объединить две карты со следующим кодом.
L.map('mapid',{ center: [39.73, -104.99], zoom: 5, layers: [mapLayer,disLayer] });
Я думал, что закончил, но экспорт карты в хорошее изображение оказалось невозможным. Я попробовал много плагинов, но ни один из них не создал достаточно хорошего изображения. Мне пришла в голову мысль о копировании SVG из инструментов разработчика и использовании Inkscape для создания хорошего изображения, но листовка отображает разные пути для разных уровней масштабирования. Менее подробные пути, когда карта полностью увеличена и детализирована, но только увеличенные в части представлены иначе.
Эта попытка также потерпела неудачу, но дала мне еще одну идею. Преобразование Geopandas DataFrames в SVGS.
Попытка 3: Python + Geopandas (экспорт в SVG)
После того, как я не использовал ListletJS, я вернулся в Geopandas с другой идеей. Экспорт геопанд в качестве SVG, а затем применять градиент к нему. Моя первоначальная идея заключалась в том, чтобы добавить градиенты с карт, сгенерированных листочками, но не нужно.
Этот пост мне очень помог в этой попытке
Я добавил код из блога в свой код из попытки 1 и изменил его в соответствии с моими потребностями.
# SOURCE: http://kuanbutts.com/2018/08/30/geodataframe-to-svg/ def process_to_svg_group(row,dis=False): orig_svg = row.geometry.svg() doc = minidom.parseString(orig_svg) paths = doc.getElementsByTagName('path') pathssvg = [] country_code = row['ISO_A2'].lower() if row['NAME'] == 'France': country_code = 'fr' if row['NAME'] == 'Norway': country_code = 'no' for path in paths: path.setAttribute('fill', 'url(#%s)'%(country_code)) path.setAttribute('stroke-width','0.1') path.setAttribute('stroke','#000000') path.setAttribute('opacity','1') path.setAttribute('transform','scale(10,-10)') pathssvg.append(path.toxml()) return ''.join(pathssvg) processed_rows = [] def_rows = [] res_symdiff = gpd.overlay(gismap, dismap, how='difference') for index,row in res_symdiff.iterrows(): country_data=[] dominant_pixels = [] stops = [] country_code = row['ISO_A2'].lower() if row['NAME'] == 'France': country_code = 'fr' if row['NAME'] == 'Norway': country_code = 'no' try: flag_image = Image.open(FLAGS_DIR+country_code+".png") except FileNotFoundError: continue flag_image = flag_image.convert("RGB") # SOURCE: https://stackoverflow.com/a/52879133/4698800 pixels = flag_image.getcolors(flag_image.width * flag_image.height) sorted_pixels = sorted(pixels, key=lambda t: t[0]) for pixel in sorted_pixels: if pixel[0]*100/(flag_image.width * flag_image.height) > 1: dominant_pixels.append(pixel) print(dominant_pixels) sum = 0 for x in dominant_pixels: sum += x[0] print(sum) for pixel in dominant_pixels: percentage = pixel[0]*100/sum print(percentage) color = "#%02x%02x%02x" % pixel[1] perc = 0 if len(country_data) > 0: for x in country_data: perc += x['percentage'] stops.append(''%(perc,color,perc+percentage,color)) country_data.append({"color":color,"percentage":percentage}) grad = ''' '''%(country_code,''.join(stops)) def_rows.append(grad) p = process_to_svg_group(row) processed_rows.append(p) props = { 'version': '1.1', 'baseProfile': 'full', 'width': '100%', 'height': '100%', 'viewBox': '{}'.format(','.join(map(str, gismap.total_bounds))), 'xmlns': 'http://www.w3.org/2000/svg', 'xmlns:ev': 'http://www.w3.org/2001/xml-events', 'xmlns:xlink': 'http://www.w3.org/1999/xlink' } template = attrs = ' '.join([template.format(key=key, val=props[key]) for key in props]) raw_svg_str = textwrap.dedent(r''' ''').format(attrs=attrs, data=''.join(processed_rows),grads=''.join(def_rows)).strip() with open('out/map.svg', 'w') as f: f.write(raw_svg_str) %s
Это смогло создать карту
Я добавил текст и фон, используя черновой пейзаж
Любопытный случай Франции и Норвегии
Поделившись картой на различных сайтах, многие спрашивали о пропавшей Франции. Я не очень хорош в географии, но доверял своему коду, чтобы убедиться, что он не пропустил страну. Поэтому я провел некоторую отладку и исследование и узнал, что в ShapeFile, который я использовал, не было данных ISOA2, хранящиеся для Франции и Норвегии. Мой код использует данные ISO A2 для сопоставления файлов флагов с картой, поэтому отсутствующие данные привели к отсутствующим странам. Я жестко закодировал пару операторов, чтобы включить как страны, так и код, приведенный выше, обновляется для этого.
Связанный материал
Репозиторий GitHub
Оригинал: “https://dev.to/haideralipunjabi/flag-colours-visualisation-from-geopandas-to-leaflet-and-back-4ohk”