<div id="map"></div>
body,
html {
  height: 100%;
}

#map {
  width: 100%;
  height: 100%;
}
function get_html_translation_table(table, quote_style) {
  //  discuss at: http://phpjs.org/functions/get_html_translation_table/
  // original by: Philip Peterson
  //  revised by: Kevin van Zonneveld (http://kevin.vanzonneveld.net)
  // bugfixed by: noname
  // bugfixed by: Alex
  // bugfixed by: Marco
  // bugfixed by: madipta
  // bugfixed by: Brett Zamir (http://brett-zamir.me)
  // bugfixed by: T.Wild
  // improved by: KELAN
  // improved by: Brett Zamir (http://brett-zamir.me)
  //    input by: Frank Forte
  //    input by: Ratheous
  //        note: It has been decided that we're not going to add global
  //        note: dependencies to php.js, meaning the constants are not
  //        note: real constants, but strings instead. Integers are also supported if someone
  //        note: chooses to create the constants themselves.
  //   example 1: get_html_translation_table('HTML_SPECIALCHARS');
  //   returns 1: {'"': '&quot;', '&': '&amp;', '<': '&lt;', '>': '&gt;'}

  var entities = {},
    hash_map = {},
    decimal;
  var constMappingTable = {},
    constMappingQuoteStyle = {};
  var useTable = {},
    useQuoteStyle = {};

  // Translate arguments
  constMappingTable[0] = "HTML_SPECIALCHARS";
  constMappingTable[1] = "HTML_ENTITIES";
  constMappingQuoteStyle[0] = "ENT_NOQUOTES";
  constMappingQuoteStyle[2] = "ENT_COMPAT";
  constMappingQuoteStyle[3] = "ENT_QUOTES";

  useTable = !isNaN(table)
    ? constMappingTable[table]
    : table
    ? table.toUpperCase()
    : "HTML_SPECIALCHARS";
  useQuoteStyle = !isNaN(quote_style)
    ? constMappingQuoteStyle[quote_style]
    : quote_style
    ? quote_style.toUpperCase()
    : "ENT_COMPAT";

  if (useTable !== "HTML_SPECIALCHARS" && useTable !== "HTML_ENTITIES") {
    throw new Error("Table: " + useTable + " not supported");
    // return false;
  }

  entities["38"] = "&amp;";
  if (useTable === "HTML_ENTITIES") {
    entities["160"] = "&nbsp;";
    entities["161"] = "&iexcl;";
    entities["162"] = "&cent;";
    entities["163"] = "&pound;";
    entities["164"] = "&curren;";
    entities["165"] = "&yen;";
    entities["166"] = "&brvbar;";
    entities["167"] = "&sect;";
    entities["168"] = "&uml;";
    entities["169"] = "&copy;";
    entities["170"] = "&ordf;";
    entities["171"] = "&laquo;";
    entities["172"] = "&not;";
    entities["173"] = "&shy;";
    entities["174"] = "&reg;";
    entities["175"] = "&macr;";
    entities["176"] = "&deg;";
    entities["177"] = "&plusmn;";
    entities["178"] = "&sup2;";
    entities["179"] = "&sup3;";
    entities["180"] = "&acute;";
    entities["181"] = "&micro;";
    entities["182"] = "&para;";
    entities["183"] = "&middot;";
    entities["184"] = "&cedil;";
    entities["185"] = "&sup1;";
    entities["186"] = "&ordm;";
    entities["187"] = "&raquo;";
    entities["188"] = "&frac14;";
    entities["189"] = "&frac12;";
    entities["190"] = "&frac34;";
    entities["191"] = "&iquest;";
    entities["192"] = "&Agrave;";
    entities["193"] = "&Aacute;";
    entities["194"] = "&Acirc;";
    entities["195"] = "&Atilde;";
    entities["196"] = "&Auml;";
    entities["197"] = "&Aring;";
    entities["198"] = "&AElig;";
    entities["199"] = "&Ccedil;";
    entities["200"] = "&Egrave;";
    entities["201"] = "&Eacute;";
    entities["202"] = "&Ecirc;";
    entities["203"] = "&Euml;";
    entities["204"] = "&Igrave;";
    entities["205"] = "&Iacute;";
    entities["206"] = "&Icirc;";
    entities["207"] = "&Iuml;";
    entities["208"] = "&ETH;";
    entities["209"] = "&Ntilde;";
    entities["210"] = "&Ograve;";
    entities["211"] = "&Oacute;";
    entities["212"] = "&Ocirc;";
    entities["213"] = "&Otilde;";
    entities["214"] = "&Ouml;";
    entities["215"] = "&times;";
    entities["216"] = "&Oslash;";
    entities["217"] = "&Ugrave;";
    entities["218"] = "&Uacute;";
    entities["219"] = "&Ucirc;";
    entities["220"] = "&Uuml;";
    entities["221"] = "&Yacute;";
    entities["222"] = "&THORN;";
    entities["223"] = "&szlig;";
    entities["224"] = "&agrave;";
    entities["225"] = "&aacute;";
    entities["226"] = "&acirc;";
    entities["227"] = "&atilde;";
    entities["228"] = "&auml;";
    entities["229"] = "&aring;";
    entities["230"] = "&aelig;";
    entities["231"] = "&ccedil;";
    entities["232"] = "&egrave;";
    entities["233"] = "&eacute;";
    entities["234"] = "&ecirc;";
    entities["235"] = "&euml;";
    entities["236"] = "&igrave;";
    entities["237"] = "&iacute;";
    entities["238"] = "&icirc;";
    entities["239"] = "&iuml;";
    entities["240"] = "&eth;";
    entities["241"] = "&ntilde;";
    entities["242"] = "&ograve;";
    entities["243"] = "&oacute;";
    entities["244"] = "&ocirc;";
    entities["245"] = "&otilde;";
    entities["246"] = "&ouml;";
    entities["247"] = "&divide;";
    entities["248"] = "&oslash;";
    entities["249"] = "&ugrave;";
    entities["250"] = "&uacute;";
    entities["251"] = "&ucirc;";
    entities["252"] = "&uuml;";
    entities["253"] = "&yacute;";
    entities["254"] = "&thorn;";
    entities["255"] = "&yuml;";
  }

  if (useQuoteStyle !== "ENT_NOQUOTES") {
    entities["34"] = "&quot;";
  }
  if (useQuoteStyle === "ENT_QUOTES") {
    entities["39"] = "&#39;";
  }
  entities["60"] = "&lt;";
  entities["62"] = "&gt;";

  // ascii decimals to real symbols
  for (decimal in entities) {
    if (entities.hasOwnProperty(decimal)) {
      hash_map[String.fromCharCode(decimal)] = entities[decimal];
    }
  }

  return hash_map;
}

