diff --git a/VIPSWeb/static/js/util.js b/VIPSWeb/static/js/util.js index a0c208de3e07437ac72ccb35edc35ebd754478c3..760530f24b8a5520c7077ef8b112707843ca5559 100755 --- a/VIPSWeb/static/js/util.js +++ b/VIPSWeb/static/js/util.js @@ -380,3 +380,38 @@ function isMomentJSAvailable() } } + +/** + * Depends on the value of currentLanguage and languageCode in settings.js + * @param cropCategory + * @returns {String} + */ +function getLocalizedCropCategoryName(cropCategory) +{ + // Fallback in case nothing works + if(cropCategory === null) + { + return "Unnamed"; + } + // Attempting the following languages (in order): current language, default language, English + var languages = [settings.currentLanguage, settings.languageCode, "en"]; + for(var j in languages) + { + for(var i in cropCategory.cropCategoryLocalSet) + { + var localeSet = cropCategory.cropCategoryLocalSet[i]; + if(localeSet.cropCategoryLocalPK.locale.trim() == languages[j].trim()) + { + return localeSet.localName; + } + } + } + // Then we try the latin name + if(cropCategory.defaultName !== null + && cropCategory.defaultName !== "") + { + return cropCategory.defaultName; + } + // Then we give up + return gettext("Unnamed"); +} diff --git a/VIPSWeb/templates/settings.js b/VIPSWeb/templates/settings.js index 5e0d72310d758bbed0261344793472616c61ef33..918e8f69193c4d484c3bdd7353e6aa83babfb866 100755 --- a/VIPSWeb/templates/settings.js +++ b/VIPSWeb/templates/settings.js @@ -52,5 +52,8 @@ var settings = { downloadSVG: gettext("Download SVG vector image"), contextButtonTitle: gettext("Chart context menu") } - } + }, + + // The attribution shown in the corner of the map + MAP_ATTRIBUTION : "© <a href='http://www.openstreetmap.org'>OpenStreetMap</a> contributors" }; diff --git a/observations/locale/de/LC_MESSAGES/django.mo b/observations/locale/de/LC_MESSAGES/django.mo index 9f1651026de521647d7079914362dc39e43581ab..71cbdf3e9d8d54be31066ec4ad8628bc2c1f2845 100755 Binary files a/observations/locale/de/LC_MESSAGES/django.mo and b/observations/locale/de/LC_MESSAGES/django.mo differ diff --git a/observations/locale/de/LC_MESSAGES/django.po b/observations/locale/de/LC_MESSAGES/django.po index 26a367570632e5d62c6a2648769a18298d2f7fff..dfb8ad3e5be956419763611e49102334ae948c6f 100755 --- a/observations/locale/de/LC_MESSAGES/django.po +++ b/observations/locale/de/LC_MESSAGES/django.po @@ -8,7 +8,7 @@ msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2017-03-14 16:07-0700\n" +"POT-Creation-Date: 2018-12-10 12:35+0100\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME <EMAIL@ADDRESS>\n" "Language-Team: LANGUAGE <LL@li.org>\n" @@ -26,15 +26,20 @@ msgstr "" msgid "View all observations" msgstr "" -#: templates/observations/detail.html:32 templates/observations/index.html:53 +#: templates/observations/detail.html:32 templates/observations/index.html:63 +#: templates/observations/index_new.html:46 +#: templates/observations/index_new.html:63 msgid "Organism" msgstr "" -#: templates/observations/detail.html:34 templates/observations/index.html:54 +#: templates/observations/detail.html:34 templates/observations/index.html:64 +#: templates/observations/index_new.html:50 +#: templates/observations/index_new.html:64 msgid "Crop" msgstr "" -#: templates/observations/detail.html:36 templates/observations/index.html:52 +#: templates/observations/detail.html:36 templates/observations/index.html:62 +#: templates/observations/index_new.html:62 msgid "Time of observation" msgstr "" @@ -55,6 +60,7 @@ msgid "Map view not available" msgstr "" #: templates/observations/index.html:25 templates/observations/index.html:28 +#: templates/observations/index_new.html:25 msgid "Observations" msgstr "" @@ -62,14 +68,39 @@ msgstr "" msgid "All pests" msgstr "" +#: templates/observations/index.html:49 +msgid "From" +msgstr "" + +#: templates/observations/index.html:53 +msgid "To" +msgstr "" + #: templates/observations/index.html:55 +msgid "Date search" +msgstr "" + +#: templates/observations/index.html:65 +#: templates/observations/index_new.html:65 msgid "Heading" msgstr "" -#: templates/observations/index.html:82 +#: templates/observations/index.html:96 msgid "Crops" msgstr "" -#: templates/observations/index.html:112 +#: templates/observations/index.html:136 msgid "View details" msgstr "" + +#: templates/observations/index_new.html:31 +msgid "Current date" +msgstr "" + +#: templates/observations/index_new.html:54 +msgid "Crop categories" +msgstr "" + +#: templates/observations/index_new.html:57 +msgid "Filter" +msgstr "" diff --git a/observations/locale/de/LC_MESSAGES/djangojs.mo b/observations/locale/de/LC_MESSAGES/djangojs.mo new file mode 100644 index 0000000000000000000000000000000000000000..71cbdf3e9d8d54be31066ec4ad8628bc2c1f2845 Binary files /dev/null and b/observations/locale/de/LC_MESSAGES/djangojs.mo differ diff --git a/observations/locale/de/LC_MESSAGES/djangojs.po b/observations/locale/de/LC_MESSAGES/djangojs.po new file mode 100644 index 0000000000000000000000000000000000000000..79356bc1b9ccbc1465b0ed748d077b2b3f388e3f --- /dev/null +++ b/observations/locale/de/LC_MESSAGES/djangojs.po @@ -0,0 +1,43 @@ +# SOME DESCRIPTIVE TITLE. +# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER +# This file is distributed under the same license as the PACKAGE package. +# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR. +# +#, fuzzy +msgid "" +msgstr "" +"Project-Id-Version: PACKAGE VERSION\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2018-12-10 12:38+0100\n" +"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" +"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n" +"Language-Team: LANGUAGE <LL@li.org>\n" +"Language: \n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: nplurals=2; plural=(n != 1);\n" + +#: static/observations/js/observationList.js:152 +msgid "Observation(s) found at location" +msgstr "" + +#: static/observations/js/observationList.js:168 +msgid "in" +msgstr "" + +#: static/observations/js/observationList.js:263 +msgid "Details" +msgstr "" + +#: static/observations/js/observationList.js:465 +msgid "Days since observation" +msgstr "" + +#: static/observations/js/observationList.js:468 +msgid "Days" +msgstr "" + +#: static/observations/js/observationList.js:470 +msgid "Older" +msgstr "" diff --git a/observations/locale/nb/LC_MESSAGES/django.mo b/observations/locale/nb/LC_MESSAGES/django.mo index 8ba93cf30c28a0c3c566cf5005ce6cd98e7794de..6a62268e9810a07d3b2fff93101609b92ec14374 100755 Binary files a/observations/locale/nb/LC_MESSAGES/django.mo and b/observations/locale/nb/LC_MESSAGES/django.mo differ diff --git a/observations/locale/nb/LC_MESSAGES/django.po b/observations/locale/nb/LC_MESSAGES/django.po index 5a30ff61786eb36f9c541f2ae18fd7e9826cc748..1a2d035813cab91f5e1d590177174aa4b59f5eee 100755 --- a/observations/locale/nb/LC_MESSAGES/django.po +++ b/observations/locale/nb/LC_MESSAGES/django.po @@ -8,7 +8,7 @@ msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2017-03-14 16:07-0700\n" +"POT-Creation-Date: 2018-12-10 12:35+0100\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME <EMAIL@ADDRESS>\n" "Language-Team: LANGUAGE <LL@li.org>\n" @@ -28,17 +28,22 @@ msgid "View all observations" msgstr "Se alle observasjoner" #: templates/observations/detail.html:32 -#: templates/observations/index.html:53 +#: templates/observations/index.html:63 +#: templates/observations/index_new.html:46 +#: templates/observations/index_new.html:63 msgid "Organism" msgstr "Organisme" #: templates/observations/detail.html:34 -#: templates/observations/index.html:54 +#: templates/observations/index.html:64 +#: templates/observations/index_new.html:50 +#: templates/observations/index_new.html:64 msgid "Crop" msgstr "Kultur" #: templates/observations/detail.html:36 -#: templates/observations/index.html:52 +#: templates/observations/index.html:62 +#: templates/observations/index_new.html:62 msgid "Time of observation" msgstr "Observasjonstidspunkt" @@ -60,6 +65,7 @@ msgstr "Kartvisning ikke tilgjengelig" #: templates/observations/index.html:25 #: templates/observations/index.html:28 +#: templates/observations/index_new.html:25 msgid "Observations" msgstr "Observasjoner" @@ -67,16 +73,41 @@ msgstr "Observasjoner" msgid "All pests" msgstr "Alle organismer" +#: templates/observations/index.html:49 +msgid "From" +msgstr "Fra" + +#: templates/observations/index.html:53 +msgid "To" +msgstr "Til" + #: templates/observations/index.html:55 +msgid "Date search" +msgstr "Datosøk" + +#: templates/observations/index.html:65 +#: templates/observations/index_new.html:65 msgid "Heading" msgstr "Overskrift" -#: templates/observations/index.html:82 +#: templates/observations/index.html:96 #, fuzzy msgid "Crops" msgstr "Kultur" -#: templates/observations/index.html:112 +#: templates/observations/index.html:136 msgid "View details" msgstr "Se detaljer" +#: templates/observations/index_new.html:31 +msgid "Current date" +msgstr "Gjeldende dato" + +#: templates/observations/index_new.html:54 +msgid "Crop categories" +msgstr "Kulturkategorier" + +#: templates/observations/index_new.html:57 +msgid "Filter" +msgstr "Gjør utvalg" + diff --git a/observations/locale/nb/LC_MESSAGES/djangojs.mo b/observations/locale/nb/LC_MESSAGES/djangojs.mo new file mode 100644 index 0000000000000000000000000000000000000000..70cb5edf086bb8d5998d09c2ffa3bfaaf0913090 Binary files /dev/null and b/observations/locale/nb/LC_MESSAGES/djangojs.mo differ diff --git a/observations/locale/nb/LC_MESSAGES/djangojs.po b/observations/locale/nb/LC_MESSAGES/djangojs.po new file mode 100644 index 0000000000000000000000000000000000000000..e7acca774b2e698c7983a8b61435a67068b4d78a --- /dev/null +++ b/observations/locale/nb/LC_MESSAGES/djangojs.po @@ -0,0 +1,44 @@ +# SOME DESCRIPTIVE TITLE. +# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER +# This file is distributed under the same license as the PACKAGE package. +# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR. +# +#, fuzzy +msgid "" +msgstr "" +"Project-Id-Version: PACKAGE VERSION\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2018-12-10 12:38+0100\n" +"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" +"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n" +"Language-Team: LANGUAGE <LL@li.org>\n" +"Language: \n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: nplurals=2; plural=(n != 1);\n" + +#: static/observations/js/observationList.js:152 +msgid "Observation(s) found at location" +msgstr "Observasjon(er) funnet på lokalitet" + +#: static/observations/js/observationList.js:168 +msgid "in" +msgstr "i" + +#: static/observations/js/observationList.js:263 +msgid "Details" +msgstr "Detaljer" + +#: static/observations/js/observationList.js:465 +msgid "Days since observation" +msgstr "Dager siden observasjon" + +#: static/observations/js/observationList.js:468 +msgid "Days" +msgstr "Dager" + +#: static/observations/js/observationList.js:470 +msgid "Older" +msgstr "Eldre" + diff --git a/observations/locale/zh_CN/LC_MESSAGES/django.mo b/observations/locale/zh_CN/LC_MESSAGES/django.mo index 2ccebfda8096607d428c0e2993b9d9f8a9bb3e1b..5dba12b8a50c6a6307127178d6370a861f97f57a 100755 Binary files a/observations/locale/zh_CN/LC_MESSAGES/django.mo and b/observations/locale/zh_CN/LC_MESSAGES/django.mo differ diff --git a/observations/locale/zh_CN/LC_MESSAGES/django.po b/observations/locale/zh_CN/LC_MESSAGES/django.po index 555c8ddc17f851ec567434249956fdf92d1b7eef..3a796fe39ecfb2886bac1b74b5752038b6386e10 100755 --- a/observations/locale/zh_CN/LC_MESSAGES/django.po +++ b/observations/locale/zh_CN/LC_MESSAGES/django.po @@ -7,7 +7,7 @@ msgid "" msgstr "" "Project-Id-Version: \n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2017-03-14 16:07-0700\n" +"POT-Creation-Date: 2018-12-10 12:35+0100\n" "PO-Revision-Date: 2016-10-27 11:26+0200\n" "Last-Translator: \n" "Language-Team: \n" @@ -25,15 +25,20 @@ msgstr "观测" msgid "View all observations" msgstr "查看所有观测" -#: templates/observations/detail.html:32 templates/observations/index.html:53 +#: templates/observations/detail.html:32 templates/observations/index.html:63 +#: templates/observations/index_new.html:46 +#: templates/observations/index_new.html:63 msgid "Organism" msgstr "生物" -#: templates/observations/detail.html:34 templates/observations/index.html:54 +#: templates/observations/detail.html:34 templates/observations/index.html:64 +#: templates/observations/index_new.html:50 +#: templates/observations/index_new.html:64 msgid "Crop" msgstr "作物" -#: templates/observations/detail.html:36 templates/observations/index.html:52 +#: templates/observations/detail.html:36 templates/observations/index.html:62 +#: templates/observations/index_new.html:62 msgid "Time of observation" msgstr "观测时间" @@ -54,6 +59,7 @@ msgid "Map view not available" msgstr "" #: templates/observations/index.html:25 templates/observations/index.html:28 +#: templates/observations/index_new.html:25 msgid "Observations" msgstr "观测" @@ -61,16 +67,41 @@ msgstr "观测" msgid "All pests" msgstr "所有病虫害" +#: templates/observations/index.html:49 +msgid "From" +msgstr "" + +#: templates/observations/index.html:53 +msgid "To" +msgstr "" + #: templates/observations/index.html:55 +msgid "Date search" +msgstr "" + +#: templates/observations/index.html:65 +#: templates/observations/index_new.html:65 msgid "Heading" msgstr "标题" -#: templates/observations/index.html:82 +#: templates/observations/index.html:96 #, fuzzy #| msgid "Crop" msgid "Crops" msgstr "作物" -#: templates/observations/index.html:112 +#: templates/observations/index.html:136 msgid "View details" msgstr "看细节" + +#: templates/observations/index_new.html:31 +msgid "Current date" +msgstr "" + +#: templates/observations/index_new.html:54 +msgid "Crop categories" +msgstr "" + +#: templates/observations/index_new.html:57 +msgid "Filter" +msgstr "" diff --git a/observations/locale/zh_CN/LC_MESSAGES/djangojs.mo b/observations/locale/zh_CN/LC_MESSAGES/djangojs.mo new file mode 100644 index 0000000000000000000000000000000000000000..314bedb17d5caa2d590b5786ba725a4f8d4dab37 Binary files /dev/null and b/observations/locale/zh_CN/LC_MESSAGES/djangojs.mo differ diff --git a/observations/locale/zh_CN/LC_MESSAGES/djangojs.po b/observations/locale/zh_CN/LC_MESSAGES/djangojs.po new file mode 100644 index 0000000000000000000000000000000000000000..0e322ed22428a86522d84c295e9564b7ef2a7d2d --- /dev/null +++ b/observations/locale/zh_CN/LC_MESSAGES/djangojs.po @@ -0,0 +1,43 @@ +# SOME DESCRIPTIVE TITLE. +# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER +# This file is distributed under the same license as the PACKAGE package. +# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR. +# +#, fuzzy +msgid "" +msgstr "" +"Project-Id-Version: PACKAGE VERSION\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2018-12-10 12:38+0100\n" +"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" +"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n" +"Language-Team: LANGUAGE <LL@li.org>\n" +"Language: \n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: nplurals=1; plural=0;\n" + +#: static/observations/js/observationList.js:152 +msgid "Observation(s) found at location" +msgstr "" + +#: static/observations/js/observationList.js:168 +msgid "in" +msgstr "" + +#: static/observations/js/observationList.js:263 +msgid "Details" +msgstr "" + +#: static/observations/js/observationList.js:465 +msgid "Days since observation" +msgstr "" + +#: static/observations/js/observationList.js:468 +msgid "Days" +msgstr "" + +#: static/observations/js/observationList.js:470 +msgid "Older" +msgstr "" diff --git a/observations/static/observations/js/observationList.js b/observations/static/observations/js/observationList.js new file mode 100644 index 0000000000000000000000000000000000000000..1ad744f711e880a43c3be3b4c7ff0090767b137d --- /dev/null +++ b/observations/static/observations/js/observationList.js @@ -0,0 +1,578 @@ +/* + * Copyright (c) 2018 NIBIO <http://www.nibio.no/>. + * + * This file is part of VIPSWeb. + * VIPSLogic is free software: you can redistribute it and/or modify + * it under the terms of the NIBIO Open Source License as published by + * NIBIO, either version 1 of the License, or (at your option) any + * later version. + * + * VIPSWeb is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * NIBIO Open Source License for more details. + * + * You should have received a copy of the NIBIO Open Source License + * along with VIPSWeb. If not, see <http://www.nibio.no/licenses/>. + * + */ + +var allObservations = []; // Populated asynchronously +var drawnFeatures = []; // Populated asynchronously +var currentDate; // Initialized in initMap +var map; +var observationLayer; + +/** + * Default coloring + * @type Array + */ +var ageColors = [ + [7, 'rgba(233, 13, 0, 1.0)'], // Red #e90d00 + [30, 'rgba(253, 202, 00, 1.0)'], // Orange #fdca00 + [60, 'rgba(0, 120, 253, 1.0)'], // Blue #0078fd +]; + + +var ageStyles, poiDetails, popOverlay; + +/* + * Observation map + * @author Tor-Einar Skog <tor-einar.skog@nibio.no> + */ +var initMap = function( + center, + zoomLevel, + organizationId, + from, + to, + pestId, + cropId, + cropCategoryId, + customAgeColors + ) +{ + // Allows for dynamic configuration of observation age groups and styles + if(typeof customAgeColors !== 'undefined') + { + ageColors = customAgeColors; + } + initAgeStyles(); + + // This is the page wide artificial time state of the page + currentDateInMillis = moment(to).format("X") * 1000; // Reference for coloring the observations + + // Background layer is OpenStreetMap + var backgroundLayer = new ol.layer.Tile({ + source: new ol.source.OSM({ + attributions: [ + new ol.Attribution({ + html: settings.MAP_ATTRIBUTION + }) + ] + }) + }); + + currentDateInMillis = moment().add(settings.systemTimeOffsetMonths,"months").format("X") * 1000; // Reference for coloring the observations + var observationFeatures = new ol.Collection(); // Starting on empty + observationLayer = new ol.layer.Vector({ + source: new ol.source.Vector({ + features: observationFeatures + }), + style: getCorrectStyle // Defined further down + }); + + // Layer for popup + popOverlay = new ol.Overlay({ + element: document.getElementById("popover") + }); + + // Creating the map + map = new ol.Map({ + target: 'observationMap', + layers: [backgroundLayer, observationLayer], + overlays: [popOverlay], + renderer: 'canvas' + }); + + // For some reason, we have to wait with selecting the layer until after the map has + // been initialized. Otherwise the popover will not display. + // OpenLayers is probably doing something that changes the DOM object + // Using Bootstrap's popover plugin. See http://getbootstrap.com/javascript/#popovers + poiDetails = $("#popover"); + + // Setting zoom and center for the map (need to do this after creating map. so that we kan transform our + // center to correct map projection) + var centerPosition = ol.proj.transform(center, 'EPSG:4326', map.getView().getProjection().getCode()); + var view = new ol.View({ + center: centerPosition, + zoom:zoomLevel + }); + map.setView(view); + + // Need to build the query string + var params = []; + + if(from !== "") + { + params.push("from=" + from); + } + if(to !== "") + { + params.push("to=" + to); + } + if(pestId !== null) + { + params.push("pestId=" + pestId); + } + if(cropId !== null) + { + params.push("cropId=" + cropId); + } + if(cropCategoryId !== null) + { + params.push("cropCategoryId=" + cropCategoryId); + } + + // Get observations from backend + $.getJSON( "/vipslogicproxy/rest/observation/list/filter/" + organizationId + (params.length > 0 ? "?" + params.join("&") : ""), function( data ) { + allObservations = data; + renderObservationTable(data); + renderObservationFeatures(); + + }); + + // Clicking on a part of the map with one or more features will result in + // the observation features being displayed + map.on('singleclick', function(evt) { + var pixel = map.getEventPixel(evt.originalEvent); + var coordinate = map.getEventCoordinate(evt.originalEvent); + displayFeatureDetails(pixel, coordinate); + }); +}; + +/** + * When user click on the map: check if there are observation features at that point, + * and them display information about them in a popup window + */ +var displayFeatureDetails = function(pixel, coordinate) { + var features = []; + map.forEachFeatureAtPixel(pixel, function(feature,layer){ + features.push(feature); + }); + + if (features.length > 0) { + titleHTML = gettext("Observation(s) found at location"); + var observationsHTML = "<ul style='list-style-type: none; margin: 0; padding: 0;'>"; + + var observations = []; + for(var i in features) + { + observations.push(getObservation(parseInt(features[i].get("observationId")))); + } + + // Sort observations in descending order + observations.sort(function(a,b){ + return a.timeOfObservation >= b.timeOfObservation ? -1 : 1; + }); + + // Create HTML list with observations + for(var i in observations){ + var observation = observations[i]; + var obsDate = moment(observation.timeOfObservation).format("YYYY-MM-DD"); + observationsHTML += "<li><i style='color: " + getObservationAgeColor(observation.timeOfObservation) + ";' class='fa fa-square' aria-hidden='true'></i> [" + obsDate + "] <a href='/observations/" + observation.observationId + "' target='new'>" + observation.organismName + " " + gettext ("in") + " " + observation.cropOrganismName.toLowerCase() + "</a></li>"; + } + observationsHTML += "</ul>"; + + // Position the popup, and hiding it + poiDetails.popover('destroy'); + // Placing and displaying the overlay + popOverlay.setPosition(coordinate); + poiDetails.popover({ + animation: true, + trigger: 'manual', + html: true, + placement: "auto top", + title: titleHTML, + content: observationsHTML + }); + + poiDetails.popover('show'); + } else { + // If no features at clicked point, hide popup (if any) + poiDetails.popover('destroy'); + } +}; + +/** + * Shows the observations on the map + */ +var renderObservationFeatures = function() +{ + var geoJSON = {"type":"FeatureCollection","features":[]}; + for(var i=0;i<allObservations.length;i++) + { + var observation = allObservations[i]; + var obsFeatures = null; + + // Showing only publicly shared observations with geolocation information + if(!observation.locationIsPrivate && observation.geoInfo !== null && observation.geoInfo.trim() !== "") + { + obsFeatures = JSON.parse(observation.geoInfo).features; + } + else + { + continue; + } + + for(var j=0; j<obsFeatures.length; j++) + { + geoJSON.features.push(obsFeatures[j]); + } + } + + var format = new ol.format.GeoJSON(); + drawnfeatures = format.readFeatures(geoJSON, { + dataProjection: 'EPSG:4326', + featureProjection: map.getView().getProjection().getCode() + }); + observationLayer.getSource().clear(); + observationLayer.getSource().addFeatures(drawnfeatures); +} + +/** + * Pull the observation from global list + */ +var getObservation = function(observationId) +{ + for(var i=0; i<allObservations.length;i++) + { + if(allObservations[i].observationId == parseInt(observationId)) + { + return allObservations[i]; + } + } + //console.info("Could not find observation with ID=" + observationId) + return null; +}; + +/** + * Show the table of observations + */ +var renderObservationTable = function(data) +{ + var tbody = document.getElementById("observationTableBody"); + var tbodyHTML = []; + for(var i=0; i<data.length;i++) + { + var obs = data[i]; + tbodyHTML.push("<tr>"); + tbodyHTML.push("<td>" + moment(obs.timeOfObservation).format("YYYY-MM-DD HH:mm ZZ") + "</td>"); + tbodyHTML.push("<td>" + obs.organismName + "</td>"); + tbodyHTML.push("<td>" + obs.cropOrganismName + "</td>"); + tbodyHTML.push("<td>" + obs.observationHeading + "</td>"); + tbodyHTML.push("<td><a href='/observations/" + obs.observationId + "' target='new'>" + gettext("Details") + "</a></td>"); + tbodyHTML.push("</tr>"); + } + tbody.innerHTML = tbodyHTML.join("\n"); +}; + +/** + * Render the select field for pest or crop + */ +var renderOrganismField = function(organismList, fieldId, selectedId) +{ + // Sort alphabetically by local name + organismList.sort(function(a,b){ + if (getLocalizedOrganismName(a) < getLocalizedOrganismName(b)) return -1; + if (getLocalizedOrganismName(a) > getLocalizedOrganismName(b)) return 1; + return 0; + }); + var list = document.getElementById(fieldId); + list.options.length=0; + list.options[0] = new Option("",""); // For the chosenjs to print data-placeholder + for(var i=0;i<organismList.length;i++) + { + var organism = organismList[i]; + var newOption = new Option(getLocalizedOrganismName(organism),organism.organismId); + if(organism.organismId === selectedId) + { + newOption.selected = true; + } + list.options[list.options.length] = newOption; + } +}; + +/** + * Render the crop category select field + */ +var renderCropCategoryField = function(cropCategoryList, selectedId) +{ + // Sort by local name + cropCategoryList.sort(function(a,b){ + if (getLocalizedCropCategoryName(a) < getLocalizedCropCategoryName(b)) return -1; + if (getLocalizedCropCategoryName(a) > getLocalizedCropCategoryName(b)) return 1; + return 0; + }); + var list = document.getElementById("cropCategoryList"); + list.options.length=0; + list.options[0] = new Option("",""); // For the chosenjs to print data-placeholder + for(var i=0;i<cropCategoryList.length;i++) + { + var cropCategory = cropCategoryList[i]; + var newOption = new Option(getLocalizedCropCategoryName(cropCategory),cropCategory.cropCategoryId); + if(cropCategory.cropCategoryId === selectedId) + { + newOption.selected = true; + } + list.options[list.options.length] = newOption; + } +}; + +var initForm = function(organizationId, + pestId, + cropId, + cropCategoryId, + postRenderFormActions + ) +{ + $.getJSON( "/vipslogicproxy/rest/observation/pest/" + organizationId , function( pestList ) { + renderOrganismField(pestList, "observationPestList", pestId); + $.getJSON( "/vipslogicproxy/rest/observation/crop/" + organizationId , function( cropList ) { + renderOrganismField(cropList, "observationCropList", cropId); + $.getJSON( "/vipslogicproxy/rest/organism/cropcategory/" + organizationId , function( cropCategoryList ) { + renderCropCategoryField(cropCategoryList, cropCategoryId); + postRenderFormActions(); // Activate chosen.js + }); + }); + }); +}; + +// Global configs +/** + * Default style for old (outdated) observations + */ +var styleOld = + new ol.style.Style({ + fill: new ol.style.Fill({ + color: 'rgba(173, 173, 173, 0.5)' + }), + stroke: new ol.style.Stroke({ + color: '#000000', + width: 1 + }), + image: new ol.style.Circle({ + radius: 6, + fill: new ol.style.Fill({ + color: '#adadad' + }), + stroke: new ol.style.Stroke({ + color: '#000000', + width: 1 + }) + }) + }); + +/** + * If an observation should be hidden entirely + */ + var styleInvisible = new ol.style.Style({ + fill: new ol.style.Fill({ + color: 'rgba(0, 0, 0, 0.0)' + }), + stroke: new ol.style.Stroke({ + color: 'rgba(0, 0, 0, 0.0)', + width: 0 + }), + image: new ol.style.Circle({ + radius: 0, + fill: new ol.style.Fill({ + color: 'rgba(0, 0, 0, 0.0)' + }) + }) + }); + + /** + * Using config at top of file (or customAgeColors passed in from web page) for dynamic style creation + */ + var initAgeStyles = function(){ + ageStyles = []; + for(var i in ageColors) + { + ageStyles.push([ + ageColors[i][0], + new ol.style.Style({ + fill: new ol.style.Fill({ + color: ageColors[i][1].replace("1.0","0.2") + }), + stroke: new ol.style.Stroke({ + color: ageColors[i][1], + width: 1 + }), + image: new ol.style.Circle({ + radius: 6, + fill: new ol.style.Fill({ + color: ageColors[i][1] + }), + stroke: new ol.style.Stroke({ + color: '#000000', + width: 1 + }) + }) + }) + ]); + } + }; + + /** + * Based on the observation's age relative to "currentDate", choose the appropriate style for map rendering + */ +var getCorrectStyle = function(feature){ + var age = getObservationRelativeAge(feature); + if(age == null) + { + return; + } + if(age < 0) + { + return styleInvisible; + } + for(var i in ageStyles) + { + if(age < ageStyles[i][0]) + { + return ageStyles[i][1]; + } + } + return styleOld; + }; + +/** + * Returns the observation's age in days relative to the "current date", which is defined by the value of the "dayInPeriod" range input + */ +var getObservationRelativeAge = function(feature) +{ + var observation = getObservation(feature.get("observationId")); // TODO include observations timestamp in feature. Speeds things up + if(observation == null) + { + console.info("No obs with id=" + feature.get("observationId")); + return null; // Means invisible + } + return Math.floor((currentDateInMillis - getUnixTimestampFromJSON(observation.timeOfObservation)) / (1000 * 60 * 60 * 24)) + 1; +}; + +/** + * Using configured ageColors + * @param aDate + * @returns + */ +function getObservationAgeColor(aDate) +{ + var age = getDaysSince(aDate); + if(age == null) + { + return; + } + if(age < 0) + { + return styleInvisible.getFill().getColor(); + } + for(var i in ageColors) + { + if(age < ageColors[i][0]) + { + return ageColors[i][1]; + } + } + return styleOld.getFill().getColor(); +} + +/** + * Using current system time, counting days since this day + */ +var getDaysSince = function(JSONDate) +{ + return Math.floor((currentDateInMillis - getUnixTimestampFromJSON(JSONDate)) / (1000 * 60 * 60 * 24)) + 1; +}; + +/** + * Sets colors and values for the map legend + * @returns + */ +function initMapLegend() +{ + var lBox = document.getElementById("legend"); + var html = "<div><strong>" + gettext("Days since observation") + "</strong></div><ul>"; + for(var i in ageColors) + { + html += '<li><i style="color: ' + ageColors[i][1] + ';" class="fa fa-square" aria-hidden="true"></i> ' + (i > 0 ? ageColors[i-1][0] + 1 : '0') + '-' + ageColors[i][0] + ' ' + gettext("Days").toLowerCase() + '</li>'; + } + html += '<li><i style="color: black;" class="fa fa-square" aria-hidden="true"></i> ' + gettext("Older") + '</li>'; + html += "</ul>"; + lBox.innerHTML = html; +} + +var from = moment(document.getElementById("dateFrom").value); + +/** + * Sets the current date in the observation "player" + * @param rangeBar + * @returns + */ +function updateCurrentDate(rangeBar){ + currentDate = from.clone(); + currentDate.add(parseInt(rangeBar.value)-1,"days"); + document.getElementById("dayInPeriodDate").innerHTML=currentDate.format('YYYY-MM-DD'); + currentDateInMillis = currentDate.format("X") * 1000; + observationLayer.getSource().changed(); + //console.info(currentDate); +} + +/** + * Updates the map observations given the date + * @param rangeBar + * @returns + */ +function updateMap(rangeBar){ + currentDate = from.clone(); + currentDate.add(parseInt(rangeBar.value)-1,"days"); + currentDateInMillis = currentDate.format("X") * 1000; + observationLayer.getSource().changed(); +} + +/** + * Moves everything one day forward + */ +var moveCurrentDayForward = function(){ + theRange = document.getElementById("dayInPeriod"); + if(parseInt(theRange.value) < theRange.max) + { + theRange.value = parseInt(theRange.value) +1; + theRange.oninput(); + } + else{ + togglePlay(document.getElementById("playButton")); + } +} + +// Placeholder for the JS timer function called for moving time ("dayInPeriod" range input) forward +var intervalId = null; // See below + +/** + * Switches between play and pause + * @param theButton + * @returns + */ +function togglePlay(theButton){ + if(intervalId == null) + { + intervalId = setInterval(moveCurrentDayForward, 250); + theButton.innerHTML = '<i class="fa fa-pause" aria-hidden="true"></i>'; + } + else + { + clearInterval(intervalId); + intervalId = null; + theButton.innerHTML = '<i class="fa fa-play" aria-hidden="true"></i>'; + } +} \ No newline at end of file diff --git a/observations/templates/observations/index.html b/observations/templates/observations/index.html old mode 100755 new mode 100644 index 1ae0f0297654e13c936057e5374ac74a2c96c6ed..7049fcc911a1f51eccf1aedb48ce0e8fb76bd9bc --- a/observations/templates/observations/index.html +++ b/observations/templates/observations/index.html @@ -3,7 +3,7 @@ {% comment %} # -# Copyright (c) 2016 NIBIO <http://www.nibio.no/>. +# Copyright (c) 2018 NIBIO <http://www.nibio.no/>. # # This file is part of VIPSWeb. # VIPSWeb is free software: you can redistribute it and/or modify @@ -21,193 +21,120 @@ # {% endcomment %} -{% load i18n %} +{% load i18n l10n %} {% block title%}{% trans "Observations" %}{%endblock%} {% block content %} - - <h1>{% trans "Observations" %}</h1> - <div class="row"> - <div class="col-xs-3"> - <div class="form-group"> - <select id="pestFilterList" class="form-control" onchange="filterObservations();"> - <option value="-1">{% trans "All pests" %}</option> - </select> - </div> - </div> - <div class="col-xs-3"> - <div class="form-group"> - <select name="cropCategoryFilterList" id="cropCategoryFilterList" class="form-control chosen-select" multiple="multiple" onchange="filterObservations();"> - {% for crop_category in crop_categories|dictsort:"name" %} - <option value="{{crop_category.crop_category_id}}">{{crop_category.name}}</option> - {% endfor %} - </select> - </div> - </div> - <div class="col-xs-6"> - <form class="form-inline" method="get" action="/observations"> - <div class="form-group"> - <input class="form-control" type="date" name="{{form.timeOfObservationFrom.html_name}}" value="{{form.timeOfObservationFrom.value | default_if_none:""}}" placeholder="{% trans "From" %}"/> - </div> - - - <div class="form-group"> - <input class="form-control" type="date" name="{{form.timeOfObservationTo.html_name}}" value="{{form.timeOfObservationTo.value | default_if_none:""}}" placeholder="{% trans "To" %}"/> - </div> - <button type="submit" class="btn btn-primary">{% trans "Date search" %}</button> - </form> - </div> +<h1>{% trans "Observations" %}</h1> +<div id="observationMap" class="map" style="position:relative;"> + <div id="popover"></div> + <div class="form-group" id="progressBar"> + <label for="dayInPeriod">{% trans "Current date"%}: <span id="dayInPeriodDate">{{to|safe}}</span></label><br/> + <button type="button" id="playButton" class="btn" onclick="togglePlay(this);"><i class="fa fa-play" aria-hidden="true"></i></button> + <input type="range" id="dayInPeriod" name="dayInPeriod" min="1" max="{{period_days}}" step="1" value="{{period_days}}" oninput="updateCurrentDate(this);" onchange="updateMap(this);"/> </div> - <table class="table"> - <thead> - <tr> - <th>{% trans "Time of observation" %}</th> - <th>{% trans "Organism" %}</th> - <th>{% trans "Crop" %}</th> - <th>{% trans "Heading" %}</th> - <th></th> - </tr> - </thead> - <tbody id="observationList"></tbody> - </table> - + <div id="legend"></div> +</div> +<form class="form-inline" method="get" action=""> + <div class="form-group"> + <input class="form-control" type="date" id="dateFrom" name="from" value="{{from|safe}}"/> + </div> + - + <div class="form-group"> + <input class="form-control" type="date" id="dateTo" name="to" value="{{to|safe}}"/> + </div> + <div class="form-group"> + <select name="pestId" id="observationPestList" class="form-control chosen-select" data-placeholder="{% trans "Organism" %}"> + </select> + </div> + <div class="form-group"> + <select name="cropId" id="observationCropList" class="form-control chosen-select" data-placeholder="{% trans "Crop" %}"> + </select> + </div> + <div class="form-group"> + <select name="cropCategoryId" id="cropCategoryList" class="form-control chosen-select" data-placeholder="{% trans "Crop categories" %}"> + </select> + </div> + <button type="submit" class="btn btn-default">{% trans "Filter" %}</button> + </form> +<div class="table-responsive"> + <table class="table table-striped" id="observationTable"> + <thead> + <th>{% trans "Time of observation" %}</th> + <th>{% trans "Organism" %}</th> + <th>{% trans "Crop" %}</th> + <th>{% trans "Heading" %}</th> + <th></th> + </thead> + <tbody id="observationTableBody"> + </tbody> + </table> + </div> {% endblock %} {% block extendCSS %} -<link href="{% static "css/3rdparty/jquery-ui.min.css" %}" rel="stylesheet" media="screen" /> +<link rel="stylesheet" href="{% static "css/3rdparty/ol.css" %}" type="text/css"> <link href="{% static "css/3rdparty/chosen.min.css" %}" rel="stylesheet" media="screen" /> <link href="{% static "css/3rdparty/chosen-bootstrap.css" %}" rel="stylesheet" media="screen" /> +<style type="text/css"> + #progressBar{ + position: absolute; + bottom: 5px; + left: 10px; + z-index: 1000; + background-color: white; + border-radius: 10px; + padding: 10px; + } + #legend{ + position: absolute; + bottom: 115px; + left: 10px; + z-index: 1000; + background-color: white; + border-radius: 10px; + padding: 10px; + } + #legend ul { + list-style: none; + padding:0; + margin:0; + } +</style> {% endblock %} {% block customJS %} <script type="text/javascript" src="{% url "django.views.i18n.javascript_catalog" %}"></script> -<script type="text/javascript" src="{% static "js/3rdparty/modernizr_custom.js" %}"></script> -<script type="text/javascript" src="{% static "js/3rdparty/jquery-ui.min.js" %}"></script> -<script type="text/javascript" src="{% static "js/3rdparty/chosen.jquery.min.js" %}"></script> +<script type="text/javascript" src="{% static "js/3rdparty/ol-debug.js" %}"></script> <script type="text/javascript" src="{% static "js/3rdparty/moment.min.js" %}"></script> -<script type="text/javascript" src="{% url "views.settings_js" %}"></script> +<script type="text/javascript" src="{% static "js/3rdparty/chosen.jquery.min.js" %}"></script> <script type="text/javascript" src="{% static "js/util.js" %}"></script> +<script type="text/javascript" src="{% url "views.settings_js" %}"></script> +<script type="text/javascript" src="{% static "observations/js/observationList.js" %}"></script> <script type="text/javascript"> - var allObservations = []; - var allPests = []; - var allPestIds = []; - var cropCategories = { - {% for crop_category in crop_categories %} - {{crop_category.crop_category_id}}:{{crop_category.crop_ids}}{% if not forloop.last %},{% endif %} - {% endfor %} - }; + /*var colors = [ + [7, 'rgba(255, 0, 0, 1.0)'], // Red + [30, 'rgba(244, 206, 66, 1.0)'], // Orange + [60, 'rgba(0, 0, 255, 1.0)'], // Blue + ];*/ $(document).ready(function() { - $(".chosen-select").chosen({placeholder_text_multiple:"{% trans "Crops" %}"}); - {% if form.timeOfObservationFrom.value or form.timeOfObservationTo.value %} - $.getJSON("/vipslogicproxy/rest/observation/broadcast/list/" + settings.vipsOrganizationId+ "?timeOfObservationFrom={{form.timeOfObservationFrom.value | default_if_none:""}}&timeOfObservationTo={{form.timeOfObservationTo.value | default_if_none:""}}", function( json ) { - {% else %} - $.getJSON("/vipslogicproxy/rest/observation/broadcast/list/" + settings.vipsOrganizationId+ "?season=" + getCurrentYear(), function( json ) { - {% endif %} - allObservations = json; - allObservations.sort(sortObservationMessages).reverse(); - for(var i in allObservations) - { - var currentOrganism = allObservations[i].organism; - if(allPestIds.indexOf(currentOrganism.organismId) < 0) - { - allPests.push(currentOrganism); - allPestIds.push(currentOrganism.organismId); - } - } - renderPestFilterList(); - renderObservations(allObservations); - }); - - // Make sure that there is a date picker present for HTML5 - // date input fields - if (!Modernizr.inputtypes.date) { - $('input[type=date]').datepicker(settings.datePickerConfig); - } + initMap( + [{{settings.MAP_CENTER_LONGITUDE|unlocalize}},{{settings.MAP_CENTER_LATITUDE|unlocalize}}], + 4, + {{settings.VIPS_ORGANIZATION_ID}}, + "{{from|safe}}", + "{{to|safe}}", + {{pest_id|default:"null"}}, + {{crop_id|default:"null"}}, + {{crop_category_id|default:"null"}} + ); }); + initMapLegend(); - function renderObservations(observations) - { - var tableHTML = ""; - for(var i in observations){ - var observation = observations[i]; - tableHTML += [ - "<tr>", - "<td>",getSimpleFormattedTimestamp(observation.timeOfObservation),"</td>", - "<td>", getLocalizedOrganismName(observation.organism)," <i>(", observation.organism.latinName, ")</i></td>", - "<td>", getLocalizedOrganismName(observation.cropOrganism) ," <i>(", observation.cropOrganism.latinName, ")</i></td>", - "<td>", observation.observationHeading, "</td>", - "<td><a href='/observations/", observation.observationId, "'>{% trans "View details" %}</a></td>", - "</tr>" - ].join(""); - } - document.getElementById("observationList").innerHTML = tableHTML; - } - - function renderPestFilterList() - { - var pestFilterList = document.getElementById("pestFilterList"); - pestFilterList.options.length = 1; - allPests.sort(sortOrganisms); - for(var i in allPests) - { - var pestOpt = new Option(getLocalizedOrganismName(allPests[i]), allPests[i].organismId); - pestFilterList.options[pestFilterList.options.length] = pestOpt; - } - - } - - function filterOnCropGroup(cropGroupFilterList) - { - var selectedCropGroupId = parseInt(cropGroupFilterList.options[cropGroupFilterList.options.selectedIndex].value); - } - - function filterObservations() - { - var pestFilterList = document.getElementById("pestFilterList"); - var cropCategoryFilterList = document.getElementById("cropCategoryFilterList"); - - var selectedPestOrganismId = parseInt(pestFilterList.options[pestFilterList.options.selectedIndex].value); - - var selectedCropCategoryIds = getMultipleSelectedValues(cropCategoryFilterList); - //console.info(selectedCropCategoryIds); - - var filteredObservations = []; - - for(var i=0;i<allObservations.length;i++) - { - var obs = allObservations[i]; - - var validCropCategory = false; - if(selectedCropCategoryIds.length > 0) - { - for(var j=0;j<selectedCropCategoryIds.length;j++) - { - if(cropCategories[selectedCropCategoryIds[j]].indexOf(obs.cropOrganism.organismId) >= 0) - { - validCropCategory = true; - } - } - } - else - { - validCropCategory = true; - } - - var validPest = false; - if(selectedPestOrganismId == -1) - { - validPest = true; - } - else if(obs.organism.organismId == selectedPestOrganismId) - { - validPest = true; - } - - if(validCropCategory && validPest) - { - filteredObservations.push(obs); - } - } - - renderObservations(filteredObservations); - } - + initForm({{settings.VIPS_ORGANIZATION_ID}}, + {{pest_id|default:"null"}}, + {{crop_id|default:"null"}}, + {{crop_category_id|default:"null"}}, + function() {$(".chosen-select").chosen({allow_single_deselect: true});} // Must do this after select lists have been populated + ); </script> + {% endblock %} \ No newline at end of file diff --git a/observations/templates/observations/index_new.html b/observations/templates/observations/index_new.html deleted file mode 100644 index 0f713b6058e603f3215a9a7dc676a63d4db9ab46..0000000000000000000000000000000000000000 --- a/observations/templates/observations/index_new.html +++ /dev/null @@ -1,69 +0,0 @@ -{% extends "base.html" %} -{% load staticfiles %} -{% comment %} - -# -# Copyright (c) 2018 NIBIO <http://www.nibio.no/>. -# -# This file is part of VIPSWeb. -# VIPSWeb is free software: you can redistribute it and/or modify -# it under the terms of the NIBIO Open Source License as published by -# NIBIO, either version 1 of the License, or (at your option) any -# later version. -# -# VIPSWeb is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# NIBIO Open Source License for more details. -# -# You should have received a copy of the NIBIO Open Source License -# along with VIPSWeb. If not, see <http://www.nibio.no/licenses/>. -# - -{% endcomment %} -{% load i18n %} -{% block title%}{% trans "Observations" %}{%endblock%} -{% block content %} -<div id="observationMap" class="map" style="position:relative;"> - <div id="popover"></div> - <div class="form-group" id="progressBar"> - <label for="dayInPeriod">${i18nBundle.currentDate}: <span id="dayInPeriodDate">${to?date}</span></label><br/> - <button type="button" id="playButton" class="btn" onclick="togglePlay(this);"><i class="fa fa-play" aria-hidden="true"></i></button> - <input type="range" id="dayInPeriod" name="dayInPeriod" min="1" max="${periodDays}" step="1" value="${periodDays}" oninput="updateCurrentDate(this);" onchange="updateMap(this)"/> - - </div> - <div id="legend"></div> - </div> -{% endblock %} -{% block extendCSS %} -<link rel="stylesheet" href="{% static "css/3rdparty/ol.css" %}" type="text/css"> -<style type="text/css"> - #progressBar{ - position: absolute; - bottom: 5px; - left: 10px; - z-index: 1000; - background-color: white; - border-radius: 10px; - padding: 10px; - } - #legend{ - position: absolute; - bottom: 115px; - left: 10px; - z-index: 1000; - background-color: white; - border-radius: 10px; - padding: 10px; - } - #legend ul { - list-style: none; - padding:0; - margin:0; - } -</style> -{% endblock %} -{% block customJS %} -<script type="text/javascript" src="{% static "js/3rdparty/ol.js" %}"></script> -<script type="text/javascript" src="{% static "observations/js/observationMap.js" %}"></script> -{% endblock %} \ No newline at end of file diff --git a/observations/templates/observations/index_old.html b/observations/templates/observations/index_old.html new file mode 100755 index 0000000000000000000000000000000000000000..1ae0f0297654e13c936057e5374ac74a2c96c6ed --- /dev/null +++ b/observations/templates/observations/index_old.html @@ -0,0 +1,213 @@ +{% extends "base.html" %} +{% load staticfiles %} +{% comment %} + +# +# Copyright (c) 2016 NIBIO <http://www.nibio.no/>. +# +# This file is part of VIPSWeb. +# VIPSWeb is free software: you can redistribute it and/or modify +# it under the terms of the NIBIO Open Source License as published by +# NIBIO, either version 1 of the License, or (at your option) any +# later version. +# +# VIPSWeb is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# NIBIO Open Source License for more details. +# +# You should have received a copy of the NIBIO Open Source License +# along with VIPSWeb. If not, see <http://www.nibio.no/licenses/>. +# + +{% endcomment %} +{% load i18n %} +{% block title%}{% trans "Observations" %}{%endblock%} +{% block content %} + + <h1>{% trans "Observations" %}</h1> + <div class="row"> + <div class="col-xs-3"> + <div class="form-group"> + <select id="pestFilterList" class="form-control" onchange="filterObservations();"> + <option value="-1">{% trans "All pests" %}</option> + </select> + </div> + </div> + <div class="col-xs-3"> + <div class="form-group"> + <select name="cropCategoryFilterList" id="cropCategoryFilterList" class="form-control chosen-select" multiple="multiple" onchange="filterObservations();"> + {% for crop_category in crop_categories|dictsort:"name" %} + <option value="{{crop_category.crop_category_id}}">{{crop_category.name}}</option> + {% endfor %} + </select> + </div> + </div> + <div class="col-xs-6"> + <form class="form-inline" method="get" action="/observations"> + <div class="form-group"> + <input class="form-control" type="date" name="{{form.timeOfObservationFrom.html_name}}" value="{{form.timeOfObservationFrom.value | default_if_none:""}}" placeholder="{% trans "From" %}"/> + </div> + - + <div class="form-group"> + <input class="form-control" type="date" name="{{form.timeOfObservationTo.html_name}}" value="{{form.timeOfObservationTo.value | default_if_none:""}}" placeholder="{% trans "To" %}"/> + </div> + <button type="submit" class="btn btn-primary">{% trans "Date search" %}</button> + </form> + </div> + </div> + <table class="table"> + <thead> + <tr> + <th>{% trans "Time of observation" %}</th> + <th>{% trans "Organism" %}</th> + <th>{% trans "Crop" %}</th> + <th>{% trans "Heading" %}</th> + <th></th> + </tr> + </thead> + <tbody id="observationList"></tbody> + </table> + +{% endblock %} +{% block extendCSS %} +<link href="{% static "css/3rdparty/jquery-ui.min.css" %}" rel="stylesheet" media="screen" /> +<link href="{% static "css/3rdparty/chosen.min.css" %}" rel="stylesheet" media="screen" /> +<link href="{% static "css/3rdparty/chosen-bootstrap.css" %}" rel="stylesheet" media="screen" /> +{% endblock %} +{% block customJS %} +<script type="text/javascript" src="{% url "django.views.i18n.javascript_catalog" %}"></script> +<script type="text/javascript" src="{% static "js/3rdparty/modernizr_custom.js" %}"></script> +<script type="text/javascript" src="{% static "js/3rdparty/jquery-ui.min.js" %}"></script> +<script type="text/javascript" src="{% static "js/3rdparty/chosen.jquery.min.js" %}"></script> +<script type="text/javascript" src="{% static "js/3rdparty/moment.min.js" %}"></script> +<script type="text/javascript" src="{% url "views.settings_js" %}"></script> +<script type="text/javascript" src="{% static "js/util.js" %}"></script> +<script type="text/javascript"> + var allObservations = []; + var allPests = []; + var allPestIds = []; + var cropCategories = { + {% for crop_category in crop_categories %} + {{crop_category.crop_category_id}}:{{crop_category.crop_ids}}{% if not forloop.last %},{% endif %} + {% endfor %} + }; + $(document).ready(function() { + $(".chosen-select").chosen({placeholder_text_multiple:"{% trans "Crops" %}"}); + {% if form.timeOfObservationFrom.value or form.timeOfObservationTo.value %} + $.getJSON("/vipslogicproxy/rest/observation/broadcast/list/" + settings.vipsOrganizationId+ "?timeOfObservationFrom={{form.timeOfObservationFrom.value | default_if_none:""}}&timeOfObservationTo={{form.timeOfObservationTo.value | default_if_none:""}}", function( json ) { + {% else %} + $.getJSON("/vipslogicproxy/rest/observation/broadcast/list/" + settings.vipsOrganizationId+ "?season=" + getCurrentYear(), function( json ) { + {% endif %} + allObservations = json; + allObservations.sort(sortObservationMessages).reverse(); + for(var i in allObservations) + { + var currentOrganism = allObservations[i].organism; + if(allPestIds.indexOf(currentOrganism.organismId) < 0) + { + allPests.push(currentOrganism); + allPestIds.push(currentOrganism.organismId); + } + } + renderPestFilterList(); + renderObservations(allObservations); + }); + + // Make sure that there is a date picker present for HTML5 + // date input fields + if (!Modernizr.inputtypes.date) { + $('input[type=date]').datepicker(settings.datePickerConfig); + } + }); + + + function renderObservations(observations) + { + var tableHTML = ""; + for(var i in observations){ + var observation = observations[i]; + tableHTML += [ + "<tr>", + "<td>",getSimpleFormattedTimestamp(observation.timeOfObservation),"</td>", + "<td>", getLocalizedOrganismName(observation.organism)," <i>(", observation.organism.latinName, ")</i></td>", + "<td>", getLocalizedOrganismName(observation.cropOrganism) ," <i>(", observation.cropOrganism.latinName, ")</i></td>", + "<td>", observation.observationHeading, "</td>", + "<td><a href='/observations/", observation.observationId, "'>{% trans "View details" %}</a></td>", + "</tr>" + ].join(""); + } + document.getElementById("observationList").innerHTML = tableHTML; + } + + function renderPestFilterList() + { + var pestFilterList = document.getElementById("pestFilterList"); + pestFilterList.options.length = 1; + allPests.sort(sortOrganisms); + for(var i in allPests) + { + var pestOpt = new Option(getLocalizedOrganismName(allPests[i]), allPests[i].organismId); + pestFilterList.options[pestFilterList.options.length] = pestOpt; + } + + } + + function filterOnCropGroup(cropGroupFilterList) + { + var selectedCropGroupId = parseInt(cropGroupFilterList.options[cropGroupFilterList.options.selectedIndex].value); + } + + function filterObservations() + { + var pestFilterList = document.getElementById("pestFilterList"); + var cropCategoryFilterList = document.getElementById("cropCategoryFilterList"); + + var selectedPestOrganismId = parseInt(pestFilterList.options[pestFilterList.options.selectedIndex].value); + + var selectedCropCategoryIds = getMultipleSelectedValues(cropCategoryFilterList); + //console.info(selectedCropCategoryIds); + + var filteredObservations = []; + + for(var i=0;i<allObservations.length;i++) + { + var obs = allObservations[i]; + + var validCropCategory = false; + if(selectedCropCategoryIds.length > 0) + { + for(var j=0;j<selectedCropCategoryIds.length;j++) + { + if(cropCategories[selectedCropCategoryIds[j]].indexOf(obs.cropOrganism.organismId) >= 0) + { + validCropCategory = true; + } + } + } + else + { + validCropCategory = true; + } + + var validPest = false; + if(selectedPestOrganismId == -1) + { + validPest = true; + } + else if(obs.organism.organismId == selectedPestOrganismId) + { + validPest = true; + } + + if(validCropCategory && validPest) + { + filteredObservations.push(obs); + } + } + + renderObservations(filteredObservations); + } + +</script> +{% endblock %} \ No newline at end of file diff --git a/observations/urls.py b/observations/urls.py index a5ed6980567fc6162522fb888ca3f803a4bff6b2..3293b90066219351ab1d582580b9fad222f02df5 100755 --- a/observations/urls.py +++ b/observations/urls.py @@ -23,7 +23,7 @@ from observations import views urlpatterns = patterns('observations.views', # ex: /forecasts/ url(r'^$', views.index, name='index'), - #url(r'new', views.index_new, name='index_new'), + url(r'old', views.index_old, name='index_old'), # ex: /observations/5/ url(r'^(?P<observation_id>\d+)/$', (views.detail), name='detail'), diff --git a/observations/views.py b/observations/views.py index d82f3a7ca4f21e17f2d0479d38b575c3772bf809..3b5e60e615f90aa511b491a10aed7056af5946f2 100755 --- a/observations/views.py +++ b/observations/views.py @@ -23,8 +23,10 @@ from django.utils import translation from django.conf import settings from observations.forms import ObservationTimeFilterForm from organisms.models import CropCategory +from datetime import datetime +from dateutil.relativedelta import relativedelta -def index(request): +def index_old(request): form = ObservationTimeFilterForm(request.GET) if form == None: form = ObservationTimeFilterForm() @@ -32,28 +34,37 @@ def index(request): "crop_categories": CropCategory.get_crop_categories(translation.get_language()), "form" : form } - return render(request, 'observations/index.html', context) + return render(request, 'observations/index_old.html', context) -""" -Bør slettes hvis ikke man skal ha et offentlig, spesialisert observasjonskart tilgjengelig -def index_new(request): +def index(request): # organizationId (internally - either from logged in user or from web page if request.session.get("vips_logic_user", None) != None: - print request.session["vips_logic_user"] organization_id = request.session["vips_logic_user"]["organization_id"] else: organization_id = settings.VIPS_ORGANIZATION_ID - # pestId - # cropId + + date_from = datetime.strptime(request.GET.get("from", "%s-01-01" % (datetime.now() + relativedelta(months = settings.SYSTEM_TIME_OFFSET_MONTHS)).year),"%Y-%m-%d") + if request.GET.get("to", None) is not None: + date_to = datetime.strptime(request.GET["to"], "%Y-%m-%d") + else: + date_to = datetime.now() + relativedelta(months = settings.SYSTEM_TIME_OFFSET_MONTHS) + #date_to = datetime.strptime(request.GET.get("to", "%s-12-31" % (datetime.now() + relativedelta(months = settings.SYSTEM_TIME_OFFSET_MONTHS)).year),"%Y-%m-%d") + period_days = date_to - date_from + current_day_in_period = (datetime.now() + relativedelta(months = settings.SYSTEM_TIME_OFFSET_MONTHS)) - date_from # cropCategoryId - # from (YYYY-mm-DD) - # to (YYYY-mm-DD) context = { - "crop_categories": CropCategory.get_crop_categories(translation.get_language()), - "organization_id": organization_id - } - return render(request, 'observations/index_new.html', context) -""" + "from" : datetime.strftime(date_from,"%Y-%m-%d"), + "to" : datetime.strftime(date_to,"%Y-%m-%d"), + "period_days" : period_days.days + 1, + "current_day_in_period" : current_day_in_period, + "pest_id" : request.GET.get("pestId", None), + "crop_id" : request.GET.get("cropId", None), + "crop_category_id": request.GET.get("cropCategoryId", None), + "crop_categories": CropCategory.get_crop_categories(translation.get_language()), + "organization_id": organization_id + } + return render(request, 'observations/index.html', context) + def detail(request, observation_id): context = { "observation_id" : observation_id,