U.S. Counties Choropleth

Juan,

It is super easy to load any TopoJSON/GeoJSON file. Here is how I produced a map of Colombia’s Departments from a TopoJSON file I found on Github:

@vlplot(width=800, height=600) + 
@vlplot(
    mark={ 
        :geoshape,
        fill=:lightgrey,
        stroke=:white
    },
    data={
        url=URI("https://raw.githubusercontent.com/deldersveld/topojson/master/countries/colombia/colombia-departments.json"),
        format={
            typ=:topojson,
            feature=:COL_adm1
        }
    },
    projection={
        typ=:albers
    }
)

To take this one step further and generate a choropleth map, I found some data for 2017 populations and converted it into a .CSV file and then did the following:

col_pops = URI("https://gist.githubusercontent.com/mthelm85/fcd178ea16c7e89cbdeb593d0b315c00/raw/4354805731cf060242f5799d9767821640dea839/colombia_populations.csv")
@vlplot(width=800, height=600) + 
@vlplot(
    mark={ 
        :geoshape
    },
    data={
        url=URI("https://raw.githubusercontent.com/deldersveld/topojson/master/countries/colombia/colombia-departments.json"),
        format={
            typ=:topojson,
            feature=:COL_adm1
        }
    },
    transform=[{
        lookup="properties.NAME_1",
        from={
            data={
                url=col_pops,
                format={
                    typ=:csv
                }
            },
            key=:department,
            fields=["population"]
        }
    }],
    color={
        "population:q",
        scale={domain=[42777, 8000000], scheme=:reds},
        legend={title="Population"}
    },
    projection={
        typ=:albers
    }
)

Note that I couldn’t write NAME_1 as a symbol (:NAME_1) in lookup. The way that the shape file was written resulted in me having to use JavaScript dot notation "properties.NAME_1" (since JSON is JavaScript Object Notation) to access the correct lookup value (JSON object property) in the TopoJSON file.

6 Likes

Where is the list of all available countries and cities?
What about shp files?

I found the Colombia shape file by just Googling. There are also ways to build them yourself: Online GIS/CAD Data Converter | SHP, KML, KMZ, TAB, CSV, ...

What is the advantage of using this choroplet tools instead of packages specialized on geographic maps, cartography, gdal, etc.?

@mthelm85, very cool! And very glad that it appears to all work, I’m almost a bit surprised :slight_smile:

1 Like

This is awesome and you just answered one of the questions I was going to ask, because I wanted to use other data. And I’m very happy you used my country as an example, even though that map is certainly crooked.

1 Like

You can use grammar of graphics with this very easily. iirc other geo-tooling packages don’t provide the same functionality.

Parce :wink:, that’s because of the Albers projection. Changing it to a Mercator projection (switching :albers with :mercator in the code above) yields the following:

1 Like

Thank you for posting this example. I am struggling with this and will appreciate some help.

I am using a geojson file which I have downloaded: africa_geo.json.

Formatted the information up to the first country’s coordinates looks like this:

