/posts  ·   /talks  ·   /boat  ·   /#30DayMapChallenge 2020 | 2022  ·  /About

2022 / Day 21: Kontur World Population

Todays query will group the world population at the Equator and then draw a joyline based on the population values derived from the Kontur World Population dataset. There might be other reasons but mainly I’m doing this so I could pack all the data into the query below (as is the tradition with all my maps for this years 30DayMapChallenge).

Anyway. For data preprocessing I imported the Kontur World Population dataset using ogr2ogr to my local PostgreSQL database with:

$ ogr2ogr -f \
    PostgreSQL \
    -nlt GEOMETRY \
    "PG:host=localhost user=postgres dbname=postgres" \
    /home/data/kontur_population.gpkg

And then in the database to preapre the data for todays query:

select
    st_asencodedpolyline(
        st_makeline(
            array_agg(
                st_translate(
                    st_force2d(pts.geom),
                    0,
                    st_z(pts.geom)/1000000.0
                ) order by pts.path
            )
        ) as geom
from (
    select
        1 as oid,
        st_segmentize(
            st_makeline(
                array_agg(
                    st_pointz(
                        q_lon,
                        0,
                        population,
                        4326
                    ) order by q_lon
                )
            ),
            1
        ) as geom
    from (
        select
            sum(coalesce(population, 0)) as population, q_lon
        from (
            select
                st_x(
                    st_snaptogrid(
                        st_transform(
                            st_centroid(geom),
                            4326
                        ),
                        1
                    )
                ) as q_lon, population
            from
                population
        ) f
        group by q_lon
        order by q_lon
    ) f
) d
    join lateral
        st_dumppoints(d.geom) pts on true
group by
    d.oid;

Which aggregates the h3 hex population values into 1 degree bins using st_snaptogrid. Then aggregates these into a 3D line along the Equator with the sum of population as z-coordinate. st_segmentize the resulting line at 1 degree, dumps the points in a lateral join and then st_translate every nodes y-coordinate by a value of population / 1000000.0. And finally outputs the linestring in polyline format using st_asencodedpolyline. This is the string we’ll use as an input for todays querys data.

So we’ll start the CTE with decoding the geometry with st_linefromencodedpolyline. We’ll plot the data on an ellipse centered at Null Island and the semi-minor axis reaching the poles and semi-major axis along the Equator as far as possible. This will give the result an oblique view centered at around [0,45]. As yesterday we’ll create a full set of 360 pieces of them rotating every one by 1 degree with st_rotate. We’ll need the boundary only (and hence st_boundary). And another thing here - we’ll reverse the resulting linestrings with st_reverse because the outer shell of a polygon runs clockwise, but we’ll want it counter-clockwise :)

From the population data in pop.geom we’ll extract all vertices at all timestamps we have for ellipses, find the corresponding location on the ellipse boundary with st_lineinterpolatepoint and st_translate the nodes y-coordinate the required amount base on the st_y value from the population-polyline