function html_entity_decode(string, quote_style) {
  //  discuss at: http://phpjs.org/functions/html_entity_decode/
  // original by: john (http://www.jd-tech.net)
  //    input by: ger
  //    input by: Ratheous
  //    input by: Nick Kolosov (http://sammy.ru)
  // improved by: Kevin van Zonneveld (http://kevin.vanzonneveld.net)
  // improved by: marc andreu
  //  revised by: Kevin van Zonneveld (http://kevin.vanzonneveld.net)
  //  revised by: Kevin van Zonneveld (http://kevin.vanzonneveld.net)
  // bugfixed by: Onno Marsman
  // bugfixed by: Brett Zamir (http://brett-zamir.me)
  // bugfixed by: Fox
  //  depends on: get_html_translation_table
  //   example 1: html_entity_decode('Kevin &amp; van Zonneveld');
  //   returns 1: 'Kevin & van Zonneveld'
  //   example 2: html_entity_decode('&amp;lt;');
  //   returns 2: '&lt;'

  var hash_map = {},
    symbol = "",
    tmp_str = "",
    entity = "";
  tmp_str = string.toString();

  if (
    false ===
    (hash_map = this.get_html_translation_table("HTML_ENTITIES", quote_style))
  ) {
    return false;
  }

  // fix &amp; problem
  // http://phpjs.org/functions/get_html_translation_table:416#comment_97660
  delete hash_map["&"];
  hash_map["&"] = "&amp;";

  for (symbol in hash_map) {
    entity = hash_map[symbol];
    tmp_str = tmp_str.split(entity).join(symbol);
  }
  tmp_str = tmp_str.split("&#039;").join("'");

  return tmp_str;
}

/// <reference path="../typings/jquery/jquery.d.ts"/>
/* global L */
//The javascript accompanying jamesgardiner.gihub.io/blog/police_data_api_in_leaflet

//initialise global variables
var forcesJSON;
var forces;
var neighbourhoods;
var areaLayer;
var i;

// create the map object and set the cooridnates of the initial view:
var map = L.map("map").setView([51.4833, -3.1833], 10);

// create the tile layer with correct attribution:
L.tileLayer("https://tile.openstreetmap.org/{z}/{x}/{y}.png", {
  attribution:
    'Map data &copy; <a href="http://openstreetmap.org">OpenStreetMap</a> contributors, <a href="http://creativecommons.org/licenses/by-sa/2.0/">CC-BY-SA</a>',
  maxZoom: 18
}).addTo(map);

//extend the L.Control class to create a custom drop down box populated with a list
//of police forces in England & Wales.
var ForceControl = L.Control.extend({
  initialize: function (name, options) {
    L.Util.setOptions(this, options);
  },

  //function to be called when the control is added to the map
  onAdd: function (map) {
    // create the control container with a particular class name
    var container = L.DomUtil.create("div", "dropdown-control");

    //create a list of available police force names and ids
    //using the data.police.uk api Forces method inside the
    //'force' global variable.
    forces = [];
    $.getJSON("https://data.police.uk/api/forces", function (data) {
      var htmlString =
        "<label>Police force: <select id=forcesList " +
        'onchange="updateNeighbourhoods()"><option>' +
        "Select a Force</option>";
      $.each(data, function (i, item) {
        forces[i] = item;
        htmlString = htmlString + "<option>" + forces[i].name + "</option>";
      });
      htmlString += "</select>";

      //update the dropdown list innerhtml with the list of forces
      container.innerHTML = htmlString;

      //allow a user to select a single option
      container.firstChild.onmousedown = container.firstChild.ondblclick =
        L.DomEvent.stopPropagation;
    });

    return container;
  }
});