{
    "features": [
        {
            "properties": {
                "economy": "7. Least developed region",
                "iso_a3": "BEN",
                "mapcolor9": 2,
                "name_alt": null,
                "postal": "BJ",
                "wikipedia": -99,
                "homepart": 1,
                "long_len": 5,
                "abbrev": "Benin",
                "continent": "Africa",
                "note_brk": null,
                "tiny": -99,
                "formal_en": "Republic of Benin",
                "su_a3": "BEN",
                "adm0_a3_wb": -99,
                "name": "Benin",
                "subregion": "Western Africa",
                "un_a3": "204",
                "wb_a2": "BJ",
                "mapcolor13": 12,
                "su_dif": 0,
                "region_un": "Africa",
                "featurecla": "Admin-0 country",
                "subunit": "Benin",
                "brk_group": null,
                "wb_a3": "BEN",
                "filename": "BEN.geojson",
                "gdp_md_est": 12830,
                "woe_id_eh": 23424764,
                "iso_n3": "204",
                "woe_note": "Exact WOE match as country",
                "level": 2,
                "formal_fr": null,
                "pop_year": -99,
                "brk_a3": "BEN",
                "gu_a3": "BEN",
                "income_grp": "5. Low income",
                "adm0_a3": "BEN",
                "adm0_dif": 0,
                "iso_a2": "BJ",
                "geou_dif": 0,
                "abbrev_len": 5,
                "brk_name": "Benin",
                "adm0_a3_us": "BEN",
                "name_len": 5,
                "scalerank": 0,
                "admin": "Benin",
                "labelrank": 5,
                "lastcensus": 2002,
                "region_wb": "Sub-Saharan Africa",
                "adm0_a3_is": "BEN",
                "name_long": "Benin",
                "note_adm0": null,
                "sovereignt": "Benin",
                "brk_diff": 0,
                "geounit": "Benin",
                "pop_est": 8791832,
                "name_sort": "Benin",
                "woe_id": 23424764,
                "mapcolor8": 2,
                "adm0_a3_un": -99,
                "mapcolor7": 1,
                "gdp_year": -99,
                "sov_a3": "BEN",
                "type": "Sovereign country",
                "fips_10_": "BN"
            },
            "geometry": {
                "coordinates": [
                    [
                        [

]

My first problem is that I am not sure what should be used as feature= in the data section.

This code produced an empty graphic:

julia> africa = "/home/js/db_docs/programmatic_indicators/geojson/africa_geo.json"
"/home/js/db_docs/programmatic_indicators/geojson/africa_geo.json"

julia> @vlplot(width=800, height=600) + 
       @vlplot(
           mark={ 
               :geoshape,
               fill=:lightgrey,
               stroke=:white
           },
           data={
               url=africa,
               format={
                   typ=:geojson,
                   feature=:geometry
               }
           },
           projection={
               typ=:naturalEarth1
           }
       )

I want to do on the Africa map what your example did using USA data. But I don’t even get as far as the first step.

I’m struggling with the GeoJSON file at the link you posted too, Johann. A couple of things:

  1. :geojson wouldn’t be a valid option. Per the VegaLite docs, JSON :json and TopoJSON :topojson are the only valid options (for JSON files).
  2. Since I couldn’t get the original file to work, I converted it to TopoJSON and I still can’t get it to work.
  3. Loading both files (geo and topo) into mapshaper.org worked just fine, so it appears that the files are okay, they just don’t play well with VegaLite for some reason.
  4. I don’t believe :naturalEarth1 is a valid projection. The Vega documentation has a list of all valid projection types.

Lastly, I was able to get it to display some geometrical shapes, but they didn’t look anything like Africa, no matter what projection type I chose :rofl: I’m wondering if maybe a projection has already been applied to the data so when VegaLite tries to apply another projection (default is mercator), this is what’s throwing it off and causing the crazy shapes I’m getting…?

Thanks Matt for confirming my problem.

By the way, I found that naturarlEarth1 projection added was added to versions > v4.0.

Nice, thanks for sharing. FYI, here’s a TopoJSON file of Africa that works.

@vlplot(width=800, height=600) + 
@vlplot(
    mark={ 
        :geoshape,
        fill=:lightgrey,
        stroke=:white
    },
    data={
        url=URI("https://raw.githubusercontent.com/deldersveld/topojson/master/continents/africa.json"),
        format={
            typ=:topojson,
            feature=:continent_Africa_subunits
        }
    },
    projection={
        typ=:mercator
    }
)

Thanks. That helped.

But that topo-file did not contain iso_a3 codes for the countries. I managed to create a custom one which contains that data with the help of geojson-maps.ash.ms and mapshaper.org.

My next task is to plot the country codes in the countries. The information is in the topology.json but I do not know at this stage to use that information.

Otherwise I will have to try and overlay this map on a base map which contains the country names. Will have to find out how to do that.

So far I have this output:

In the topo.json I have

julia> a = JSON.parsefile("custom.topo.json")
Dict{String,Any} with 4 entries:
  "arcs"      => Any[Any[Any[131242, 106085], Any[-78, -99], Any[-31, -51], Any[-89, -219], Any[-19, -76], Any[…
  "objects"   => Dict{String,Any}("custom"=>Dict{String,Any}("geometries"=>Any[Dict{String,Any}("arcs"=>Any[Any…
  "type"      => "Topology"
  "transform" => Dict{String,Any}("translate"=>Any[-25.3604, -46.9658],"scale"=>Any[0.000426044, 0.000420089])

julia> a["objects"]["custom"]["geometries"][1]["properties"]["iso_a3"]
"BDI"

How do I get BDI to be printed in the correct country?

So I took a look at the Vega docs and there is a getCentroid function which would allow you to compute the central point of each shape (country). This could then be used for placement of text marks that contain whatever info you want. I played with it for about 20 minutes and was unable to get it working, so please post your resolution if you find one!

There is a github post here that demonstrates a basic structure for computing and using centroids with Vega.

I will only be able to experiment further on Monday. Thanks for the information.

I am not winning. In the example the data sources are used in an inverse manner from what I have done with the Africa data and I have been unsuccessful in my efforts to do it the other way round. I simply do not really understand the background processes well enough and the documentation (and lack of examples) does not help.

I opened an issue on the VegaLite.jl GitHub repo, so hopefully the package authors can work this issue out.

1 Like

There is also:

3 Likes

I know it’s been a while since this question was posted, but it took me a while to plot a choropleth from a DataFrame so I’ll post the way I do it by using Shapefile.jl and Plots.jl

I hope this could help someone out there. The resulting map without working so much on the aesthetics is the following:

using Plots
using Shapefile
using DataFrames
using Colors

#building a custom categorical colorbar
colorbar = cgrad(["#FFFFFF", "#ffffcc", "#a1dab4", "#41b6c4", "#253494"], categorical = true)

#loading the shapefile 
shape = Shapefile.Table("AMVA/AreaMetropol.shp");

#Building the DataFrame from the shapefile polygons and names
data = DataFrame("zone" => shape.Name, "geometry" => Shapefile.shapes(shape));

#The trick is to transform each of the polygons to Julia Shapes
plot_shapes = []

#so each polygon is iterated to extect its vertices x and y coordinates
for poly in data.geometry
    
    vertices_x = [point.x for point in poly.points]
    vertices_y = [point.y for point in poly.points]
    
    #Then those x and y coordinates are transformed into Shapes
    push!(plot_shapes, Shape(vertices_x, vertices_y))
    
end

#Add the new Shapes to the DataFrame
data[:, "plots_shapes"] .= plot_shapes;

#Assign the values for each feature to the DataFrame, in this case random integers are used
data[:, "value"] = rand(0:10, 10);

#Finally the array of shapes are given to the plot function and its values are used in the fill_z argument
plot(data.plots_shapes, fill_z = data.value, size = (600, 650), lw = .1, xtickfontsize = 15, 
    ytickfontsize = 15, titlefontsize = 20, xlabel = "Longitud [°]", ylabel = "Latitud [°]", guidefontsize = 15, 
    color = colorbar, vmin = 1, dpi = 200, label = "")


11 Likes

Thanks for posting this, it should me out!