Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[IDEA - DO NOT MERGE] Visualise uncertainty by differential blurring of polity shapes #43

Closed
wants to merge 34 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
34 commits
Select commit Hold shift + click to select a range
d1a02df
remove unwanted features
edwardchalstrey1 Jul 23, 2024
7a28063
add blur feature to polity map
edwardchalstrey1 Jul 23, 2024
355f588
add blur to world map
edwardchalstrey1 Jul 23, 2024
c3a2e33
add setBlur func
edwardchalstrey1 Jul 23, 2024
ff24b38
rudimentary blur logic
edwardchalstrey1 Jul 23, 2024
29a1465
update text box background fill
edwardchalstrey1 Jul 23, 2024
7588384
narrow opacity range
edwardchalstrey1 Jul 23, 2024
815e7d6
add scroll to legend
edwardchalstrey1 Jul 24, 2024
f782c4e
keep legend title in place
edwardchalstrey1 Jul 24, 2024
aecd64f
use shape.polity_start_year to determine blur level
edwardchalstrey1 Jul 24, 2024
a8c3f55
correctly adjust blur for each shape separately
edwardchalstrey1 Jul 24, 2024
8ba8111
add shape id to polity map tooltip
edwardchalstrey1 Jul 24, 2024
6d72d33
change 'Shapefile name' to 'Shape ID'
edwardchalstrey1 Jul 24, 2024
67d5495
update edit box text
edwardchalstrey1 Jul 24, 2024
df60746
rename VideoShapefile to Cliopatria
edwardchalstrey1 Jul 24, 2024
a5ae432
add confidence field to Cliopatria model
edwardchalstrey1 Jul 25, 2024
7933529
update views and tests to include confidence
edwardchalstrey1 Jul 25, 2024
4d812aa
update default blur values
edwardchalstrey1 Jul 25, 2024
803502c
set stdDeviationValue based on confidence in db
edwardchalstrey1 Jul 25, 2024
9ae63da
correctly set stdDeviationValue based on confidence in db
edwardchalstrey1 Jul 25, 2024
53c01ae
add blurValue function
edwardchalstrey1 Jul 25, 2024
4665395
check for 'None' or null
edwardchalstrey1 Jul 25, 2024
83d11fa
report confidence score in shape tooltip
edwardchalstrey1 Jul 25, 2024
1a95ae5
refactor so formattedConfidence doesn't require layer
edwardchalstrey1 Jul 25, 2024
c246ca5
add formattedConfidence to each shape
edwardchalstrey1 Jul 25, 2024
c2436be
add confidence scores underneath polity map
edwardchalstrey1 Jul 25, 2024
632852e
only show confidence scores if user authenticated
edwardchalstrey1 Jul 25, 2024
1cfb47f
button to show or hide confidence scores
edwardchalstrey1 Jul 26, 2024
5b0e3b4
list shapes in order
edwardchalstrey1 Jul 26, 2024
37e49a4
rename populate_cliopatria
edwardchalstrey1 Jul 26, 2024
6304255
remove border lines for polity maps
edwardchalstrey1 Jul 26, 2024
7866eb9
Merge branch 'dev' into uncertainty
edwardchalstrey1 Jul 26, 2024
c013fee
update API with new model name Cliopatria
edwardchalstrey1 Jul 26, 2024
6676cb9
change to Cliopatria
edwardchalstrey1 Jul 26, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions docs/source/getting-started/setup/spatialdb.rst
Original file line number Diff line number Diff line change
Expand Up @@ -35,11 +35,11 @@ Cliopatria shape dataset
$ python cliopatria/convert_data.py /path/to/cliopatria.geojson

Note: this will create a new file with the same name but with the suffix "_seshat_processed.geojson"
3. Populate ``core_videoshapefile`` table using the following command:
3. Populate ``core_cliopatria`` table using the following command:

.. code-block:: bash

$ python manage.py populate_videodata /path/to/cliopatria_seshat_processed.geojson
$ python manage.py populate_cliopatria /path/to/cliopatria_seshat_processed.geojson