//extend the L.Control class to create a custom drop down box
// initially with simple placeholder text
var NeighbourhoodControl = L.Control.extend({
  initialize: function (name, options, placeholder) {
    L.Util.setOptions(this, options);
  },

  //once added to the map div, carry out the following
  onAdd: function (map) {
    //create the control container with a particular class name
    var container = L.DomUtil.create("div", "dropdown-control");

    //add the following to the innerHTML
    var htmlString =
      '<label>Neighbourhood:<select id="neighbourhoodsList" ' +
      'onchange="neighbourhoodChanged()" onclick="neighbourhoodChanged()">' +
      "<option>Select a neighbourhood</option></select>";
    container.innerHTML = htmlString;

    //allow a user to select a single option
    container.firstChild.onmousedown = container.firstChild.ondblclick =
      L.DomEvent.stopPropagation;

    return container;
  }
});

map.addControl(new ForceControl("forcesList", { position: "topright" }));
map.addControl(
  new NeighbourhoodControl("neighbourhoodList", { position: "topright" })
);

//update the current list of neighbourhoods using the selected force id
var updateNeighbourhoods = function (name) {
  //get the force name
  var force = $("#forcesList").val();

  //empty the neighbourhoods array
  neighbourhoods = [];

  //find the force id for the api call
  for (i in forces) {
    if (forces[i].name === force) {
      var id = forces[i].id;
    }
  }

  //if the id is matched successfully then get a list of neighbourhoods in
  //that force area.
  if (id) {
    var url = "https://data.police.uk/api/" + id + "/neighbourhoods";
    $.getJSON(url, function (data) {
      var htmlString = '<select id="neighbourhoodsList">';
      $.each(data, function (i, item) {
        neighbourhoods[i] = item;
        htmlString =
          htmlString + "<option>" + neighbourhoods[i].name + "</option>";
      });
      htmlString = htmlString + "</select>";

      //change the innerHTML
      $("#neighbourhoodsList").html(htmlString);

      //reorder the options
      $("#neighbourhoodsList").html(
        $("#neighbourhoodsList option").sort(function (a, b) {
          return a.text == b.text ? 0 : a.text < b.text ? -1 : 1;
        })
      );

      //Prepend a placeholder so that onchange is fired when user selects
      $("#neighbourhoodsList").prepend(
        $("<option>Select a neighbourhood</option>")
      );
    });
  } else {
    $("#neighbourhoodsList").html(
      '<select id="neighbourhoodsList"><option>Select a police force</option></select>'
    );
  }
};

//function called when the selected neighbourhood is changed
var neighbourhoodChanged = function () {
  //get the selected 'hood name
  var hood = $("#neighbourhoodsList").val();

  //compare the name of the 'hood to get the id
  for (i in neighbourhoods) {
    if (
      html_entity_decode(neighbourhoods[i].name) === html_entity_decode(hood)
    ) {
      var id = [];
      id[0] = neighbourhoods[i].id;
    }
  }

  //compare the name of the force to get the id
  var force = $("#forcesList").val();
  for (i in forces) {
    if (forces[i].name === force) {
      id[1] = forces[i].id;
    }
  }

  //if we match both the force and 'hood then carry on, else break
  if (id[0] && id[1]) {
    var latlng = [];
    var url = "https://data.police.uk/api/" + id[1] + "/" + id[0] + "/boundary";
    $.getJSON(url, function (data) {
      //create an array of boundary lat lon pairs
      $.each(data, function (i, item) {
        latlng.push(new L.LatLng(data[i].latitude, data[i].longitude));
      });

      //if a layer is already present, remove it
      if (areaLayer) {
        map.removeLayer(areaLayer);
      }

      //create a new polygon object using the latlng array
      areaLayer = new L.Polygon(latlng, {
        clickable: true,
        weight: 3,
        opacity: 0.4,
        fillOpacity: 0.1
      });

      //redraw the map to the bounds of the new polygon
      map.fitBounds(areaLayer.getBounds());
      //add the polygon to the map
      areaLayer.addTo(map);
    });
  }
};

External CSS

  1. https://cdnjs.cloudflare.com/ajax/libs/leaflet/1.6.0/leaflet.css

External JavaScript

  1. https://cdnjs.cloudflare.com/ajax/libs/leaflet/1.6.0/leaflet.js
  2. https://cdnjs.cloudflare.com/ajax/libs/jquery/3.4.1/jquery.min.js
  3. https://cdnjs.cloudflare.com/ajax/libs/geojson/0.5.0/geojson.min.js