With to give a bit of context let’s add the longitudinal locations of the 20 biggest cities we were looking at a few days ago in 2022 / Day 19: Globe as well on the Equator line. The list is compiled according to Wikipedias [List of largest cities](https://en.wikipedia.org/wiki/List_of_largest_cities with their locations from NaturalEarth.

The process here is similar only that we will not have to shift any y-coordinate values - only st_lineinterpolatepoint the correct amount.

output image

with
    pop as (
        select
            st_linefromencodedpolyline(
'{pA~fsia@be@_ibEhL_ibEbR_ibEuv@_ibE{|M_ibEbxK_ibEha@_ibEw`d@_ibE`yZ_ibEnzJ_ibEtM_ibEnA_ibEeG_ibEgA_ibEbB_ibExE_ibEqC_ibEsA_ibErB_ibEa`G_ibEioA_ibEawxD_ibE~nwD_ibEe~]_ibE`jQ_ibEhoW_ibEsB_ibEc_D_ibEewF_ibEuulA_ibEbjz@_ibEdgY_ibEjqB_ibEkV_ibEhj@_ibE|M_ibEzH_ibEgC_ibEgI_ibEoP_ibErB_ibEjX_ibEY_ibE_L_ibEeoJ_ibEdqH_ibE~e@_ibEunA_ibEptB_ibEibB_ibEiqA_ibEn{B_ibEyzB_ibEpqA_ibEaj\_ibEqjdC_ibE{dzn@_ibEgqeX_ibEdu}o@_ibEz_jH_ibEmsjD_ibEqqjz@_ibEhwzS_ibEdhds@_ibEymaJ_ibEfi`A_ibElofK_ibEewel@_ibE|`y_@_ibE|cnF_ibEbubC_ibEcmL_ibEcf{J_ibEosjE_ibEevoU_ibE|sj_@_ibEu|_j@_ibE`c_G_ibEwvqJ_ibEyjut@_ibEizjhA_ibEhchmA_ibEc_yD_ibErk~z@_ibEcrhA_ibE`moV_ibEe_}Y_ibEs]_ibEqisO_ibEuqhL_ibExqeW_ibE}`pu@_ibEjrbp@_ibEongL_ibEb`hZ_ibEw}yr@_ibEjo|]_ibEeaaV_ibErgdF_ibEewfvA_ibEfgtw@_ibEjwzp@_ibE}~j|B_ibElm|s@_ibE|g}Z_ibEejroB_ibEnwdmB_ibEh_qC_ibEyosZ_ibE|hlfA_ibEt`dY_ibEnlY_ibEkbmD_ibEreaP_ibE}pfD_ibEa`mB_ibEjtwL_ibExgeM_ibEgh~S_ibEjs`J_ibEityc@_ibEm|rN_ibEzkd|@_ibEstxQ_ibEt~uA_ibE|ngP_ibEu|mB_ibEiw}K_ibEk_o[_ibEhpcV_ibEaq``A_ibEnonJ_ibEo}kdA_ibEj~|zA_ibErf}d@_ibEmetr@_ibEehhU_ibE|f~dA_ibExydE_ibEkilJ_ibEe_}O_ibElgeK_ibEx_eS_ibEs|{E_ibEyxnR_ibEzxmv@_ibEm@_ibEm@_ibEu`@_ibEpc@_ibEizB_ibEel@_ibEijE_ibE{wJ_ibEggM_ibEirY_ibErxf@_ibEaeU_ibEzmi@_ibEno@_ibEdl@_ibE_|i@_ibEa~nt@_ibEo|vC_ibEnqtc@_ibEemgJ_ibEcwmG_ibElkfH_ibEouxE_ibEpu~E_ibEce~cA_ibEq~ab@_ibErm`b@_ibEiy`i@_ibEnvgf@_ibEg`tdA_ibEz{ud@_ibE{fmxA_ibErmuD_ibEulh]_ibE|c}jB_ibEa{s~@_ibEyumbA_ibEvawfA_ibEaxtkA_ibExz~D_ibEqf{zC_ibElnnk@_ibEfql`@_ibE~kugA_ibE`|_~@_ibEj~kN_ibEu~x{A_ibE~_q_B_ibE||I_ibEb{e{@_ibEjyxX_ibEmilK_ibE_dsh@_ibElt}h@_ibEwuwT_ibEbvdS_ibElsl@_ibEeona@_ibEj~cY_ibEodyH_ibEezax@_ibEsv}aA_ibEqjnuB_ibEwczY_ibEwfuuG_ibEzwp}J_ibEu~n~@_ibEfjvwA_ibEorhzA_ibExhja@_ibEsihq@_ibEmmvH_ibE~`|d@_ibEr{{|B_ibEvqc|B_ibEdahJ_ibEcigu@_ibEugraC_ibEhlkoB_ibE`alq@_ibEgp|h@_ibEtrv@_ibEfg`|@_ibEwhE_ibE}pek@_ibE`rndA_ibE`~fY_ibEjkuJ_ibEgz_T_ibEmbyE_ibE`zh[_ibE}jlK_ibE|{vO_ibEejqP_ibEjtO_ibErs{N_ibExouN_ibE_|_J_ibEokeF_ibE}erM_ibE{`ruB_ibEvfwU_ibEmefrA_ibEzucfA_ibEopm~B_ibEc~qrA_ibEupcyK_ibEbohgC_ibEj{ekA_ibEo`m{A_ibEe|urF_ibEzffqA_ibEladyE_ibE{vwW_ibEztopE_ibEhwjK_ibE`{tJ_ibEtyzj@_ibEyhwaB_ibE`rxE_ibEdnwcB_ibEc}rxD_ibErw~fD_ibE_unr@_ibEr~veB_ibExkexA_ibEr_fbC_ibEjerH_ibEk_zi@_ibE}{j_@_ibEtzyyA_ibEgxxS_ibEsfbv@_ibEsyv_B_ibEsu~[_ibEtkz^_ibE_x`U_ibEkkbuD_ibEuoy]_ibEubkhB_ibEsymxA_ibE~`pkG_ibEpcuC_ibE}b|\_ibEts}Y_ibE}nwcB_ibEiealG_ibEdbbS_ibEbimtC_ibEugwmB_ibE`fqn@_ibEr{dxD_ibE_k}G_ibEepijB_ibEwimuF_ibEdon_O_ibEneo_B_ibEljzP_ibEgwxQ_ibEjomZ_ibEcjxlC_ibEjkm{F_ibEyvyN_ibEll}R_ibExcmD_ibEnvcg@_ibE}wH_ibEzu}B_ibE}`hd@_ibEo{jN_ibEsbeA_ibE|jun@_ibEirueA_ibEwcdoB_ibEzik`D_ibEdwhl@_ibE|fR_ibEgtlB_ibEiq|W_ibEfqa^_ibEmoj@_ibEn~uG_ibEsrk@_ibE~eY_ibEuvha@_ibEjr~\_ibEuwyM_ibE|myU_ibE{bM_ibEbg^_ibEwcD_ibEm`D_ibE}kP_ibEg|J_ibEpfO_ibEhkZ_ibEllE_ibEd_@_ibEceC_ibEs{d@_ibElnP_ibEunZ_ibEz}[_ibEzaC_ibE{wO_ibEfpL_ibEingC_ibEhsyA_ibEgzrL_ibErk~J_ibE|tf@_ibEinL_ibEh}q@_ibEdri@_ibE'
            ) geom
    ),
    cities as (
        select
            name, cast(lon as numeric) lon,
            cast(lat as numeric),
            round(cast(lat as numeric),1) as rounded_lat
        from (
            values (
                'Tokyo', 139.749462, 35.686963
            ),(
                'Delhi', 77.199980, 28.600023
            ),(
                'Shanghai', 121.434559, 31.218398
            ),(
                'São Paulo', -46.626966, -23.556734
            ),(
                'Mexico City', -99.132934, 19.444388
            ),(
                'Cairo', 31.248022, 30.051906
            ),(
                'Mumbai', 72.855043, 19.018936
            ),(
                'Beijing', 116.386340, 39.930838
            ),(
                'Dhaka', 90.406634, 23.725006
            ),(
                'Osaka', 135.458199, 34.751981
            ),(
                'New York', -73.981963, 40.751925
            ),(
                'Karachi', 67.01, 24.86
            ),(
                'Buenos Aires', -58.399477, -34.600556
            ),(
                'Chongqing', 106.5504, 29.5637
            ),(
                'Istanbul', 29.008056, 41.106942
            ),(
                'Kolkata',88.37, 22.5675
            ),(
                'Manila',120.980271, 14.606105
            ),(
                'Lagos',3.389585,6.445208
            ),(
                'Rio do Janeiro',-43.226967, -22.923077
            ),(
                'Tianjin', 117.2054, 39.1336
            )
        ) x (name, lon, lat)
    ),
    ellipsis as (
        select
            s,
            st_reverse(
                st_boundary(
                    st_scale(
                        st_rotate(
                            st_buffer(
                                st_point(0,0, 4326),
                                180.0
                            ),
                            radians(s),
                            st_point(0,0,4326)
                        ),    
                        1, 0.5
                    )
                )
            ) as geom
        from
            generate_series(0,360, 1) s
    ),
    pop_lines as (
        select
            s,
            st_setsrid(
                st_makeline(
                    array_agg(
                        st_translate(
                            st_force2d(pts),
                            0,
                            st_y(pops.geom)
                        ) order by p
                    )
                ),
            4326) as geom
        from
            ellipsis d
                join lateral
                    generate_series(0,360,1) p on true
                join lateral
                    st_lineinterpolatepoint(
                        d.geom,
                        p::numeric/360.0
                    ) pts on true
            left join (
                select
                    p.path[1]-1 as path, p.geom
                from
                    pop
                        join lateral
                            st_dumppoints(pop.geom) p on true
            ) pops on pops.path = p
        where
            s < 360
        group by
            d.s    
    ),
    city_locs as (
        select
            d.s,
            c.name,
            st_setsrid(st_makeline(st_point(0,90,4326), p),4326) as geom,
            deg,
            case
                /* label quadrant for qgis*/
                when deg >= 337.5 and deg < 22.5 then 1
                when deg >= 22.5 and deg < 67.5 then 2
                when deg >= 67.5 and deg < 112.5 then 5
                when deg >= 112.5 and deg < 157.5 then 8
                when deg >= 157.5 and deg < 202.5 then 7
                when deg >= 202.5 and deg < 247.5 then 6
                when deg >= 247.5 and deg < 292.5 then 3
                when deg >= 292.5 and deg < 337.5 then 0
            end as lbl_box                
        from
            cities c,
            ellipsis d
                join lateral
                    st_lineinterpolatepoint(
                        d.geom,
                        (round(c.lon,0)+180) / 360.0
                    ) p on true
                join lateral
                    degrees(
                        st_azimuth(
                            st_point(0,0,4326),
                            p
                        )
                    ) deg on true
    )
select
    row_number() over()::int as oid, s, name,
    ('2022.11.20 '||(((24.0 * s) / 360.0||' hours')::interval)::time)::timestamp as ts,
    geom, deg, lbl_box, cl
from (
    select
        s, geom, deg, lbl_box,
        case
            when lbl_box in (3,5,6,7,8) then name
            else ''
        end as name, 'cities' as cl
    from
        city_locs
    union all
    select
        s, geom, null::numeric as deg, null::int as lbl_box,
        null::varchar as name, 'pop_lines' as cl
    from
        pop_lines
) d
;