GADM
Expand Down
8 changes: 4 additions & 4 deletions seshat/apps/core/forms.py
Original file line number Diff line number Diff line change
Expand Up @@ -130,7 +130,7 @@ class Meta:
'end_year': 'End Year',
'home_seshat_region': 'Home Seshat Region',
'polity_tag': 'Polity Tag',
'shapefile_name': 'Shapefile name',
'shapefile_name': 'Shape ID',
'private_comment': 'Private Comment (optional)',
'general_description': 'General Description of the Polity',

Expand All @@ -149,7 +149,7 @@ class Meta:
'home_seshat_region': forms.Select(attrs={'class': 'form-control js-example-basic-single form-select mb-3',}),
'polity_tag': forms.Select(attrs={'class': 'form-control form-select mb-3',}),
'shapefile_name': forms.TextInput(attrs={'class': 'form-control mb-3', }),
'private_comment': forms.Textarea(attrs={'class': 'form-control', 'style': 'height: 100px', 'placeholder':'Add a private comment that will only be visible to Seshat experts and RAs.\nUse this box to request edits to the polity map data.'}),
'private_comment': forms.Textarea(attrs={'class': 'form-control', 'style': 'height: 100px', 'placeholder':'Add a private comment that will only be visible to Seshat experts and RAs.\nUse this box to request edits to the polity border shapes. Enter the ID of the relevant shape in the box above (found by clicking on the map).'}),
'general_description': forms.Textarea(attrs={'class': 'form-control mb-3', 'style': 'height: 265px', 'placeholder':'Add a general description (optional)'}),

}
Expand All @@ -171,7 +171,7 @@ class Meta:
'start_year': 'Start Year',
'end_year': 'End Year',
'home_seshat_region': 'Home Seshat Region',
'shapefile_name': 'Shapefile name',
'shapefile_name': 'Shape ID',
'polity_tag': 'Polity Tag',
'private_comment': 'Private Comment (optional)',
'general_description': 'General Description of the Polity',
Expand All @@ -191,7 +191,7 @@ class Meta:
'home_seshat_region': forms.Select(attrs={'class': 'form-control js-example-basic-single form-select mb-3',}),
'polity_tag': forms.Select(attrs={'class': 'form-control form-select mb-3',}),
'shapefile_name': forms.TextInput(attrs={'class': 'form-control mb-3', }),
'private_comment': forms.Textarea(attrs={'class': 'form-control', 'style': 'height: 100px', 'placeholder':'Add a private comment that will only be visible to seshat experts and RAs.\nUse this box to request edits to the polity map data.'}),
'private_comment': forms.Textarea(attrs={'class': 'form-control', 'style': 'height: 100px', 'placeholder':'Add a private comment that will only be visible to Seshat experts and RAs.\nUse this box to request edits to the polity border shapes. Enter the ID of the relevant shape in the box above (found by clicking on the map).'}),
'general_description': forms.Textarea(attrs={'class': 'form-control mb-3', 'style': 'height: 265px', 'placeholder':'Add a general description (optional)'}),

}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
from django.contrib.gis.geos import GEOSGeometry, MultiPolygon
from django.core.management.base import BaseCommand
from django.db import connection
from seshat.apps.core.models import VideoShapefile
from seshat.apps.core.models import Cliopatria


class Command(BaseCommand):
Expand All @@ -30,23 +30,23 @@ def handle(self, *args, **options):
cliopatria_data = json.load(f)
self.stdout.write(self.style.SUCCESS(f"Successfully loaded Cliopatria shape dataset from {cliopatria_geojson_path}"))

# Clear the VideoShapefile table
self.stdout.write(self.style.SUCCESS('Clearing VideoShapefile table...'))
VideoShapefile.objects.all().delete()
self.stdout.write(self.style.SUCCESS('VideoShapefile table cleared'))
# Clear the Cliopatria table
self.stdout.write(self.style.SUCCESS('Clearing Cliopatria table...'))
Cliopatria.objects.all().delete()
self.stdout.write(self.style.SUCCESS('Cliopatria table cleared'))

