Skip to content

Commit

Permalink
Multi tenant mini sass (#4157)
Browse files Browse the repository at this point in the history
* Update third party layer: Minissas (#3720)

* update minisass third party layer

* update minisass url

* remove unused file

* Add api to retrieve minisass

* Store minisass token, update frontend

---------

Co-authored-by: Faneva Andriamiadantsoa <fanevanjanahary@gmail.com>
  • Loading branch information
dimasciput and fanevanjanahary authored Aug 18, 2024
1 parent 0a05a92 commit 41e6522
Show file tree
Hide file tree
Showing 7 changed files with 231 additions and 50 deletions.
5 changes: 5 additions & 0 deletions bims/admins/site_setting.py
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,11 @@ class Meta:
required=False
)

minisass_token = forms.CharField(
widget=forms.PasswordInput(render_value=True),
required=False
)

def __init__(self, *args, **kwargs):
super(SiteSettingAdminForm, self).__init__(*args, **kwargs)
if self.instance and self.instance.pk:
Expand Down
4 changes: 4 additions & 0 deletions bims/api_urls.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
IsHarvestingGeocontext, HarvestGeocontextView, ClearHarvestingGeocontextCache,
GetGeocontextLogLinesView
)
from bims.api_views.minisass_observations import MiniSASSObservationsView
from bims.api_views.invasions import InvasionsList
from bims.api_views.taxon_update import UpdateTaxon, ReviewTaxonProposal
from bims.api_views.reference import DeleteRecordsByReferenceId
Expand Down Expand Up @@ -376,6 +377,9 @@
path('harvesting-geocontext-logs/',
GetGeocontextLogLinesView.as_view(),
name='get_log_lines'),
path('minisass-observations/',
MiniSASSObservationsView.as_view(),
name='minisass-observations'),
path('taxon-group-validated/<int:id>/',
TaxonGroupTotalValidated.as_view(),
name='taxon-group-total-validated'),
Expand Down
45 changes: 45 additions & 0 deletions bims/api_views/minisass_observations.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
import requests
import os
import json

from preferences import preferences
from rest_framework.views import APIView
from rest_framework.response import Response
from rest_framework.permissions import IsAuthenticated

from django.conf import settings


class MiniSASSObservationsView(APIView):
permission_classes = [IsAuthenticated]

def get(self, request, format=None):
file_path = os.path.join(
settings.MEDIA_ROOT,
'observations_data.json')
data = None

if os.path.exists(file_path):
with open(file_path, 'r') as file:
data = json.load(file)

if data:
return Response(data)

url = "https://minisass.org/monitor/observations/"
token = preferences.SiteSetting.minisass_token
headers = {
"Authorization": f"Bearer {token}"
}

response = requests.get(url, headers=headers)

if response.status_code == 200:
data = response.json()
with open(file_path, 'w') as file:
json.dump(data, file)
return Response(data)
else:
return Response(
response.text,
status=response.status_code)
18 changes: 18 additions & 0 deletions bims/migrations/0433_sitesetting_minisass_token.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
# Generated by Django 4.2.11 on 2024-08-18 17:57

from django.db import migrations, models


class Migration(migrations.Migration):

dependencies = [
('bims', '0432_sitesetting_park_layer_csv'),
]

operations = [
migrations.AddField(
model_name='sitesetting',
name='minisass_token',
field=models.CharField(blank=True, default=''),
),
]
5 changes: 5 additions & 0 deletions bims/models/site_setting.py
Original file line number Diff line number Diff line change
Expand Up @@ -122,6 +122,11 @@ class SiteSetting(Preferences):
blank=True
)

minisass_token = models.CharField(
default='',
blank=True
)

