7. Making a Web Map (Draft UNDER CONSTRUCTION, probably broken)

This section shows you how to build an interactive web map using:

  • Leaflet.js for mapping
  • Turf.js for spatial analysis
  • Custom and open basemaps
  • GeoJSON data you created earlier

We’ll start simple and progressively add new capabilities.


Part 1: Basic Map with Pumps and Deaths

Start with a Leaflet map showing your digitized water pumps and cholera deaths as proportional symbols.

<!-- Part 1: Basic Leaflet Map with Pumps and Deaths -->
<!DOCTYPE html>
<html>
<head>
  <title>Snow Cholera Map</title>
  <meta charset="utf-8" />
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <link rel="stylesheet" href="https://unpkg.com/leaflet@1.9.4/dist/leaflet.css" />
  <script src="https://unpkg.com/leaflet@1.9.4/dist/leaflet.js"></script>
  <style> body { margin: 0; } #map { height: 100vh; } </style>
</head>
<body>
  <div id="map"></div>
  <script>
    const map = L.map('map').setView([51.513, -0.137], 17);
    L.tileLayer('https://a.basemaps.cartocdn.com/light_all/{z}/{x}/{y}.png', {
      attribution: '© CartoDB, OSM contributors'
    }).addTo(map);

    // Water pumps
    fetch('snow_map_pumps.geojson')
      .then(res => res.json())
      .then(data => {
        L.geoJSON(data, {
          pointToLayer: (feature, latlng) => L.circleMarker(latlng, {
            radius: 10, color: 'white', fillColor: 'blue',
            fillOpacity: 1, weight: 2
          }).bindPopup(`Pump: ${feature.properties.label}`)
        }).addTo(map);
      });

    // Cholera deaths
    fetch('snow_map_deaths.geojson')
      .then(res => res.json())
      .then(data => {
        L.geoJSON(data, {
          pointToLayer: (feature, latlng) => {
            const deaths = feature.properties.Num_Cases || 1;
            return L.circleMarker(latlng, {
              radius: Math.sqrt(deaths) * 4,
              fillColor: 'red', color: 'darkred',
              weight: 1, fillOpacity: 0.7
            }).bindPopup(`Deaths: ${deaths}`);
          }
        }).addTo(map);
      });
  </script>
</body>
</html>

Part 2: Adding the Snow Map Raster Tile Layer

Replace or add to the basemap using the historical John Snow map served via Allmaps.xyz.

Add the following after initializing the map:

// Add historical map as a tile layer
const snowMapTiles = L.tileLayer('https://allmaps.xyz/images/45bbc7ea6e45e5c9/{z}/{x}/{y}.png', {
  attribution: 'John Snow map via Allmaps',
  opacity: 0.8
});
snowMapTiles.addTo(map); // optional to start with historical view

You can toggle between this and the Carto base layer later using controls (see Part 4).


Part 3: Enhancing with Turf.js (Voronoi + Weighted Mean)

Add Turf.js to perform spatial analysis: build Voronoi regions around pumps and compute a weighted spatial mean of cholera deaths.

Add this script in the <head> section:

<script src="https://unpkg.com/@turf/turf@6.5.0/turf.min.js"></script>

Then insert this logic inside the <script> after loading pump and death data:

// Pump Voronoi polygons
fetch('snow_map_pumps.geojson')
  .then(res => res.json())
  .then(pumpData => {
    const voronoi = turf.voronoi(pumpData, {
      bbox: [-0.144, 51.509, -0.13, 51.517]
    });

    L.geoJSON(voronoi, {
      style: {
        color: 'red',
        weight: 2,
        fillOpacity: 0.01
      }
    }).addTo(map);
  });

// Weighted spatial mean of deaths
fetch('snow_map_deaths.geojson')
  .then(res => res.json())
  .then(deathData => {
    const weighted = deathData.features.map(f => ({
      ...f,
      properties: {
        ...f.properties,
        weight: f.properties.Num_Cases || 1
      }
    }));

    const center = turf.centerMean(turf.featureCollection(weighted), {
      weight: "weight"
    });

    L.geoJSON(center, {
      pointToLayer: (feature, latlng) => {
        return L.circleMarker(latlng, {
          radius: 10,
          color: 'black',
          fillColor: 'yellow',
          fillOpacity: 0.9
        }).bindPopup("Weighted spatial mean of deaths");
      }
    }).addTo(map);
  });

Part 4: Add Layer Groups and Toggle Controls

To allow toggling layers on/off, define LayerGroups and use L.control.layers().

Step 1: Create empty LayerGroups

Before fetching data, define these groups:

const pumpLayer = L.layerGroup().addTo(map);
const deathLayer = L.layerGroup().addTo(map);
const voronoiLayer = L.layerGroup().addTo(map);
const meanLayer = L.layerGroup().addTo(map);

Step 2: Add features to those groups

When creating layers, replace .addTo(map) with .addTo(pumpLayer), .addTo(deathLayer), etc.

Step 3: Add layer controls at the end

const baseMaps = {
  "CARTO Positron": osm,
  "Snow Map (Historical)": snowMapTiles
};

const overlayMaps = {
  "Water Pumps": pumpLayer,
  "Cholera Deaths": deathLayer,
  "Voronoi Diagram": voronoiLayer,
  "Weighted Mean Center": meanLayer
};

L.control.layers(baseMaps, overlayMaps).addTo(map);

results matching ""

    No results matching ""