• Home
  • Publications
  • Vitae
  • Datasets
  • The “Lion CIty Soundscapes” dataset consists of 62 360° audiovisual recordings to characterise the soundscape in Singapore. The locations represent perceptual dimensions of “Full of Life & Exciting”, “Calm & Tranquil”, “Lifeless & Boring”, and “Chaotic & Restless”.

  • The ISO Pleasantness and ISO Eventfulness map layers computed from binaural recordings at each site represent the perceived affective quality of each site according to ISO 12913-3.

  • For more information: follow the hyperlinks to the associated publications detailing the site selection method and the recording and excerpting methodology. This is an enhancement of the original visualisation.

lcs_data_df = transpose(lcs_data)

// Loading the D3 library or data manipulation and visualization
d3 = require("d3@6")

// Include FontAwesome for icons
html`<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/5.15.4/css/all.min.css"`
filtered_sites = {
  return lcs_data_df.filter(d => 
    //d.sitecat === sitecat_select // selected site
    sitecat_select.includes(d.sitecat) // selected sites
  );
}
sitecat_options = ["Calm", "Chaotic", "Monotonous", "Vibrant"]

// Create the site category input
viewof sitecat_select = Inputs.checkbox(sitecat_options, 
{value: ["Calm", "Chaotic", "Monotonous", "Vibrant"], label: "Site Type:"})
function createColorMap(data) {
  const colorMap = new Map(data.map(d => [d.value, d.color]));
  return value => colorMap.get(value) || "transparent";
}

pal_isopl = createColorMap(isoplColors);
pal_isoev = createColorMap(isoevColors);

// Function to extract unique values from GeoJSON
function getUniqueValues(geojson, property) {
  const values = new Set();
  geojson.features.forEach(feature => {
    values.add(feature.properties[property]);
  });
  return Array.from(values);
}

// Load GeoJson files
//isoevGeoJson = await d3.json(`r_isoev.geojson`);
//isoplGeoJson = await d3.json(`r_isopl.geojson`);

//isoplColors
// Load Leaflet library
L = require('leaflet@1.9.4')