# Iterate through the data and create VideoShapefile instances
# Iterate through the data and create Cliopatria instances
self.stdout.write(self.style.SUCCESS('Adding data to the database...'))
for feature in cliopatria_data['features']:
properties = feature['properties']
self.stdout.write(self.style.SUCCESS(f"Creating VideoShapefile instance for {properties['DisplayName']} ({properties['FromYear']} - {properties['ToYear']})"))
self.stdout.write(self.style.SUCCESS(f"Creating Cliopatria instance for {properties['DisplayName']} ({properties['FromYear']} - {properties['ToYear']})"))

# Save geom and convert Polygon to MultiPolygon if necessary
geom = GEOSGeometry(json.dumps(feature['geometry']))
if geom.geom_type == 'Polygon':
geom = MultiPolygon(geom)

VideoShapefile.objects.create(
Cliopatria.objects.create(
geom=geom,
name=properties['DisplayName'],
wikipedia_name=properties['Wikipedia'],
Expand All @@ -72,14 +72,14 @@ def handle(self, *args, **options):
## Use this code if you want to simplify the geometries
# with connection.cursor() as cursor:
# cursor.execute("""
# UPDATE core_videoshapefile
# UPDATE core_cliopatria
# SET simplified_geom = ST_Simplify(geom, 0.07);
# """)

## Use this code if you don't need to simplify the geometries
with connection.cursor() as cursor:
cursor.execute("""
UPDATE core_videoshapefile
UPDATE core_cliopatria
SET simplified_geom = geom;
""")
self.stdout.write(self.style.SUCCESS('Simplified geometries added'))
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
# Generated by Django 4.0.3 on 2024-07-24 15:08

from django.db import migrations


class Migration(migrations.Migration):

dependencies = [
('core', '0066_alter_country_options_alter_polity_options_and_more'),
]

operations = [
migrations.RenameModel(
old_name='VideoShapefile',
new_name='Cliopatria',
),
]
18 changes: 18 additions & 0 deletions seshat/apps/core/migrations/0068_cliopatria_confidence.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
# Generated by Django 4.0.3 on 2024-07-24 15:42

from django.db import migrations, models


class Migration(migrations.Migration):

dependencies = [
('core', '0067_rename_videoshapefile_cliopatria'),
]

operations = [
migrations.AddField(
model_name='cliopatria',
name='confidence',
field=models.IntegerField(choices=[(1, 'Approximate'), (2, 'Moderate precision'), (3, 'Determined by international law')], null=True),
),
]
8 changes: 7 additions & 1 deletion seshat/apps/core/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -1121,7 +1121,7 @@ def __str__(self) -> str:

# Shapefile models

class VideoShapefile(models.Model):
class Cliopatria(models.Model):
"""
Model representing Cliopatria polity borders dataset.
"""
Expand All @@ -1139,6 +1139,12 @@ class VideoShapefile(models.Model):
colour=models.CharField(max_length=7)
components=models.CharField(max_length=500, null=True)
member_of=models.CharField(max_length=500, null=True)
CONFIDENCE_CHOICES = [
(1, 'Approximate'),
(2, 'Moderate precision'),
(3, 'Determined by international law'),
]
confidence = models.IntegerField(choices=CONFIDENCE_CHOICES, null=True)

def __str__(self):
return "Name: %s" % self.name
Expand Down
104 changes: 101 additions & 3 deletions seshat/apps/core/static/core/js/map_functions.js
Original file line number Diff line number Diff line change
Expand Up @@ -246,11 +246,15 @@ function updateLegend() {
return a.polity.localeCompare(b.polity);
});

// Add a legend for highlighted polities
// After adding polities to the legend
if (addedPolities.length > 0) {
var legendTitle = document.createElement('h3');
legendTitle.textContent = 'Selected Polities';
legendDiv.appendChild(legendTitle);

// Create a container for polity items
var polityContainer = document.createElement('div');

for (var i = 0; i < addedPolities.length; i++) {
var legendItem = document.createElement('p');
var colorBox = document.createElement('span');
Expand All @@ -262,9 +266,22 @@ function updateLegend() {
colorBox.style.marginRight = '10px';
legendItem.appendChild(colorBox);
legendItem.appendChild(document.createTextNode(addedPolities[i].polity));
legendDiv.appendChild(legendItem);
polityContainer.appendChild(legendItem); // Append to the container
}
};

// Append the container to the legendDiv
legendDiv.appendChild(polityContainer);

// Make the polityContainer scrollable if there are more than 7 polities
if (addedPolities.length > 7) {
polityContainer.style.maxHeight = '420px'; // Adjust based on actual item height
polityContainer.style.overflowY = 'scroll';
} else {
// Reset to default if fewer than 7 polities to ensure it behaves correctly on subsequent updates
polityContainer.style.maxHeight = '';
polityContainer.style.overflowY = '';
}
}

} else if (variable in categorical_variables) {

Expand Down Expand Up @@ -448,4 +465,85 @@ function populateVariableDropdown(variables) {
chooseVariableDropdown.appendChild(optgroup);
}
});
}

