Рубрики
Без рубрики

Визуализация цветов флага – от геопанд до листовок и спины

Визуализация данных всегда интересовала меня, и я давно скрываю R/DataIsbeautiful. 1 -го … Tagged с Python, JavaScript.

Визуализация данных всегда интересовала меня, и я давно скрываю r/dataisbeautiful Анкет 1 июля 2019 года, Пост о частоте цветов флага по континенту поймал интерес многих людей. Именно из этого поста у меня возникла идея сделать эту визуализацию.

Идея была простой (исполнение не было), рассчитайте соотношение цветов каждого флага и цвета каждой страны на карте, используя эти цвета. У меня был Предыдущий проект, в котором использовались коэффициенты цвета флага, чтобы сделать значки атома В Поэтому я знал, что смогу сделать это. К сожалению, я был неправ, и мне потребовалось три попытки визуализировать его должным образом.

Прежде чем вступить в детали каждой попытки, вот источники данных, которые я использовал.

Попытка 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(`
                
                
                ${flag.map((entry,index) =>{
                    return ``
                })}
                
                
                `);
                $(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 = '''
            
                %s           
            
            
            '''%(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'''
    
    
    {data:s}
    {grads:s}
    
''').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)


Это смогло создать карту

Я добавил текст и фон, используя черновой пейзаж

Любопытный случай Франции и Норвегии

Поделившись картой на различных сайтах, многие спрашивали о пропавшей Франции. Я не очень хорош в географии, но доверял своему коду, чтобы убедиться, что он не пропустил страну. Поэтому я провел некоторую отладку и исследование и узнал, что в ShapeFile, который я использовал, не было данных ISOA2, хранящиеся для Франции и Норвегии. Мой код использует данные ISO A2 для сопоставления файлов флагов с картой, поэтому отсутствующие данные привели к отсутствующим странам. Я жестко закодировал пару операторов, чтобы включить как страны, так и код, приведенный выше, обновляется для этого.

Связанный материал

Репозиторий GitHub

Оригинал: “https://dev.to/haideralipunjabi/flag-colours-visualisation-from-geopandas-to-leaflet-and-back-4ohk”