diff --git a/config.example.edn b/config.example.edn
index b09ea8329..d69737579 100644
--- a/config.example.edn
+++ b/config.example.edn
@@ -7,12 +7,16 @@
:triangulum.server/mode "dev"
:triangulum.server/log-dir "logs"
- :triangulum.server/handler collect-earth-online.routing/authenticated-routing-handler
+ :triangulum.server/handler triangulum.handler/authenticated-routing-handler
:triangulum.server/keystore-file "keystore.pkcs12"
:triangulum.server/keystore-type "pkcs12"
:triangulum.server/keystore-password "foobar"
;; handler (server)
+ :triangulum.handler/not-found-handler triangulum.views/not-found-page
+ :triangulum.handler/redirect-handler collect-earth-online.handlers/redirect-handler
+ :triangulum.handler/route-authenticator collect-earth-online.handlers/route-authenticator
+ :triangulum.handler/routing-tables [collect-earth-online.routing/routes]
:triangulum.handler/session-key "changeme12345678" ; must be 16 characters
:triangulum.handler/bad-tokens #{".php"}
:triangulum.handler/private-request-keys #{:base64Image :plotFileBase64 :sampleFileBase64}
diff --git a/deps.edn b/deps.edn
index f31083d69..3d5650efc 100644
--- a/deps.edn
+++ b/deps.edn
@@ -7,7 +7,7 @@
org.clojure/data.json {:mvn/version "1.0.0"}
ring/ring {:mvn/version "1.8.2"}
sig-gis/triangulum {:git/url "https://github.com/sig-gis/triangulum"
- :git/sha "5cffefc2e8a8027a178d7ff1103cb2e38e174bba"}}
+ :git/sha "3d41dab63e1bc8ebe046f64db44ae3df986f5bdf"}}
:aliases {:build-db {:main-opts ["-m" "triangulum.build-db"]}
:config {:main-opts ["-m" "triangulum.config"]}
diff --git a/src/clj/collect_earth_online/db/doi.clj b/src/clj/collect_earth_online/db/doi.clj
index bc5a38dfc..7fdf05b3c 100644
--- a/src/clj/collect_earth_online/db/doi.clj
+++ b/src/clj/collect_earth_online/db/doi.clj
@@ -25,7 +25,7 @@
(defn get-doi-reference
[{:keys [params]}]
(let [project-id (tc/val->int (:projectId params))
- doi-path (:doi_path (first (call-sql "select_doi_by_project" project-id)))]
+ doi-path (:doi_path (last (call-sql "select_doi_by_project" project-id)))]
(data-response {:doiPath doi-path})))
(defn create-contributors-list
@@ -110,59 +110,68 @@
(update :survey_questions #(tc/jsonb->clj %))
(update :aoi_features #(tc/jsonb->clj %))
(update :created_date #(str %))
+ (update :closed_date (fn [x] (when x (str x))))
(update :published_date #(str %)))))
(defn upload-deposition-files!
- [bucket-url project-id zip-file]
+ [doi-id zip-file]
(let [headers (req-headers)]
- (http/put (str bucket-url "/" project-id)
- {:content-type :multipart/form-data
- :headers headers
- :as :json
- :multipart [{:name "Content/type" :content "application/octet-stream"}
- {:name "file" :content (io/file zip-file)}]})))
+ (http/post (str base-url "/deposit/depositions/" doi-id "/files")
+ {:headers headers
+ :multipart [{:name "Content/type" :content "application/octet-stream"}
+ {:name "file" :content (io/file zip-file)}]})))
(defn upload-doi-files!
[doi-id project-id]
(let [project-id project-id
doi (first (call-sql "select_doi_by_id" doi-id))
- bucket (-> doi :full_data tc/jsonb->clj :links :bucket)
project-data (json/write-str (get-project-data project-id))
zip-file (create-and-zip-files-for-doi project-id project-data)]
(try
- (:body (upload-deposition-files! bucket project-id zip-file))
- (catch Exception _
+ (:body (upload-deposition-files! (:doi_uid doi) zip-file))
+ (catch Exception e
(throw (ex-info "Failed to upload files."
{:details "Error in file upload to zenodo"}))))))
(defn create-doi!
[{:keys [params session]}]
- (let [user-id (:userId session -1)
- project-id (:projectId params)
- project-name (:projectName params)
- institution-name (:name (first (call-sql "select_institution_by_id" (-> params :institution) user-id)))
- description (:description params)
- creator (first (call-sql "get_user_by_id" user-id))
- contributors (call-sql "select_assigned_users_by_project" project-id)]
- (try
- (->
- (create-zenodo-deposition! institution-name project-name creator contributors description)
- :body
- (insert-doi! project-id user-id)
- (upload-doi-files! project-id))
- (data-response {:message "DOI created successfully"})
- (catch Exception _
- (data-response {:message "Failed to create DOI."}
- {:status 500})))))
+ (let [user-id (:userId session -1)
+ project-id (:projectId params)
+ project-name (:projectName params)
+ institution-name (:name (first (call-sql "select_institution_by_id" (-> params :institution) user-id)))
+ description (:description params)
+ creator (first (call-sql "get_user_by_id" user-id))
+ contributors (call-sql "select_assigned_users_by_project" project-id)
+ project-published? (:availability (first (call-sql "select_project_by_id" project-id)))
+ doi-published? (:submitted (last (call-sql "select_doi_by_project" project-id)))]
+ (cond
+ (and (not= "published" project-published?)
+ (not= "closed" project-published?)) (data-response {:message "In order to create a DOI, the project must be published"}
+ {:status 500})
+ doi-published? (data-response {:message "A DOI for this project has already been published."}
+ {:status 500})
+ :else
+ (try
+ (->
+ (create-zenodo-deposition! institution-name project-name creator contributors description)
+ :body
+ (insert-doi! project-id user-id)
+ (upload-doi-files! project-id))
+ (data-response {:message "DOI created successfully"})
+ (catch Exception _
+ (data-response {:message "Failed to create DOI."}
+ {:status 500}))))))
(defn publish-doi!
"request zenodo to publish the DOI on DataCite"
[{:keys [params]}]
(let [project-id (:projectId params)
- doi-id (:doi_uid (first (call-sql "select_doi_by_project" project-id)))]
+ doi-id (:doi_uid (last (call-sql "select_doi_by_project" project-id)))
+ req (http/post (str base-url "/deposit/depositions/" doi-id "/actions/publish")
+ {:as :json
+ :headers (req-headers)})]
(try
- (http/post (str base-url "/deposition/depositions/" doi-id "/actions/publish")
- {:headers (req-headers)})
+ (call-sql "update_doi" doi-id (tc/clj->jsonb (:body req)))
(data-response {})
(catch Exception _
(data-response {:message "Failed to publish DOI"}
diff --git a/src/clj/collect_earth_online/db/plots.clj b/src/clj/collect_earth_online/db/plots.clj
index cb5eec7ed..c0e63bc88 100644
--- a/src/clj/collect_earth_online/db/plots.clj
+++ b/src/clj/collect_earth_online/db/plots.clj
@@ -256,22 +256,23 @@
;;;
(defn add-user-samples [{:keys [params session]}]
- (let [project-id (tc/val->int (:projectId params))
- plot-id (tc/val->int (:plotId params))
- session-user-id (:userId session -1)
- current-user-id (tc/val->int (:currentUserId params -1))
- review-mode? (and (tc/val->bool (:inReviewMode params))
+ (let [project-id (tc/val->int (:projectId params))
+ plot-id (tc/val->int (:plotId params))
+ session-user-id (:userId session -1)
+ current-user-id (tc/val->int (:currentUserId params -1))
+ review-mode? (and (tc/val->bool (:inReviewMode params))
(pos? current-user-id)
(is-proj-admin? session-user-id project-id nil))
- confidence (tc/val->int (:confidence params))
- collection-start (tc/val->long (:collectionStart params))
- user-samples (:userSamples params)
- user-images (:userImages params)
- new-plot-samples (:newPlotSamples params)
- user-id (if review-mode? current-user-id session-user-id)
+ confidence (tc/val->int (:confidence params))
+ confidence-comment (:confidenceComment params)
+ collection-start (tc/val->long (:collectionStart params))
+ user-samples (:userSamples params)
+ user-images (:userImages params)
+ new-plot-samples (:newPlotSamples params)
+ user-id (if review-mode? current-user-id session-user-id)
;; Samples created in the UI have IDs starting with 1. When the new sample is created
;; in Postgres, it gets different ID. The user sample ID needs to be updated to match.
- id-translation (when new-plot-samples
+ id-translation (when new-plot-samples
(call-sql "delete_user_plot_by_plot" plot-id user-id)
(call-sql "delete_samples_by_plot" plot-id)
(reduce (fn [acc {:keys [id visibleId sampleGeom]}]
@@ -288,6 +289,7 @@
plot-id
user-id
(when (pos? confidence) confidence)
+ (when confidence-comment confidence-comment)
(when-not review-mode? (Timestamp. collection-start))
(tc/clj->jsonb (set/rename-keys user-samples id-translation))
(tc/clj->jsonb (set/rename-keys user-images id-translation)))
diff --git a/src/clj/collect_earth_online/generators/external_file.clj b/src/clj/collect_earth_online/generators/external_file.clj
index a64bf6843..c223e04eb 100644
--- a/src/clj/collect_earth_online/generators/external_file.clj
+++ b/src/clj/collect_earth_online/generators/external_file.clj
@@ -277,7 +277,7 @@
(create-shape-files folder-name "sample" project-id)
(create-data-file folder-name project-data)
(sh-wrapper tmp-dir {}
- (str "7z a " folder-name "/files" ".zip " folder-name "/*"))
+ (str "7z a " folder-name "files" ".zip " folder-name "*"))
(str folder-name "files.zip")))
(defn zip-shape-files
diff --git a/src/clj/collect_earth_online/handlers.clj b/src/clj/collect_earth_online/handlers.clj
new file mode 100644
index 000000000..33c7f4005
--- /dev/null
+++ b/src/clj/collect_earth_online/handlers.clj
@@ -0,0 +1,33 @@
+(ns collect-earth-online.handlers
+ (:require [collect-earth-online.db.institutions :refer [is-inst-admin?]]
+ [collect-earth-online.db.projects :refer [can-collect? is-proj-admin?]]
+ [ring.util.codec :refer [url-encode]]
+ [ring.util.response :refer [redirect]]
+ [triangulum.response :refer [no-cross-traffic?]]
+ [triangulum.type-conversion :refer [val->int]]))
+
+(defn route-authenticator [{:keys [session params headers] :as _request} auth-type]
+ (let [user-id (:userId session -1)
+ institution-id (val->int (:institutionId params))
+ project-id (val->int (:projectId params))
+ token-key (:tokenKey params)]
+ (condp = auth-type
+ :user (pos? user-id)
+ :super (= 1 user-id)
+ :collect (can-collect? user-id project-id token-key)
+ :token (can-collect? -99 project-id token-key)
+ :admin (cond
+ (pos? project-id) (is-proj-admin? user-id project-id token-key)
+ (pos? institution-id) (is-inst-admin? user-id institution-id))
+ :no-cross (no-cross-traffic? headers)
+ true)))
+
+(defn redirect-handler [{:keys [session query-string uri] :as _request}]
+ (let [full-url (url-encode (str uri (when query-string (str "?" query-string))))]
+ (if (:userId session)
+ (redirect (str "/home?flash_message=You do not have permission to access "
+ full-url))
+ (redirect (str "/login?returnurl="
+ full-url
+ "&flash_message=You must login to see "
+ full-url)))))
diff --git a/src/clj/collect_earth_online/routing.clj b/src/clj/collect_earth_online/routing.clj
index d9b09147a..9128517e5 100644
--- a/src/clj/collect_earth_online/routing.clj
+++ b/src/clj/collect_earth_online/routing.clj
@@ -1,17 +1,13 @@
(ns collect-earth-online.routing
- (:require [triangulum.views :refer [render-page not-found-page]]
- [ring.util.response :refer [redirect]]
- [ring.util.codec :refer [url-encode]]
- [triangulum.type-conversion :as tc]
- [triangulum.response :refer [forbidden-response no-cross-traffic?]]
- [collect-earth-online.db.doi :as doi]
+ (:require [collect-earth-online.db.doi :as doi]
[collect-earth-online.db.geodash :as geodash]
[collect-earth-online.db.imagery :as imagery]
[collect-earth-online.db.institutions :as institutions]
[collect-earth-online.db.plots :as plots]
[collect-earth-online.db.projects :as projects]
[collect-earth-online.db.users :as users]
- [collect-earth-online.proxy :as proxy]))
+ [collect-earth-online.proxy :as proxy]
+ [triangulum.views :refer [render-page]]))
(def routes
{;; Page Routes
@@ -194,41 +190,3 @@
[:get "/get-nicfi-tiles"] {:handler proxy/get-nicfi-tiles
:auth-type :no-cross
:auth-action :block}})
-
-(defn- redirect-auth [user-id]
- (fn [request]
- (let [{:keys [query-string uri]} request
- full-url (url-encode (str uri (when query-string (str "?" query-string))))]
- (if (pos? user-id)
- (redirect (str "/home?flash_message=You do not have permission to access "
- full-url))
- (redirect (str "/login?returnurl="
- full-url
- "&flash_message=You must login to see "
- full-url))))))
-
-(defn authenticated-routing-handler [{:keys [uri request-method params headers session] :as request}]
- (let [{:keys [auth-type auth-action handler] :as route} (get routes [request-method uri])
- user-id (:userId session -1)
- institution-id (tc/val->int (:institutionId params))
- project-id (tc/val->int (:projectId params))
- next-handler (if route
- (if (condp = auth-type
- :user (pos? user-id)
- :super (= 1 user-id)
- :collect (projects/can-collect? user-id project-id (:tokenKey params))
- :token (projects/can-collect? -99 project-id (:tokenKey params))
- :admin (cond
- (pos? project-id)
- (projects/is-proj-admin? user-id project-id (:tokenKey params))
-
- (pos? institution-id)
- (institutions/is-inst-admin? user-id institution-id))
- :no-cross (no-cross-traffic? headers)
- true)
- handler
- (if (= :redirect auth-action)
- (redirect-auth user-id)
- forbidden-response))
- not-found-page)]
- (next-handler request)))
diff --git a/src/clj/collect_earth_online/workers.clj b/src/clj/collect_earth_online/workers.clj
index aa6a8e601..ba2143704 100644
--- a/src/clj/collect_earth_online/workers.clj
+++ b/src/clj/collect_earth_online/workers.clj
@@ -19,7 +19,7 @@
file (reverse (file-seq d))]
(io/delete-file file))))
-(defn- start-clean-up-service! []
+(defn start-clean-up-service! []
(log-str "Starting temp file removal service.")
(future
(while true
diff --git a/src/css/custom.css b/src/css/custom.css
index 14414bc46..8365183d3 100644
--- a/src/css/custom.css
+++ b/src/css/custom.css
@@ -541,6 +541,7 @@ a:hover {
}
#lPanel ul {
padding-left: 0;
+ margin-left: 5px;
list-style: none;
margin-bottom: 0;
}
@@ -859,5 +860,7 @@ input:checked + .switch-slider:before {
}
body {
- padding-top:60px;
+ margin-left: 10px;
+ margin-top: 60px;
+ overflow-x: hidden;
}
diff --git a/src/js/collection.jsx b/src/js/collection.jsx
index b01d29985..a7f6e8923 100644
--- a/src/js/collection.jsx
+++ b/src/js/collection.jsx
@@ -734,6 +734,9 @@ class Collection extends React.Component {
confidence: this.state.currentProject.projectOptions.collectConfidence
? this.state.currentPlot.confidence
: -1,
+ confidenceComment: this.state.currentProject.projectOptions.collectConfidence
+ ? this.state.currentPlot.confidenceComment
+ : null,
collectionStart: this.state.collectionStart,
userSamples: this.state.userSamples,
userImages: this.state.userImages,
@@ -970,6 +973,9 @@ class Collection extends React.Component {
setFlaggedReason = (flaggedReason) =>
this.setState({ currentPlot: { ...this.state.currentPlot, flaggedReason } });
+ setConfidenceComment = (confidenceComment) =>
+ this.setState({ currentPlot: { ...this.state.currentPlot, confidenceComment } });
+
render() {
return (
@@ -1014,6 +1020,7 @@ class Collection extends React.Component {
surveyQuestions={this.state.currentProject.surveyQuestions}
toggleQuitModal={this.toggleQuitModal}
userName={this.props.userName}
+ collectConfidence={this.state.currentProject.projectOptions?.collectConfidence}
>
{
- const { answerMode, currentPlot, inReviewMode, surveyQuestions } = this.props;
+ const { answerMode, currentPlot, inReviewMode, surveyQuestions, collectConfidence } = this.props;
+ const { confidence, confidenceComment } = currentPlot;
const visibleSurveyQuestions = filterObject(surveyQuestions, ([_id, val]) => val.hideQuestion != true);
const noneAnswered = everyObject(visibleSurveyQuestions, ([_id, sq]) => safeLength(sq.answered) === 0);
const hasSamples = safeLength(currentPlot.samples) > 0;
@@ -1154,6 +1164,9 @@ class SideBar extends React.Component {
} else if (!allAnswered) {
alert("All questions must be answered to save the collection.");
return false;
+ } else if (!(collectConfidence && (confidence && confidenceComment))) {
+ alert("You must input a confidence and write a comment about it before saving the interpretation.");
+ return false;
} else {
return true;
}
@@ -1585,9 +1598,15 @@ class ImageryOptions extends React.Component {
super(props);
this.state = {
showImageryOptions: true,
+ enableGrid: false,
};
}
+ enableGrid() {
+ this.setState({ enableGrid: !this.state.enableGrid });
+ return mercator.addGridLayer(this.props.mapConfig, !this.state.enableGrid);
+ }
+
render() {
const { props } = this;
const commonProps = {
@@ -1658,6 +1677,16 @@ class ImageryOptions extends React.Component {
}[imagery.sourceConfig.type]
);
})}
+ this.enableGrid()}
+ type="checkbox"
+ />
+
);
diff --git a/src/js/geodash/MapWidget.jsx b/src/js/geodash/MapWidget.jsx
index 4eb501dce..0697c74c5 100644
--- a/src/js/geodash/MapWidget.jsx
+++ b/src/js/geodash/MapWidget.jsx
@@ -160,7 +160,9 @@ export default class MapWidget extends React.Component {
this.props.imageryList[0];
const basemapLayer = new TileLayer({
source: mercator.createSource(
- sourceConfig,
+ (widget.basemapType === "PlanetNICFI") ?
+ {... sourceConfig, time: widget.basemapNICFIDate}
+ : sourceConfig,
id,
attribution,
isProxied,
diff --git a/src/js/geodash/TimeSeriesDesigner.jsx b/src/js/geodash/TimeSeriesDesigner.jsx
index c26e22537..d50b0a187 100644
--- a/src/js/geodash/TimeSeriesDesigner.jsx
+++ b/src/js/geodash/TimeSeriesDesigner.jsx
@@ -24,7 +24,6 @@ export async function getBandsFromGateway(setBands, assetId, assetType) {
}
);
const data = await res.json();
- console.log("data is: ", data);
if (data.hasOwnProperty("bands")) {
setBands(data.bands);
} else if (data.hasOwnProperty("errMsg")) {
diff --git a/src/js/geodash/form/BasemapSelector.jsx b/src/js/geodash/form/BasemapSelector.jsx
index 74a2accc2..bae824c50 100644
--- a/src/js/geodash/form/BasemapSelector.jsx
+++ b/src/js/geodash/form/BasemapSelector.jsx
@@ -1,11 +1,68 @@
-import React, { useContext } from "react";
+import React, { useContext, useState, useEffect } from "react";
import { EditorContext } from "../constants";
import SvgIcon from "../../components/svg/SvgIcon";
+
export default function BasemapSelector() {
- const { setWidgetDesign, getWidgetDesign, imagery, getInstitutionImagery, institutionId } =
- useContext(EditorContext);
+ const { widget, widgetDesign, setWidgetDesign, getWidgetDesign, imagery, getInstitutionImagery, institutionId } =
+ useContext(EditorContext);
+ const [nicfiLayers, setNICFILayers] = useState([]);
+ const [imageryType, setImageryType] = useState("");
+ const [basemap, setBasemap] = useState(imagery.filter((i)=>i.id === getWidgetDesign("basemapId")));
+
+ function nicfiDateSelector(){
+ const options = (
+ nicfiLayers || []).map(
+ (l)=>{
+ const name = l.slice(34, l.length - 7);
+ return (
+ );}
+ );
+ return (
+
+
+
+
);
+ }
+
+ const getNICFILayers = () => {
+ fetch("/get-nicfi-dates")
+ .then((response) => (response.ok ? response.json() : Promise.reject(response)))
+ .then((layers) => {
+ setNICFILayers(layers);
+ setWidgetDesign("basemapNICFIDate", widget[0].basemapNICFIDate);
+ })
+ .catch((error) => console.error(error));
+ };
+
+ useEffect(()=> {
+ setWidgetDesign("basemapType", imageryType);
+ (imageryType === "PlanetNICFI") ?
+ getNICFILayers()
+ : setWidgetDesign("basemapNICFIDate", null);
+ }, [imageryType]);
+
+ useEffect(()=>{
+ setImageryType((imagery || []).filter((i)=> i.id === getWidgetDesign("basemapId"))[0].sourceConfig.type);
+ setBasemap((imagery || []).filter((i)=> i.id === getWidgetDesign("basemapId"))[0]);
+ }, [imagery]);
+
return (
@@ -22,16 +79,20 @@ export default function BasemapSelector() {
+ {(imageryType === "PlanetNICFI") && nicfiDateSelector()}
Adding imagery to basemaps is available on the
{
+ const { nicfiLayers, selectedTime } = state;
+ const index = nicfiLayers.indexOf(selectedTime);
+
+ return {
+ isDisabledLeft: index >= nicfiLayers.length - 1,
+ isDisabledRight: index <= 0
+ }
+ })
+ }
}
getNICFILayers = () => {
@@ -231,6 +246,21 @@ export class PlanetNICFIMenu extends React.Component {
}
};
+ switchImagery = (direction) => {
+ this.setState((prevState) => {
+ const { nicfiLayers, selectedTime } = prevState;
+ const currentIndex = nicfiLayers.indexOf(selectedTime);
+
+ const move = direction === "forward" ? -1 : 1;
+ const newIndex = currentIndex + move;
+
+ // Check boundary conditions
+ if (newIndex < 0 || newIndex >= nicfiLayers.length) return {};
+
+ return { selectedTime: nicfiLayers[newIndex] };
+ });
+ }
+
updatePlanetLayer = () => {
this.updateImageryInformation();
mercator.updateLayerSource(
@@ -250,14 +280,17 @@ export class PlanetNICFIMenu extends React.Component {
return (
nicfiLayers.length > 0 && (
-
-
-
-
-
)
);
diff --git a/src/js/imagery/imageryOptions.js b/src/js/imagery/imageryOptions.js
index 1d1a45017..c55f12862 100644
--- a/src/js/imagery/imageryOptions.js
+++ b/src/js/imagery/imageryOptions.js
@@ -253,18 +253,18 @@ export const imageryOptions = [
{
key: "min",
display: "Min",
- type: "number",
+ type: "text",
options: {
- placeholder: "1-100",
+ placeholder: "[1-100] | [0.1,0.2,0.1]",
step: "0.01",
},
},
{
key: "max",
display: "Max",
- type: "number",
+ type: "text",
options: {
- placeholder: "2800-3200",
+ placeholder: "[2800-3200] | [0.3,0.5,0.3]",
step: "0.01",
},
},
@@ -309,18 +309,18 @@ export const imageryOptions = [
{
key: "min",
display: "Min",
- type: "number",
+ type: "text",
options: {
- placeholder: "1-100",
+ placeholder: "[1-100] | [0.1,0.2,0.1]",
step: "0.01",
},
},
{
key: "max",
display: "Max",
- type: "number",
+ type: "text",
options: {
- placeholder: "2800-3200",
+ placeholder: "[2800-3200] | [0.3,0.5,0.3]",
step: "0.01",
},
},
diff --git a/src/js/project/PlotDesign.jsx b/src/js/project/PlotDesign.jsx
index ade920e2c..bcbc1aacb 100644
--- a/src/js/project/PlotDesign.jsx
+++ b/src/js/project/PlotDesign.jsx
@@ -430,6 +430,7 @@ export class PlotDesign extends React.Component {
},
shp: {
display: "SHP File",
+ alert: "CEO may overestimate the number of project plots when using a ShapeFile.",
description:
"Specify your own plot boundaries by uploading a zipped Shapefile (containing SHP, SHX, DBF, and PRJ files) of polygon features. Each feature must have a unique PLOTID value.",
layout: this.renderFileInput("shp"),
@@ -456,6 +457,8 @@ export class PlotDesign extends React.Component {
{`- ${plotOptions[plotDistribution].description}`}
+ {plotOptions[plotDistribution].alert &&
+
- {plotOptions[plotDistribution].alert}
}
{plotOptions[plotDistribution].layout}
Plot Confidence: {this.props.confidence}
+
+ Comment on the confidence:
+
+ >
)}
{this.renderFlagClearButtons()}
>
diff --git a/src/js/utils/mercator.js b/src/js/utils/mercator.js
index b1d3cb064..9004a7561 100644
--- a/src/js/utils/mercator.js
+++ b/src/js/utils/mercator.js
@@ -23,7 +23,7 @@ import { platformModifierKeyOnly } from "ol/events/condition";
import { Circle, LineString, Point, Polygon } from "ol/geom";
import { DragBox, Select, Draw, Modify, Snap } from "ol/interaction";
import { GeoJSON, KML } from "ol/format";
-import { Tile as TileLayer, Vector as VectorLayer, Group as LayerGroup } from "ol/layer";
+import { Tile as TileLayer, Vector as VectorLayer, Group as LayerGroup, Graticule } from "ol/layer";
import { BingMaps, Cluster, OSM, TileWMS, Vector as VectorSource, XYZ } from "ol/source";
import { Circle as CircleStyle, Fill, Stroke, Style, Text as StyleText } from "ol/style";
import { fromLonLat, transform, transformExtent, getPointResolution } from "ol/proj";
@@ -475,6 +475,34 @@ mercator.addVectorLayer = (mapConfig, layerId, vectorSource, style) => {
return mapConfig;
};
+mercator.addGridLayer = (mapConfig, showLayer) => {
+ const gridIntervals = [90, 45, 30, 20, 10, 5, 2, 1, 30/60, 20/60,
+ 10/60, 5/60, 2/60, 1/60, 30/3600, 20/3600,
+ 10/3600, 5/3600, 2/3600, 1/3600, 0.8/3600,
+ 0.5/3600, 0.3/3600, 0.2/3600, 0.1/3600];
+ const grid = new Graticule({
+ // the style to use for the lines, optional.
+ strokeStyle: new Stroke({
+ color: 'rgba(255,120,0,0.9)',
+ width: 2,
+ lineDash: [0.5, 4],
+ }),
+ id: 9999,
+ showLabels: false,
+ minResolution: 0.0001,
+ intervals: gridIntervals,
+ wrapX: false,
+ });
+ if(showLayer) {
+ mapConfig.map.addLayer(grid);
+ } else {
+ const layerId = 9999;
+ const layer = mercator.getLayerById(mapConfig, layerId);
+ mapConfig.map.removeLayer(layer);
+ }
+ return mapConfig;
+};
+
/*****************************************************************************
***
*** Functions to verify map input arguments
diff --git a/src/js/utils/validation.js b/src/js/utils/validation.js
index 13b66314b..ddf70a3c7 100644
--- a/src/js/utils/validation.js
+++ b/src/js/utils/validation.js
@@ -111,11 +111,9 @@ export default function getErrors (form) {
!sampleResolution &&
"A sample spacing is required for gridded sample distribution.",
sampleDistribution === "csv" &&
- sampleFileNeeded &&
!(sampleFileName || "").includes(".csv") &&
"A sample CSV (.csv) file is required.",
sampleDistribution === "shp" &&
- sampleFileNeeded &&
!(sampleFileName || "").includes(".zip") &&
"A sample SHP (.zip) file is required.",
sampleDistribution === "gridded" &&
diff --git a/src/js/widgetLayoutEditor.jsx b/src/js/widgetLayoutEditor.jsx
index 1dadd588d..547eae7de 100644
--- a/src/js/widgetLayoutEditor.jsx
+++ b/src/js/widgetLayoutEditor.jsx
@@ -312,9 +312,10 @@ class WidgetLayoutEditor extends React.PureComponent {
};
buildNewWidget = () => {
- const { title, type, widgetDesign } = this.state;
+ const { title, type, widgetDesign, basemapNICFIDate } = this.state;
return {
name: title,
+ basemapNICFIDate,
type,
...widgetDesign,
};
@@ -461,7 +462,12 @@ class WidgetLayoutEditor extends React.PureComponent {
className="form-control"
id="widgetTitle"
onChange={(e) => this.updateTitle(e.target.value)}
- placeholder="Enter title"
+ placeholder={
+ this.getWidgetDesign("basemapNICFIDate") ?
+ "Planet NICFI " +
+ this.getWidgetDesign("basemapNICFIDate").slice(
+ 34, this.getWidgetDesign("basemapNICFIDate").length - 7)
+ : "Enter Title"}
type="text"
value={this.state.title}
/>
@@ -497,6 +503,7 @@ class WidgetLayoutEditor extends React.PureComponent {
setWidgetDesign: this.setWidgetDesign,
widgetDesign: this.state.widgetDesign,
imagery: this.state.imagery,
+ widget: this.state.widgets.filter((w)=>w.basemapId === this.state.widgetDesign.basemapId)[0],
getInstitutionImagery: this.getInstitutionImagery,
}}
>
diff --git a/src/sql/changes/2023-04-17-lowercase-user-emails.sql b/src/sql/changes/2023-04-17-lowercase-user-emails.sql
deleted file mode 100644
index dc7f5bb98..000000000
--- a/src/sql/changes/2023-04-17-lowercase-user-emails.sql
+++ /dev/null
@@ -1 +0,0 @@
-UPDATE users SET email = LOWER(email);
diff --git a/src/sql/changes/2023-04-29-create-doi-table.sql b/src/sql/changes/2023-04-29-create-doi-table.sql
deleted file mode 100644
index 09bef0429..000000000
--- a/src/sql/changes/2023-04-29-create-doi-table.sql
+++ /dev/null
@@ -1,8 +0,0 @@
-CREATE TABLE doi(
- doi_uid INTEGER NOT NULL PRIMARY KEY,
- project_rid INTEGER NOT NULL REFERENCES projects (project_uid) ON DELETE CASCADE ON UPDATE CASCADE,
- user_id INTEGER NOT NULL REFERENCES users (user_uid) ON DELETE CASCADE ON UPDATE CASCADE,
- doi_path TEXT,
- full_data jsonb NOT NULL,
- created timestamp DEFAULT NOW()
-);
diff --git a/src/sql/changes/2023-06-05-add-constraints.sql b/src/sql/changes/2023-06-05-add-constraints.sql
deleted file mode 100644
index 2551b5df4..000000000
--- a/src/sql/changes/2023-06-05-add-constraints.sql
+++ /dev/null
@@ -1,4 +0,0 @@
-ALTER TABLE IF EXISTS public.plot_locks DROP CONSTRAINT plot_locks_pkey;
-
-ALTER TABLE IF EXISTS public.plot_locks ADD CONSTRAINT plot_locks_plot_rid_key UNIQUE (plot_rid);
-ALTER TABLE IF EXISTS public.plot_locks ADD CONSTRAINT plot_locks_user_rid_key UNIQUE (user_rid);
diff --git a/src/sql/changes/2023-11-20-add-confidence-comment-column.sql b/src/sql/changes/2023-11-20-add-confidence-comment-column.sql
new file mode 100644
index 000000000..6341f4df0
--- /dev/null
+++ b/src/sql/changes/2023-11-20-add-confidence-comment-column.sql
@@ -0,0 +1 @@
+ALTER TABLE user_plots ADD COLUMN confidence_comment text;
diff --git a/src/sql/functions/doi.sql b/src/sql/functions/doi.sql
index 0611575bd..0ae40b2e8 100644
--- a/src/sql/functions/doi.sql
+++ b/src/sql/functions/doi.sql
@@ -45,3 +45,14 @@ CREATE OR REPLACE FUNCTION insert_doi(_id integer, _project_id integer,
RETURNING doi_uid
$$ LANGUAGE SQL;
+
+-- Update DOI
+CREATE OR REPLACE FUNCTION update_doi(_id integer, _full_data jsonb)
+ RETURNS integer AS $$
+
+ UPDATE doi
+ set full_data = _full_data
+ WHERE doi_uid = _id
+ RETURNING doi_uid
+
+$$ LANGUAGE SQL;
diff --git a/src/sql/functions/plots.sql b/src/sql/functions/plots.sql
index 15167deaa..18530b9de 100644
--- a/src/sql/functions/plots.sql
+++ b/src/sql/functions/plots.sql
@@ -419,6 +419,7 @@ CREATE OR REPLACE FUNCTION upsert_user_samples(
_plot_id integer,
_user_id integer,
_confidence integer,
+ _confidence_comment text,
_collection_start timestamp,
_samples jsonb,
_images jsonb
@@ -426,12 +427,13 @@ CREATE OR REPLACE FUNCTION upsert_user_samples(
WITH user_plot_table AS (
INSERT INTO user_plots AS up
- (user_rid, plot_rid, confidence, collection_start, collection_time)
+ (user_rid, plot_rid, confidence, confidence_comment ,collection_start, collection_time)
VALUES
- (_user_id, _plot_id, _confidence, _collection_start, Now())
+ (_user_id, _plot_id, _confidence, _confidence_comment , _collection_start, Now())
ON CONFLICT (user_rid, plot_rid) DO
UPDATE
SET confidence = coalesce(excluded.confidence, up.confidence),
+ confidence_comment = _confidence_comment,
collection_start = coalesce(excluded.collection_start, up.collection_start),
collection_time = CASE WHEN excluded.collection_start IS NOT NULL THEN localtimestamp ELSE up.collection_time END,
flagged = FALSE,
diff --git a/src/sql/tables/all.sql b/src/sql/tables/all.sql
index 06a781b81..c014c72dc 100644
--- a/src/sql/tables/all.sql
+++ b/src/sql/tables/all.sql
@@ -204,7 +204,7 @@ CREATE INDEX project_widgets_project_rid ON project_widgets (project_rid);
-- Stores doi information for a project
CREATE TABLE doi(
doi_uid INTEGER NOT NULL PRIMARY KEY,
- project_rid INTEGER UNIQUE NOT NULL REFERENCES projects (project_uid) ON DELETE CASCADE ON UPDATE CASCADE,
+ project_rid INTEGER NOT NULL REFERENCES projects (project_uid) ON DELETE CASCADE ON UPDATE CASCADE,
user_id INTEGER NOT NULL REFERENCES users (user_uid) ON DELETE CASCADE ON UPDATE CASCADE,
doi_path TEXT,
full_data jsonb NOT NULL,