iucn_api_key = models.CharField(
max_length=255,
default='',
Expand Down
200 changes: 152 additions & 48 deletions bims/static/js/views/control_panel/third_party_layer_panel.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,9 @@ define(['shared', 'backbone', 'underscore', 'jqueryUi',
miniSASSSelected: false,
inWARDSelected: false,
fetchingInWARDSData: false,
fetchingMiniSASS: false,
inWARDSStationsUrl: "/bims_proxy/https://inwards.award.org.za/app_json/wq_stations.php",
miniSASSUrl: "/api/minisass-observations/",
events: {
'click .close-button': 'closeClicked',
'click .update-search': 'updateSearch',
Expand Down Expand Up @@ -39,6 +41,22 @@ define(['shared', 'backbone', 'underscore', 'jqueryUi',
image: image
});
},
miniSASSStyleFunction: function (feature) {
let properties = feature.getProperties();
let color = 'gray';
if (properties['color']) {
color = properties['color'];
} else {
color = '#1dc6c0';
}
let image = new ol.style.Circle({
radius: 5,
fill: new ol.style.Fill({color: color})
});
return new ol.style.Style({
image: image
});
},
addInWARDSLayer: function () {
this.inWARDSLayer = new ol.layer.Vector({
source: null,
Expand All @@ -48,44 +66,77 @@ define(['shared', 'backbone', 'underscore', 'jqueryUi',
this.map.addLayer(this.inWARDSLayer);
},
addMiniSASSLayer: function () {
let options = {
url: '/bims_proxy/http://minisass.org/geoserver/wms',
params: {
name: 'MiniSASS',
layers: 'miniSASS:minisass_observations',
format: 'image/png',
getFeatureFormat: 'text/html'
}
};
this.miniSASSLayer = new ol.layer.Tile({
source: new ol.source.TileWMS(options)
this.miniSASSLayer = new ol.layer.Vector({
source: null,
style: this.miniSASSStyleFunction
});
this.miniSASSLayer.setVisible(false);
this.map.addLayer(this.miniSASSLayer);
Shared.Dispatcher.trigger(
'layers:renderLegend',
options['params']['layers'],
options['params']['name'],
options['url'],
options['params']['layers'],
false
);
},
isValidCoordinate: function(coordinate) {
const [longitude, latitude] = coordinate;
if (
typeof longitude !== 'number' ||
typeof latitude !== 'number' ||
longitude < -180 ||
longitude > 180 ||
latitude < -90 ||
latitude > 90
) {
return false;
}
return true;
},
toggleMiniSASSLayer: function (e) {
let self = this;
this.miniSASSSelected = $(e.target).is(":checked");
if (this.miniSASSSelected) {
this.miniSASSLayer.setVisible(true);
// Move layer to top
this.map.removeLayer(this.miniSASSLayer);
this.map.getLayers().insertAt(this.map.getLayers().getLength(), this.miniSASSLayer);
let mapLegend = $('#map-legend');
mapLegend.find(`[data-name='${this.miniSASSLayer.getSource().getParams()['layers']}']`).show();
if (!mapLegend.is('visible')) {
Shared.Dispatcher.trigger('map:showMapLegends');

// Show fetching message
if (!this.fetchingMiniSASS) {
let fetchingMessage = $('<span class="fetching" style="font-size: 10pt; font-style: italic"> (fetching)</span>');
$(e.target).parent().find('.label').append(fetchingMessage);

$.ajax({
type: 'GET',
url: this.miniSASSUrl,
success: function (data) {
let geojson = {
"type": "FeatureCollection",
"features": []
}
for(let i=0; i < data.length; i++) {
let observation = data[i];
let coordinate = [parseFloat(observation.longitude), parseFloat(observation.latitude)];
if (self.isValidCoordinate(coordinate)) {
let properties = observation;
delete properties.longitude;
delete properties.latitude;
let feature = {
"type": "Feature",
"geometry": {"type": "Point", "coordinates": coordinate},
"properties": properties
}
geojson.features.push(feature);
}
}
let source = new ol.source.Vector({
features: (
new ol.format.GeoJSON()
).readFeatures(geojson, {featureProjection: 'EPSG:3857'})
});
self.miniSASSLayer.setSource(source);
$(e.target).parent().find('.fetching').remove();
}
})
this.fetchingMiniSASS = true;
}
} else {
this.miniSASSLayer.setVisible(false);
$('#map-legend').find(`[data-name='${this.miniSASSLayer.getSource().getParams()['layers']}']`).hide();
}
},
toggleInward: function (e) {
Expand Down Expand Up @@ -123,6 +174,68 @@ define(['shared', 'backbone', 'underscore', 'jqueryUi',
self.inWARDSLayer.setVisible(false);
}
},
objectToTable: function (obj) {
// Create the table and the table body
let table = document.createElement('table');
let tbody = document.createElement('tbody');

function handleValue(value) {
if (value && typeof value === 'object') {
try {
// Attempt to convert the object to a string
return JSON.stringify(value, getCircularReplacer(), 2);
} catch (error) {
// Fallback for objects that cannot be stringified
return '[Circular]';
}
} else {
return value !== null ? value : 'null';
}
}

function getCircularReplacer() {
const seen = new WeakSet();
return (key, value) => {
if (typeof value === "object" && value !== null) {
if (seen.has(value)) {
return "[Circular]";
}
seen.add(value);
}
return value;
};
}

for (let key in obj) {
if (obj.hasOwnProperty(key)) {
let tr = document.createElement('tr');

let tdKey = document.createElement('td');
tdKey.textContent = key;
tr.appendChild(tdKey);

let tdValue = document.createElement('td');
tdValue.textContent = handleValue(obj[key]);
tr.appendChild(tdValue);

tbody.appendChild(tr);
}
}

table.appendChild(tbody);

table.style.borderCollapse = 'collapse';
table.style.width = '100%';
table.style.margin = '20px 0';

let cells = table.querySelectorAll('td');
cells.forEach(cell => {
cell.style.border = '1px solid #ddd';
cell.style.padding = '8px';
});

return table;
},
showFeatureInfo: function (lon, lat, siteExist = false, featureData = null) {
if (!this.miniSASSSelected && !this.inWARDSelected) {
return false;
Expand Down Expand Up @@ -152,34 +265,25 @@ define(['shared', 'backbone', 'underscore', 'jqueryUi',

if (this.miniSASSSelected) {
const source = this.miniSASSLayer.getSource();
const getFeatureFormat = source.getParams()['getFeatureFormat'];
const layerName = source.getParams()['name'];
const queryLayer = source.getParams()['layers'];
let layerSource = source.getGetFeatureInfoUrl(
coordinate,
view.getResolution(),
view.getProjection(),
{'INFO_FORMAT': getFeatureFormat}
);
layerSource += '&QUERY_LAYERS=' + queryLayer;
$.ajax({
type: 'POST',
url: '/get_feature/',
data: {
'layerSource': layerSource
},
success: function (result) {
if (!result) {
return true;
}
const data = result['feature_data'];
if (!data) return true;
const pixel = this.map.getPixelFromCoordinate(coordinate);
let minisassData = [];
self.map.forEachFeatureAtPixel(pixel, function(feature) {
if (feature.getProperties().hasOwnProperty('minisass_ml_score')) {
minisassData.push(feature.getProperties())
}
if (minisassData.length > 0) {
let minisassDataTable = minisassData[0];
delete minisassDataTable['geometry'];
delete minisassDataTable['organisationtype'];
delete minisassDataTable['images'];
delete minisassDataTable['site'];
const minisassTable = $(self.objectToTable(minisassDataTable));
self.showContentToSidePanel(
lon, lat, layerName, data, siteExist, openSidePanel
lon, lat, minisassData[0]['sitename'], minisassTable.prop('outerHTML'), siteExist, openSidePanel
)
openSidePanel = true;
}
});

}
},
showContentToSidePanel: function (lon, lat, panelTitle, panelContent, siteExist, openSidePanel = false) {
Expand Down
4 changes: 2 additions & 2 deletions bims/templates/map_page/search-panel-templates.html
Original file line number Diff line number Diff line change
Expand Up @@ -631,13 +631,13 @@ <h5 class="modal-title" id="${modalId}Label">Save Polygon</h5>
<div class="row-title">
<input type="checkbox" value="MiniSASS"
class="mini-sass-check">
MiniSASS
<span class="label">MiniSASS</span>
</div>
<small class="text-muted">
MiniSASS is a citizen science tool which can be
used by anyone to monitor the health of a river.
The data are served with permission from <a
href="http://www.minisass.org/en/">http://www.minisass.org/en/</a>,
href="https://minisass.org/">https://minisass.org/</a>,
and further information about the tool is available
via this website.
</small>
Expand Down

0 comments on commit 41e6522

Please sign in to comment.