<!DOCTYPE html> <html> <head> <title>Lab 7</title> <link rel="stylesheet" href="https://unpkg.com/leaflet@1.9.4/dist/leaflet.css" integrity="sha256-p4NxAoJBhIIN+hmNHrzRCf9tD/miZyoHS5obTRR9BMY=" crossorigin=""/> <script src="https://unpkg.com/leaflet@1.9.4/dist/leaflet.js" integrity="sha256-20nQCchB9co0qIjJZRGuk2/Z9VM+kNiyxNV1lvTlZBo=" crossorigin=""></script> <script src="https://code.jquery.com/jquery-3.5.1.min.js" integrity="sha256-9/aliU8dGd2tb6OSsuzixeV4y/faTqgFtohetphbbj0=" crossorigin="anonymous"></script> <script src="https://code.jquery.com/ui/1.12.1/jquery-ui.min.js" integrity="sha256-VazP97ZCwtekAsvgPBSUwPFKdrwD3unUfSGVYrahUqU=" crossorigin="anonymous"></script> <style type="text/css"> html, body { margin: 0; padding: 0; height: 100%; } #map { min-height: 100%; } </style> </head> <body> <div id="map"></div> <script type="text/javascript"> var map = L.map('map', { center: [37.3367844,-97.3114977], zoom: 4 }); L.tileLayer('https://cartodb-basemaps-{s}.global.ssl.fastly.net/light_all/{z}/{x}/{y}.png', { attribution: 'Map tiles by Carto, under CC BY 3.0. Data by OpenStreetMap, under ODbL.', maxZoom: 11, minZoom: 4 }).addTo(map); //change the file url to match yours $.getJSON("https://raw.githubusercontent.com/neiugis/lab7_map/main/city.geojson") .done(function(data) { var info = processData(data); createPropSymbols(info.timestamps, data); createSliderUI(info.timestamps); }); function processData(data) { // First, initialize the variables to hold the timestamps and min/max population values var timestamps = []; // square brackets to define an array of data // because there are multiple timestamps var min = Infinity; // for the min, begin with the largest possible value - infinity var max = -Infinity;// for the max, begin with the smallest possible value - negative infinity // Go through each row/feature of the data table // Note data is the variable name in the function definition - processData(data) for (var feature in data.features) { var properties = data.features[feature].properties; // At each row, go through the columns/attributes to get the values for (var attribute in properties) { if ( attribute != 'id' && attribute != 'name' && attribute != 'latitude' && attribute != 'longitude' ) // != means NOT EQUAL TO // These columns are NOT recorded // Modify this part when mapping your own data { if ( $.inArray(attribute,timestamps) === -1) { // JQuery in.Array() method searches for a specified value within an array and return its index (or -1 if not found) // here, the new timestamp is only added when it is not already in the array // triple equals === compares both type and value timestamps.push(attribute); // The JS push() method adds new items to the end of an array // and returns the new length of the array } if (properties[attribute] < min) { min = properties[attribute]; // record/update the current smaller values as the min } if (properties[attribute] > max) { max = properties[attribute]; // record/update the current larger values as the max } } } } return { // the function finally returns the timestamps array, the min and max of population data timestamps : timestamps, min : min, max : max } } // The function to draw the proportional symbols function createPropSymbols(timestamps, data) { cities = L.geoJson(data, { // By default, Leaflet draws geojson points as simple markers // To alter this, the pointToLayer function needs to be used pointToLayer: function(feature, latlng) { return L.circleMarker(latlng, { // we use circle marker for the points fillColor: "#501e65", // fill color of the circles color: '#501e65', // border color of the circles weight: 2, // circle line weight in pixels fillOpacity: 0.5 // fill opacity (0-1) }).on({ mouseover: function(e) { this.openPopup(); this.setStyle({fillColor: 'green'}); // fill color turns green when mouseover }, mouseout: function(e) { this.closePopup(); this.setStyle({fillColor: '#501e65'}); // fill turns original color when mouseout } }); } }).addTo(map); updatePropSymbols(timestamps[0]); // this function is defined below // When loaded, the map will first show proportional symbols with the first timestamp's data } // The function to update/resize each circle marker according to a value in the time series function updatePropSymbols(timestamp) { cities.eachLayer(function(layer) { // eachLayer() is an Leaflet function to iterate over the layers/points of the map var props = layer.feature.properties; // attributes var radius = calcPropRadius(props[timestamp]); // circle radius, calculation function defined below // pop-up information (when mouseover) for each city is also defined here var popupContent = props.name + ' ' + timestamp + ' population: ' + String(props[timestamp]) ; layer.setRadius(radius); // Leaflet method for setting the radius of a circle layer.bindPopup(popupContent, { offset: new L.Point(0,-radius) }); // bind the popup content, with an offset }); } // calculate the radius of the proportional symbols based on area function calcPropRadius(attributeValue) { var scaleFactor = 0.001; // the scale factor is used to scale the values; the units of the radius are in meters // you may determine the scale factor accordingly based on the range of the values and the mapping scale var area = attributeValue * scaleFactor; return Math.sqrt(area/Math.PI); // the function return the radius of the circle to be used in the updatePropSymbols() } function createSliderUI(timestamps) { var sliderControl = L.control({ position: 'bottomleft'} ); // position of the slider // Another use of L.control :) sliderControl.onAdd = function(map) { //initialize a range slider with mousedown control var slider = L.DomUtil.create("input", "range-slider"); L.DomEvent.addListener(slider, 'mousedown', function(e) { L.DomEvent.stopPropagation(e); }); // Define the labels of the time slider as an array of strings // Modify this for your data var labels = ["1950", "1960","1970","1980", "1990", "2000","2010"]; $(slider) .attr({ 'type':'range', 'max': timestamps[timestamps.length-1], 'min':timestamps[0], 'step': 10, // Change this to match the numeric interval between adjacent timestamps 'value': String(timestamps[0]) }) .on('input change', function() { updatePropSymbols($(this).val().toString()); // automatic update the map for the timestamp var i = $.inArray(this.value,timestamps); $(".temporal-legend").text(labels[i]); // automatic update the label for the timestamp }); return slider; } sliderControl.addTo(map); createTimeLabel("1950"); //The starting timestamp label } // Add labels to the time slider when the map first loaded function createTimeLabel(startTimestamp) { var temporalLegend = L.control({position: 'bottomleft' }); // same position as the slider // One more use of L.control !! temporalLegend.onAdd = function(map) { var output = L.DomUtil.create("output", "temporal-legend"); $(output).text(startTimestamp); return output; } temporalLegend.addTo(map); } </script> </body> </html>