World Map

Updated: 2020-05-07

There are many online services to log the places you've visited, why not build one on your own?

This would be our first baby step to build an online product: a dead simple locally served web page.

Create a HTML page

<html>
  <head></head>
  <body>
    <h1>My World map</h1>
  </body>
</html>

Save it to index.html, since it has nothing but a simple header (<h1></h1>), it can be directly open in a browser.

Prep

Our dependencies: D3.js, GeoJson, Python HTTP Server.

Add a <div> and give it an id map.

<body>
  <h1>My World map</h1>
  <div id="map"></div>
</body>

It is empty now and will be filled by some Javascript code. To do that we need to use D3, a popular data visualization library.

<head>
  <script src="https://d3js.org/d3.v5.min.js"></script>
  <script>
    // next: add our own code here
  </script>
</head>

Another missing piece is the data, or the contours of the countries. There are many versions of such data online, we are looking for a geojson file so we do not need to do further processing. Here we are using the one from https://github.com/johan/world.geo.json/blob/master/countries.geo.json

I'm downloading the file and saving it in the same folder as the html file; alternatively you can direct load it from the link.

<script>
  async function main() {
    const response = await fetch("/countries.geo.json");
    const data = await response.json();

    // check the data
    console.log(data);

    // more code to come ...
  }
  main();
</script>

Now opening the file in browser no longer works, we need a "server" to serve both the html and the data files. Python is available everywhere, and it comes with a simple HTTP server. Note that we are using Python 3, since 2 is officially deprecated, if you use python instead of python3 in the command line, check the version first:

$ python3 -m http.server 8000

Now open the browser and go to localhost:8000, hopefully Chrome, then in the developer tool, you can see the output of the console.log().

Draw the map

const width = 1260,
  height = 800;

const projection = d3
  .geoMercator()
  .translate([width / 2, height / 2])
  .scale(150);

const canvas = d3
  .select("#map")
  .append("canvas")
  .attr("width", width)
  .attr("height", height);

const context = canvas.node().getContext("2d");
context.lineWidth = 0.5;
context.strokeStyle = "#333";

const path = d3.geoPath().projection(projection).context(context);

// draw the world map!
context.beginPath();
path(data);
context.stroke();

Highlight The Visited Countries

Replace the "draw the world map!" part with this:

const visited = ["CHN", "TWN", "USA", "GBR"];
data.features.forEach((feature) => {
  context.beginPath();
  path(feature);
  context.fillStyle = visited.includes(feature.id) ? "brown" : "#DDD";
  context.fill();
  context.stroke();
  context.closePath();
});

Put Everything Together

<html>
  <head>
    <script src="https://d3js.org/d3.v5.min.js"></script>
    <script>
      async function main() {
        const response = await fetch("/countries.geo.json");
        const data = await response.json();
        const width = 1260,
          height = 800;

        const projection = d3
          .geoMercator()
          .translate([width / 2, height / 2])
          .scale(150);

        const canvas = d3
          .select("#map")
          .append("canvas")
          .attr("width", width)
          .attr("height", height);

        const context = canvas.node().getContext("2d");
        context.lineWidth = 0.5;
        context.strokeStyle = "black";

        const path = d3.geoPath().projection(projection).context(context);

        const visited = ["CHN", "TWN", "USA", "GBR"];
        data.features.forEach((feature) => {
          context.beginPath();
          path(feature);
          context.fillStyle = visited.includes(feature.id) ? "brown" : "#DDD";
          context.fill();
          context.stroke();
          context.closePath();
        });
      }

      main();
    </script>
  </head>
  <body>
    <h1>My World map</h1>
    <div id="map"></div>
  </body>
</html>