skip to Main Content

I have a set of 1000 coordinates, with each representing the urban centres of a particular state. These towns belong to one of 20 counties and I know which towns belong to which county. I wish to shade a map based on data I have for each county. For example, if a county has a value of 100% then I wish to color this county dark red, if a county is associated with 0% then I will color this part of the map white.

I don’t have the boundaries for each county since these are old counties and it would involve a huge amount of work to trace the boundaries from old maps. I have the boundaries of the state including islands and lakes etc.

So here is the data I have:

Town1 50.1,4.89 County1    
Town2 49.9,4.78 County1    
Town3 50.3,4.59 County1    
Town4 50.2,4.99 County1    
Town5 50.0,4.99 County1    
...
Town1000 57.0,8.33 County20 

and

County1 100%   
County2 100%   
County3 68%   
...
County20 0% 

as well as the state boundaries.

Solution1: So, one approach to create my desired map might be to create polygons around each coordinate (town), with this polygon representing all the area on the map closest to this town, and closer to no other town. Then I color this polygon according to the data of its county.

Solution2: Perhaps a better approach would be to blend colors between towns. So if I have two adjacent towns in different counties, one with 100% and one with 0%, then the half way point between them would be coloured pink (half way between dark red and white).

So I wish to programatically produce this map in the form of an image file where this file is easily scalable and where I can import it into the likes of Photoshop in order to add other elements. Would you recommend SVG in this case?

What library or algorithm might I use to produce the polygons as required in Solution 1?

Is there a library I can use to produce an SVG document with a gradient like mesh as required by Solution 2?

I wish to use Python3 if possible but I am open to other languages. I am also open to other solutions and alternatives to SVG.

I am using MacOS.

3

Answers


  1. Your first approach is called a Voronoi diagramm

    See the description in wikipedia

    There is a solution for this kind of diagramms using D3 library for javascipt

    D3 approach

    Just to make this solution complete, I am pasting here the code from M.Bostock example

    var w = 1280,
        h = 800;
    
    var projection = d3.geo.azimuthal()
        .mode("equidistant")
        .origin([-98, 38])
        .scale(1400)
        .translate([640, 360]);
    
    var path = d3.geo.path()
        .projection(projection);
    
    var svg = d3.select("body").insert("svg:svg", "h2")
        .attr("width", w)
        .attr("height", h);
    
    var states = svg.append("svg:g")
        .attr("id", "states");
    
    var cells = svg.append("svg:g")
        .attr("id", "cells");
    
    d3.json("us-states.json", function(collection) {
      states.selectAll("path")
          .data(collection.features)
        .enter().append("svg:path")
          .attr("d", path);
    });
    
    d3.csv("airports.csv", function(airports) {
      var positions = [];
    
      airports.forEach(function(airport) {
        positions.push(projection([+airport.longitude, +airport.latitude]));
      });
    
      // Compute the Voronoi diagram of airports' projected positions.
      var polygons = d3.geom.voronoi(positions);
    
      var g = cells.selectAll("g")
          .data(airports)
        .enter().append("svg:g");
    
      g.append("svg:path")
          .attr("class", "cell")
          .attr("d", function(d, i) { return "M" + polygons[i].join("L") + "Z"; })
          .on("mouseover", function(d, i) {
            d3.select("#footer span").text(d.name);
            d3.select("#footer .hint").text(d.city + ", " + d.state);
          });
    
      g.append("svg:circle")
          .attr("cx", function(d, i) { return positions[i][0]; })
          .attr("cy", function(d, i) { return positions[i][1]; })
          .attr("r", 1.5);
    });
    

    Your second solution is easily achievable with OpenGL and Gouraud shading, but then it’s not easy to export it to SVG (or to anything else beyond a bitmap). I will think about any alternative to it.

    Login or Signup to reply.
  2. While there are powerful frameworks for generic data representation, the proper tool for this particular task is GIS. I’ll try explain how to do this in QGIS.

    1. Prepare your data for import to QGIS.

    Create a CSV file with your data in the following format:

    Town,X,Y,County,Value
    Town1,50.1,4.89,County1,100
    Town2,49.9,4.78,County1,100
    Town3,50.3,4.59,County1,100
    Town4,50.2,4.99,County1,100
    Town5,50.0,4.99,County1,100
    ...
    Town1000,57.0,8.33,County20,0
    
    1. Add it as a CSV (Delimited Text) layer

      • Layer -> Add layer -> Add Delimited Text Layer
    2. Customize the style of created layer to get a heatmap like you describe in your second variant.

      • Right click on the layer title on the layers panel (left bottom) -> Properties -> Symbology
      • Change the “Single symbol” dropdown to “Heatmap”
      • Click “Apply”
      • Select “Value” field (remember it in CSV?) in the “Weight points by”
      • “Apply”
      • Tune other parameters (color ramp, radius) for your needs
    3. Create a Voronoi polygons from this layer

      • Vector -> Geometry Tools -> Voronoi polygons
      • Click “…” near the destination layer path input box, choose “Save to file”, save it to GeoJSON layer (QGIS doesn’t support polygon layers in CSV, you can choose other format if you understand).
      • Click “Run in background”.
    4. Customize the style of newly created layer to get regions of each county colored by the different colors.

      • Right click on the layer title on the layers panel (left bottom) -> Properties -> Symbology
      • Select “Categorized” instead of “Single symbol”
      • Select “County” in the “Column” dropdown
      • Click “Classify” to assign colors to all counties
      • “Apply”
    5. To create an SVG from current map:

      • Project -> New Print Layout

      • Click the “Add a new map to the layout” button on the tool panel, draw the large rectangle of map to place it on the layout.

      • Click “Export as SVG”

    That’s all.

    Black-to-transparent heatmap over Voronoi polygons – https://svgshare.com/s/5MZ.

    Heatmap only – https://svgshare.com/s/5ND.

    Script which I used to generate data – https://gist.github.com/ei-grad/1355223cd8a3c6ba16deb454ddef50b4.

    Login or Signup to reply.
  3. For Solution1 in python, you can use Voronoi Diagrams in scipy. The following code (a modified version of this SO post) creates a voronoi diagram, draws it using different alphas according to the values of each country and saves it to image Map.png:

    import matplotlib.pyplot as plt
    import numpy as np
    from scipy.spatial import Voronoi, voronoi_plot_2d
    import random
    
    # make up data points
    points = np.random.rand(15,2)
    values = np.random.uniform(low=0.0, high=1.0, size=(len(points),))
    
    # compute Voronoi tesselation
    vor = Voronoi(points)
    
    # plot
    voronoi_plot_2d(vor)
    
    # colorize
    for region, value in zip(vor.regions, values):
        if not -1 in region:
            polygon = [vor.vertices[i] for i in region]
            plt.fill(*zip(*polygon), color='r',  alpha=value)
    
    plt.savefig('Map.png')
    

    Your points should be loaded in the points variable in line 7 and the country’s percentages in the values variable in line 8.

    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search