function blurValue(polityYear, confidence) {
var stdDeviationValue;

var highBlurValue = 3;
var lowBlurValue = 1.5;
var noBlurValue = 0;

// Check if confidence is not recorded in the db, blur based on polityYear
if (confidence === 'None' || confidence === null || typeof confidence === 'undefined') {
if (polityYear < 0) {
confidenceScore = 1;
} else if (polityYear < 1500) {
confidenceScore = 2;
} else {
confidenceScore = 3;
}
} else {
// Convert confidence to a number if it's not 'None'
confidenceScore = Number(confidence);
}

// Determine the blur based on the numeric confidence value
if (confidenceScore === 1) {
stdDeviationValue = highBlurValue;
} else if (confidenceScore === 2) {
stdDeviationValue = lowBlurValue;
} else if (confidenceScore === 3) {
stdDeviationValue = noBlurValue;
}

// Return both stdDeviationValue and confidenceScore as an object
return { stdDeviationValue, confidenceScore };
}

function setBlur(layer) {
var { stdDeviationValue, confidenceScore } = blurValue(layer.feature.properties.polity_start_year, layer.feature.properties.confidence);

// Generate a unique filter ID based on the stdDeviationValue or another unique property
var filterId = 'shape-blur-' + stdDeviationValue;

// Check if the filter already exists, if not, create it
if (!document.getElementById(filterId)) {
var svgDefs = document.querySelector('svg defs') || createSvgDefs();
var filter = document.createElementNS('http://www.w3.org/2000/svg', 'filter');
filter.setAttribute('id', filterId);
var feGaussianBlur = document.createElementNS('http://www.w3.org/2000/svg', 'feGaussianBlur');
feGaussianBlur.setAttribute('stdDeviation', stdDeviationValue);
filter.appendChild(feGaussianBlur);
svgDefs.appendChild(filter);
}

// Apply the unique filter to this layer's path elements
layer._path.style.filter = 'url(#' + filterId + ')';
}

function formattedConfidence(polityYear, confidence) {
var { stdDeviationValue, confidenceScore } = blurValue(polityYear, confidence);

if (confidenceScore === 1) {
var precision = 'Approximate';
} else if (confidenceScore === 2) {
var precision = 'Moderately precise';
} else if (confidenceScore === 3) {
var precision = 'Determined by international law';
};

if (confidence === 'None' || confidence === null || typeof confidence === 'undefined') {
return 'Uncoded (defaults to ' + confidenceScore + ': ' + precision + ')';
} else {
return confidenceScore + ': ' + precision;
}
}

// Helper function to create SVG defs if it doesn't exist
function createSvgDefs() {
var svgElement = document.querySelector('svg');
var defs = document.createElementNS('http://www.w3.org/2000/svg', 'defs');
svgElement.appendChild(defs);
return defs;
}
Loading
Loading