// Include Leaflet CSS and custom legend CSS
html`<link rel="stylesheet" href="https://unpkg.com/leaflet@1.9.4/dist/leaflet.css" integrity="sha256-p4NxAoJBhIIN+hmNHrzRCf9tD/miZyoHS5obTRR9BMY=" crossorigin="" />`
html`
<style>
.info.legend {
  line-height: 18px;
  color: #555;
  background: white;
  background: rgba(255,255,255,0.8);
  box-shadow: 0 0 15px rgba(0,0,0,0.2);
  border-radius: 5px;
  padding: 10px;
}
.info.legend i {
  width: 18px;
  height: 18px;
  float: left;
  margin-right: 8px;
  opacity: 0.7;
}
</style>
`
map = {
        // Set dimensions 
        let width = 850; 
        let height = width / 1.6; 

        // Create the container for the map
        let container = DOM.element('div', {
                style: `width: ${width}px; height: ${height}px`
        });
        yield container;
        
        // Initialize the map
        let map = L.map(container).setView([1.34,103.8], 11);
        L.tileLayer('https://{s}.basemaps.cartocdn.com/light_all/{z}/{x}/{y}@2x.png',
                {attribution: '&copy; <a href="http://osm.org/copyright">OpenStreetMap</a>contributors'}).addTo(map);
  
        // Define icons
        const iconSet = {
                'Calm': L.icon({
                        iconUrl: calm_icon,
                        iconSize: [32, 32],
                        iconAnchor: [16, 32],
                        popupAnchor: [1, -25]
                }),
                'Chaotic': L.icon({
                        iconUrl: chaotic_icon,
                        iconSize: [32, 32],
                        iconAnchor: [16, 32],
                        popupAnchor: [1, -25]
                }),
                'Vibrant': L.icon({
                        iconUrl: vibrant_icon,
                        iconSize: [32, 32],
                        iconAnchor: [16, 32],
                        popupAnchor: [1, -25]
                }),
                'Monotonous': L.icon({
                        iconUrl: monotonous_icon,
                        iconSize: [32, 32],
                        iconAnchor: [16, 32],
                        popupAnchor: [1, -25]
                })
        };
        
        // Add markers for each filtered site
        const markers = L.layerGroup(filtered_sites.map(d => {
                return L.marker([d.lat, d.lng], {
                        icon: iconSet[d.sitecat] 
                        }).bindPopup(`
                                <h4 style='color: black'><a href='${d.yt_link}'>${d.desc}</a></h4>
                                <b>Type:</b> ${d.sitecat}<br>
                                <b>SPL:</b> ${d.spl.toFixed(1)} dB(A)<br>
                                <b>ISOPL:</b> ${d.isopl.toFixed(3)}<br>
                                <b>ISOEV:</b> ${d.isoev.toFixed(3)}<br>
                                ${d.yt_iframe}
                                `, { maxWidth: 560 });
                        }));
        markers.addTo(map);

        // Add legend to the map
        var legend = L.control({position: 'bottomright'});
        legend.onAdd = function (map) {
                var div = L.DomUtil.create('div', 'info legend'),
                        categories = ['Vibrant','Calm','Monotonous','Chaotic'],
                        colors = ['#CE2E6C', '#FFB5B5', '#F0DECB', '#504658'];
      
                div.innerHTML += '<b>Site Type</b><br>';
        
                // Loop through the categories to generate labels
                for (let i = 0; i < categories.length; i++) {
                        div.innerHTML += `<i style="background:
                                ${colors[i]}"></i> ${categories[i]}<br>`;
                }
        
                return div;
        };
        legend.addTo(map);
        
        // Add polygons
        const isoplLayer = L.geoJSON(isoplGeoJson, {
                style: feature => ({
                        fillColor: pal_isopl(feature.properties.var1pred),
                        fillOpacity: 0.5,
                        weight: 0
                })
        }).addTo(map);

        const isoevLayer = L.geoJSON(isoevGeoJson, {
                style: feature => ({
                        fillColor: pal_isoev(feature.properties.var1pred),
                        fillOpacity: 0.5,
                        weight: 0
                })
        }).addTo(map);
        
        // Add legend for ISOPL
        const legendIsopl = L.control({ position: 'bottomleft' });
        legendIsopl.onAdd = function (map) {
                const div = L.DomUtil.create('div', 'info legend');
                const minValue = -0.2889;
                const maxValue = 0.4136;
                const steps = 10; // Number of steps in the gradient

                div.innerHTML += '<b>ISOPL<b><br>';
                
                // Create a flex container
                let legendContent = '<div style="display: flex; align-items: center;">';
                
                // Create gradient bar
                let gradientBar = '<div style="width: 20px; height: 200px;';
                
                // Define linear gradient
                gradientBar += 'background: linear-gradient(to bottom,';
                gradientBar += 'rgba(69,117,180,0.5), rgba(145,191,219,0.5),';
                gradientBar += 'rgba(224,243,248,0.5), rgba(255,255,191,0.5),';
                gradientBar += 'rgba(254,224,144,0.5), rgba(252,141,89,0.5),';
                gradientBar += 'rgba(215,48,39,0.5))"></div>';
                
                legendContent += gradientBar;
                
                // Add labels
                legendContent += `<div style="width: 30px; height: 200px; display: flex;                                       flex-direction: column; justify-content: space-between; margin-left: 5px;">
                        <span>${maxValue.toFixed(2)}</span>
                        <span>${(maxValue - (maxValue - minValue) * 0.2).toFixed(1)}</span>
                        <span>${(maxValue - (maxValue - minValue) * 0.4).toFixed(1)}</span>
                        <span>${(maxValue - (maxValue - minValue) * 0.6).toFixed(1)}</span>
                        <span>${(maxValue - (maxValue - minValue) * 0.8).toFixed(1)}</span>
                        <span>${minValue.toFixed(2)}</span>
                        </div>`;
                legendContent += '</div>'; // Close the flex container
    
                div.innerHTML += legendContent;
                
                return div;
        };
        legendIsopl.addTo(map);
        
        // Add legend for ISOEV
        const legendIsoev = L.control({ position: 'bottomleft' });
        legendIsoev.onAdd = function (map) {
                const div = L.DomUtil.create('div', 'info legend');
                const minValue = -0.4313712;
                const maxValue = 0.392208;
                const steps = 10; // Number of steps in the gradient

                div.innerHTML += '<b>ISOEV<b><br>';
                
                // Create a flex container
                let legendContent = '<div style="display: flex; align-items: center;">';
                
                // Create gradient bar
                let gradientBar = '<div style="width: 20px; height: 200px;';
                
                // Define linear gradient
                gradientBar += 'background: linear-gradient(to bottom,';
                gradientBar += 'rgba(27,120,55,0.5), rgba(127,191,123,0.5),';
                gradientBar += 'rgba(217,240,211,0.5), rgba(247,247,247,0.5),';
                gradientBar += 'rgba(231,212,232,0.5), rgba(175,141,195,0.5),';
                gradientBar += 'rgba(118,42,131,0.5))"></div>';
                
                legendContent += gradientBar;
                
                // Add labels
                legendContent += `<div style="width: 30px; height: 200px; display: flex;                                       flex-direction: column; justify-content: space-between; margin-left: 5px;">
                        <span>${maxValue.toFixed(2)}</span>
                        <span>${(maxValue - (maxValue - minValue) * 0.2).toFixed(1)}</span>
                        <span>${(maxValue - (maxValue - minValue) * 0.4).toFixed(1)}</span>
                        <span>${(maxValue - (maxValue - minValue) * 0.6).toFixed(1)}</span>
                        <span>${(maxValue - (maxValue - minValue) * 0.8).toFixed(1)}</span>
                        <span>${minValue.toFixed(2)}</span>
                        </div>`;
                legendContent += '</div>'; // Close the flex container
    
                div.innerHTML += legendContent;
                
                return div;
        };
        legendIsoev.addTo(map);
        
        // Add layer control
        const overlayMaps = {
                'Markers': markers,
                'ISO Pleasantness': isoplLayer,
                'ISO Eventfulness': isoevLayer
        };
        L.control.layers(null, overlayMaps).addTo(map);

        return map;
}
// Create the scatter plot with the unit circle and axes
Plot.plot({
        width: 480,
        height: 480,
        x: {
                domain: [-0.5, 0.5],
                label: "ISOPL",
                grid: true
        },
        y: {
                domain: [-0.5, 0.5],
                label: "ISOEV",
                grid: true
        },
        //r: { type: "linear", domain: [0, 12], range: [0, 480 - 60] },
        marks: [
                //Plot.dot([[0, 0]], { r: [3] }), // scaled (channel r)
                Plot.dot(filtered_sites, { x: "isopl", y: "isoev", 
                        stroke: "sitecat", tip: true,
                        channels: {description: "desc", spl: "spl"}}),
                Plot.crosshair(filtered_sites, {x: "isopl", y: "isoev"}),
                Plot.ruleX([0], { stroke: "black" }),
                Plot.ruleY([0], { stroke: "black" })
        ],
        color:{domain: ["Calm", "Chaotic", "Monotonous", "Vibrant"], range: ['#FFB5B5','#504658', '#F0DECB', '#CE2E6C']}
});
//create a table with the filtered sites

viewof table = Inputs.table(filtered_sites, {
  columns: [
    "id",
    "sitecat",
    "desc",
    "spl",
    "isopl",
    "isoev"
  ],
  header: {
        id: "ID",
        sitecat: "Site Type",
        desc: "Description",
        spl: "dB(A)",
        isopl: "ISOPleasantness",
        isoev: "ISOEventfulness"
  },
  width: {
        id: 40,
        sitecat: 60,
        desc: 150,
        spl: 60,
        isopl: 80,
        isoev: 80
  },
  format: {
    depth: x => Number.isInteger(x) ? x.toString() : x.toFixed(2)
  },
  rows: Infinity, 
  pagination: true
})

2024, Lam Bhan

 
  • Edit this page
  • Report an issue