function editWorld() {
  if (customization) return;
  $("#worldConfigurator").dialog({title: "Configure World", resizable: false, width: "42em",
    buttons: {
      "Whole World": () => applyWorldPreset(100, 50),
      "Northern": () => applyWorldPreset(33, 25),
      "Tropical": () => applyWorldPreset(33, 50),
      "Southern": () => applyWorldPreset(33, 75),
      "Restore Winds": restoreDefaultWinds
    }, open: function() {
      const buttons = $(this).dialog("widget").find(".ui-dialog-buttonset > button");
      buttons[0].addEventListener("mousemove", () => tip("Click to set map size to cover the whole World"));
      buttons[1].addEventListener("mousemove", () => tip("Click to set map size to cover the Northern latitudes"));
      buttons[2].addEventListener("mousemove", () => tip("Click to set map size to cover the Tropical latitudes"));
      buttons[3].addEventListener("mousemove", () => tip("Click to set map size to cover the Southern latitudes"));
      buttons[4].addEventListener("mousemove", () => tip("Click to restore default wind directions"));
    }
  });

  const globe = d3.select("#globe");
  const clr = d3.scaleSequential(d3.interpolateSpectral);
  const tMax = +temperatureEquatorOutput.max, tMin = +temperatureEquatorOutput.min; // temperature extremes
  const projection = d3.geoOrthographic().translate([100, 100]).scale(100);
  const path = d3.geoPath(projection);

  updateGlobeTemperature();
  updateGlobePosition();

  if (modules.editWorld) return;
  modules.editWorld = true;

  document.getElementById("worldControls").addEventListener("input", (e) => updateWorld(e.target));
  globe.select("#globeWindArrows").on("click", changeWind);
  globe.select("#globeGraticule").attr("d", round(path(d3.geoGraticule()()))); // globe graticule
  updateWindDirections();

  function updateWorld(el) {
    if (el) {
      document.getElementById(el.dataset.stored+"Input").value = el.value;
      document.getElementById(el.dataset.stored+"Output").value = el.value;
      if (el.dataset.stored) lock(el.dataset.stored);
    }

    updateGlobeTemperature();
    updateGlobePosition();
    calculateTemperatures();
    generatePrecipitation();
    const heights = new Uint8Array(pack.cells.h);
    Rivers.generate();
    Lakes.defineGroup();
    Rivers.specify();
    pack.cells.h = new Float32Array(heights);
    defineBiomes();

    if (layerIsOn("toggleTemp")) drawTemp();
    if (layerIsOn("togglePrec")) drawPrec();
    if (layerIsOn("toggleBiomes")) drawBiomes();
    if (layerIsOn("toggleCoordinates")) drawCoordinates();
    if (document.getElementById("canvas3d")) setTimeout(ThreeD.update(), 500);
  }

  function updateGlobePosition() {
    const size = +document.getElementById("mapSizeOutput").value;
    const eqD = graphHeight / 2 * 100 / size;

    calculateMapCoordinates();
    const mc = mapCoordinates; // shortcut
    const scale = +distanceScaleInput.value, unit = distanceUnitInput.value;
    const meridian = toKilometer(eqD * 2 * scale);
    document.getElementById("mapSize").innerHTML = `${graphWidth}x${graphHeight}`;
    document.getElementById("mapSizeFriendly").innerHTML = `${rn(graphWidth * scale)}x${rn(graphHeight * scale)} ${unit}`;
    document.getElementById("meridianLength").innerHTML = rn(eqD * 2);
    document.getElementById("meridianLengthFriendly").innerHTML = `${rn(eqD * 2 * scale)} ${unit}`;
    document.getElementById("meridianLengthEarth").innerHTML = meridian ? " = " + rn(meridian / 200) + "%🌏" : "";
    document.getElementById("mapCoordinates").innerHTML = `${lat(mc.latN)} ${Math.abs(rn(mc.lonW))}°W; ${lat(mc.latS)} ${rn(mc.lonE)}°E`;

    function toKilometer(v) {
      if (unit === "km") return v;
      else if (unit === "mi") return v * 1.60934;
      else if (unit === "lg") return v * 5.556;
      else if (unit === "vr") return v * 1.0668;
      return 0; // 0 if distanceUnitInput is a custom unit
    }

    function lat(lat) {return lat > 0 ? Math.abs(rn(lat)) + "°N" : Math.abs(rn(lat)) + "°S";} // parse latitude value
    const area = d3.geoGraticule().extent([[mc.lonW, mc.latN], [mc.lonE, mc.latS]]);
    globe.select("#globeArea").attr("d", round(path(area.outline()))); // map area
  }

  function updateGlobeTemperature() {
    const tEq = +document.getElementById("temperatureEquatorOutput").value;
    document.getElementById("temperatureEquatorF").innerHTML = rn(tEq * 9/5 + 32);
    const tPole = +document.getElementById("temperaturePoleOutput").value;
    document.getElementById("temperaturePoleF").innerHTML = rn(tPole * 9/5 + 32);
    globe.selectAll(".tempGradient90").attr("stop-color", clr(1 - (tPole - tMin) / (tMax - tMin)));
    globe.selectAll(".tempGradient60").attr("stop-color", clr(1 - (tEq - (tEq - tPole) * 2/3 - tMin) / (tMax - tMin)));
    globe.selectAll(".tempGradient30").attr("stop-color", clr(1 - (tEq - (tEq - tPole) * 1/3 - tMin) / (tMax - tMin)));
    globe.select(".tempGradient0").attr("stop-color", clr(1 - (tEq - tMin) / (tMax - tMin)));
  }

  function updateWindDirections() {
    globe.select("#globeWindArrows").selectAll("path").each(function(d, i) {
      const tr = parseTransform(this.getAttribute("transform"));
      this.setAttribute("transform", `rotate(${options.winds[i]} ${tr[1]} ${tr[2]})`);
    });
  }

  function changeWind() {
    const arrow = d3.event.target.nextElementSibling;
    const tier = +arrow.dataset.tier;
    options.winds[tier] = (options.winds[tier] + 45) % 360;
    const tr = parseTransform(arrow.getAttribute("transform"));
    arrow.setAttribute("transform", `rotate(${options.winds[tier]} ${tr[1]} ${tr[2]})`);
    localStorage.setItem("winds", options.winds);
    const mapTiers = d3.range(mapCoordinates.latN, mapCoordinates.latS, -30).map(c => (90-c) / 30 | 0);
    if (mapTiers.includes(tier)) updateWorld();
  }

  function restoreDefaultWinds() {
    const defaultWinds = [225, 45, 225, 315, 135, 315];
    const mapTiers = d3.range(mapCoordinates.latN, mapCoordinates.latS, -30).map(c => (90-c) / 30 | 0);
    const update = mapTiers.some(t => options.winds[t] != defaultWinds[t]);
    options.winds = defaultWinds;
    updateWindDirections();
    if (update) updateWorld();
  }

  function applyWorldPreset(size, lat) {
    document.getElementById("mapSizeInput").value = document.getElementById("mapSizeOutput").value = size;
    document.getElementById("latitudeInput").value = document.getElementById("latitudeOutput").value = lat;
    lock("mapSize");
    lock("latitude");
    updateWorld();
  }
}