Skip to content
This repository has been archived by the owner on Apr 6, 2023. It is now read-only.

Commit

Permalink
Merge pull request #274 from MLH-Fellowship/mapillaryui
Browse files Browse the repository at this point in the history
Added Date selector, org ID, Mapillary in map editors by default
  • Loading branch information
Zack LaVergne authored Aug 9, 2022
2 parents c3e7f7f + b5eca82 commit bf5b0e2
Show file tree
Hide file tree
Showing 17 changed files with 326 additions and 25 deletions.
11 changes: 10 additions & 1 deletion backend/models/dtos/project_dto.py
Original file line number Diff line number Diff line change
Expand Up @@ -199,7 +199,11 @@ class ProjectDTO(Model):
enforce_random_task_selection = BooleanType(
required=False, default=False, serialized_name="enforceRandomTaskSelection"
)

earliest_street_imagery = UTCDateTimeType(serialized_name="earliestStreetImagery")
image_capture_mode = BooleanType(
required=False, default=False, serialized_name="imageCaptureMode"
)
mapillary_organization_id = StringType(serialized_name="mapillaryOrganizationId")
private = BooleanType(required=True)
changeset_comment = StringType(serialized_name="changesetComment")
osmcha_filter_id = StringType(serialized_name="osmchaFilterId")
Expand Down Expand Up @@ -462,6 +466,11 @@ class ProjectSummary(Model):
author = StringType()
created = UTCDateTimeType()
due_date = UTCDateTimeType(serialized_name="dueDate")
earliest_street_imagery = UTCDateTimeType(serialized_name="earliestStreetImagery")
image_capture_mode = BooleanType(
required=False, default=False, serialized_name="imageCaptureMode"
)
mapillary_organization_id = StringType(serialized_name="mapillaryOrganizationId")
last_updated = UTCDateTimeType(serialized_name="lastUpdated")
priority = StringType(serialized_name="projectPriority")
campaigns = ListType(ModelType(CampaignDTO), default=[])
Expand Down
12 changes: 12 additions & 0 deletions backend/models/postgis/project.py
Original file line number Diff line number Diff line change
Expand Up @@ -146,7 +146,10 @@ class Project(db.Model):
db.String
) # Optional custom filter id for filtering on OSMCha
due_date = db.Column(db.DateTime)
earliest_street_imagery = db.Column(db.DateTime)
imagery = db.Column(db.String)
image_capture_mode = db.Column(db.Boolean, default=False)
mapillary_organization_id = db.Column(db.String)
josm_preset = db.Column(db.String)
id_presets = db.Column(ARRAY(db.String))
extra_id_params = db.Column(db.String)
Expand Down Expand Up @@ -378,6 +381,9 @@ def update(self, project_dto: ProjectDTO):
self.mapper_level = MappingLevel[project_dto.mapper_level.upper()].value
self.changeset_comment = project_dto.changeset_comment
self.due_date = project_dto.due_date
self.earliest_street_imagery = project_dto.earliest_street_imagery
self.image_capture_mode = project_dto.image_capture_mode
self.mapillary_organization_id = project_dto.mapillary_organization_id
self.imagery = project_dto.imagery
self.josm_preset = project_dto.josm_preset
self.id_presets = project_dto.id_presets
Expand Down Expand Up @@ -836,6 +842,9 @@ def get_project_summary(self, preferred_locale) -> ProjectSummary:
summary.country_tag = self.country
summary.changeset_comment = self.changeset_comment
summary.due_date = self.due_date
summary.earliest_street_imagery = self.earliest_street_imagery
summary.image_capture_mode = self.image_capture_mode
summary.mapillary_organization_id = self.mapillary_organization_id
summary.created = self.created
summary.last_updated = self.last_updated
summary.osmcha_filter_id = self.osmcha_filter_id
Expand Down Expand Up @@ -1011,6 +1020,9 @@ def _get_project_and_base_dto(self):
base_dto.changeset_comment = self.changeset_comment
base_dto.osmcha_filter_id = self.osmcha_filter_id
base_dto.due_date = self.due_date
base_dto.earliest_street_imagery = self.earliest_street_imagery
base_dto.image_capture_mode = self.image_capture_mode
base_dto.mapillary_organization_id = self.mapillary_organization_id
base_dto.imagery = self.imagery
base_dto.josm_preset = self.josm_preset
base_dto.id_presets = self.id_presets
Expand Down
6 changes: 5 additions & 1 deletion frontend/src/assets/styles/_extra.scss
Original file line number Diff line number Diff line change
Expand Up @@ -383,6 +383,10 @@ a[href="https://www.mapbox.com/map-feedback/"] {
}
}

div.react-datepicker-popper {
z-index: 5;
}

.comment-textarea {
box-sizing: border-box;

Expand All @@ -398,4 +402,4 @@ a[href="https://www.mapbox.com/map-feedback/"] {
.sticky-top {
position: sticky !important;
top: 0;
}
}
29 changes: 27 additions & 2 deletions frontend/src/components/editor.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,15 @@ import '@hotosm/id/dist/iD.css';

import { OSM_CONSUMER_KEY, OSM_CONSUMER_SECRET, OSM_SERVER_URL } from '../config';

export default function Editor({ setDisable, comment, presets, imagery, gpxUrl }) {
export default function Editor({
setDisable,
comment,
presets,
imagery,
gpxUrl,
earliestStreetImagery,
imageCaptureMode,
}) {
const dispatch = useDispatch();
const session = useSelector((state) => state.auth.get('session'));
const iDContext = useSelector((state) => state.editor.context);
Expand Down Expand Up @@ -105,8 +113,25 @@ export default function Editor({ setDisable, comment, presets, imagery, gpxUrl }
setDisable(false);
}
});

if (imageCaptureMode) {
if (earliestStreetImagery) {
iDContext.photos().setDateFilter('fromDate', earliestStreetImagery.substr(0, 10), false);
}
window.location.href =
window.location.href + '&photo_overlay=mapillary,mapillary-map-features,mapillary-signs';
}
}
}, [session, iDContext, setDisable, presets, locale, gpxUrl]);
}, [
session,
iDContext,
setDisable,
presets,
locale,
gpxUrl,
earliestStreetImagery,
imageCaptureMode,
]);

return <div className="w-100 vh-minus-77-ns" id="id-container"></div>;
}
8 changes: 8 additions & 0 deletions frontend/src/components/projectCreate/messages.js
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,14 @@ export default defineMessages({
id: 'management.projects.create.trim_tasks.trim_to_aoi',
defaultMessage: 'Trim the tasks to define the exact Area of Interest for mapping.',
},
trimExcludeWater: {
id: 'management.projects.create.trim_tasks.trim_exclude_water',
defaultMessage: 'Trim the tasks to exclude water areas.',
},
trimCoverPathsRoads: {
id: 'management.projects.create.trim_tasks.trim_cover_paths_roads',
defaultMessage: 'Trim the tasks to only cover paths and roads.',
},
tinyTasks: {
id: 'management.projects.create.trim_tasks.tiny_tasks',
defaultMessage:
Expand Down
25 changes: 25 additions & 0 deletions frontend/src/components/projectCreate/trimProject.js
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,9 @@ const removeTinyTasks = (metadata, updateMetadata) => {
export default function TrimProject({ metadata, mapObj, updateMetadata }) {
const token = useSelector((state) => state.auth.get('token'));
const [clipStatus, setClipStatus] = useState(false);
const [roadStatus, setRoadStatus] = useState(false);
const [waterStatus, setWaterStatus] = useState(false);

const [tinyTasksNumber, setTinyTasksNumber] = useState(0);

const trimTaskGridAsync = useAsync(trimTaskGrid);
Expand Down Expand Up @@ -72,6 +75,28 @@ export default function TrimProject({ metadata, mapObj, updateMetadata }) {
onChange={() => setClipStatus(!clipStatus)}
label={<FormattedMessage {...messages.trimToAOI} />}
/>

<div className="pt3">
<SwitchToggle
isChecked={waterStatus}
labelPosition="right"
onChange={() => {
setWaterStatus(!waterStatus);
}}
label={<FormattedMessage {...messages.trimExcludeWater} />}
/>
</div>
<div className="pt3">
<SwitchToggle
isChecked={roadStatus}
labelPosition="right"
onChange={() => {
setRoadStatus(!roadStatus);
}}
label={<FormattedMessage {...messages.trimCoverPathsRoads} />}
/>
</div>

<div className="pt3">
<CustomButton
onClick={() =>
Expand Down
87 changes: 86 additions & 1 deletion frontend/src/components/projectEdit/imageryForm.js
Original file line number Diff line number Diff line change
@@ -1,17 +1,22 @@
import React, { useContext, useState, useLayoutEffect } from 'react';
import Select from 'react-select';
import { FormattedMessage } from 'react-intl';

import DatePicker from 'react-datepicker';
import { SwitchToggle } from '../formInputs';
import messages from './messages';
import { StateContext, styleClasses } from '../../views/projectEdit';
import { Code } from '../code';
import { fetchLocalJSONAPI } from '../../network/genericJSONRequest';
import { useImageryOption, IMAGERY_OPTIONS } from '../../hooks/UseImageryOption';
import { MAPILLARY_TOKEN } from '../../config';
import axios from 'axios';

export const ImageryForm = () => {
const { projectInfo, setProjectInfo } = useContext(StateContext);
const [licenses, setLicenses] = useState(null);

const [organization, setOrganization] = useState(null);

useLayoutEffect(() => {
const fetchLicenses = async () => {
fetchLocalJSONAPI('licenses/')
Expand All @@ -21,6 +26,15 @@ export const ImageryForm = () => {
fetchLicenses();
}, [setLicenses]);

if (projectInfo.mapillaryOrganizationId) {
axios
.get(
`https://graph.mapillary.com/${projectInfo.mapillaryOrganizationId}?access_token=${MAPILLARY_TOKEN}&fields=name`,
)
.then((resp) => setOrganization(resp.data.name))
.catch(() => setOrganization(null));
}

let defaultValue = null;
if (licenses !== null && projectInfo.licenseId !== null) {
defaultValue = licenses.filter((l) => l.licenseId === projectInfo.licenseId)[0];
Expand All @@ -34,6 +48,7 @@ export const ImageryForm = () => {
</label>
<ImageryField imagery={projectInfo.imagery} setProjectInfo={setProjectInfo} />
</div>

<div className={styleClasses.divClass}>
<label className={styleClasses.labelClass}>
<FormattedMessage {...messages.license} />
Expand All @@ -54,6 +69,76 @@ export const ImageryForm = () => {
className="w-50 z-1"
/>
</div>

<div className={styleClasses.divClass}>
<label className={styleClasses.labelClass}>
<FormattedMessage {...messages.imageCaptureMode} />
</label>
<p className={styleClasses.pClass}>
<FormattedMessage {...messages.imageCaptureModeInfo} />
</p>
<SwitchToggle
label={<FormattedMessage {...messages.imageCaptureMode} />}
labelPosition="right"
isChecked={projectInfo.imageCaptureMode}
onChange={() =>
setProjectInfo({ ...projectInfo, imageCaptureMode: !projectInfo.imageCaptureMode })
}
/>
</div>

{projectInfo.imageCaptureMode && (
<>
<div className={styleClasses.divClass}>
<label className={styleClasses.labelClass}>
<FormattedMessage {...messages.imageryCaptureDate} />
</label>
<FormattedMessage {...messages.imageryCaptureDateAfter} />
<span>: &nbsp;&nbsp;</span>
<DatePicker
selected={Date.parse(projectInfo.earliestStreetImagery)}
onChange={(date) =>
setProjectInfo({
...projectInfo,
earliestStreetImagery: date,
})
}
placeholderText="DD/MM/YYYY"
dateFormat="dd/MM/yyyy"
className={styleClasses.inputClass}
showYearDropdown
scrollableYearDropdown
/>
</div>

<div className={styleClasses.divClass}>
<label className={styleClasses.labelClass}>
<FormattedMessage {...messages.mapillaryOrganizationId} />
</label>
<p className={styleClasses.pClass}>
<FormattedMessage {...messages.mapillaryOrganizationIdInfo} />
</p>

<b>
<FormattedMessage {...messages.mapillaryOrganizationSelected} />
</b>
<span>:&nbsp;{organization}</span>

<input
className={styleClasses.inputClass}
type="text"
name="mapillaryOrganizationId"
value={projectInfo.mapillaryOrganizationId || ''}
onChange={(e) => {
setProjectInfo({
...projectInfo,
mapillaryOrganizationId: e.target.value,
});
}}
/>
</div>
</>
)}
</div>
);
};
Expand Down
28 changes: 28 additions & 0 deletions frontend/src/components/projectEdit/messages.js
Original file line number Diff line number Diff line change
Expand Up @@ -593,6 +593,34 @@ export default defineMessages({
defaultMessage:
'This will remove the custom editor from the project. Are you sure you don\'t want to disable the custom editor by toggling the "Enabled" checkbox above?',
},
imageCaptureMode: {
id: 'projects.formInputs.imageCaptureMode',
defaultMessage: 'Image capture mode',
},
imageCaptureModeInfo: {
id: 'projects.formInputs.imageCaptureMode.info',
defaultMessage: 'Adapts Tasking Manager to street imagery capture workflow.',
},
imageryCaptureDate: {
id: 'projects.formInputs.imageryCaptureDate',
defaultMessage: 'Imagery capture date',
},
imageryCaptureDateAfter: {
id: 'projects.formInputs.imageryCaptureDate.after',
defaultMessage: 'After',
},
mapillaryOrganizationId: {
id: 'projects.formInputs.mapillaryOrganizationId',
defaultMessage: 'Mapillary organization ID',
},
mapillaryOrganizationIdInfo: {
id: 'projects.formInputs.mapillaryOrganizationId.info',
defaultMessage: '15-digit identifier to filter Mapillary contributions.',
},
mapillaryOrganizationSelected: {
id: 'projects.formInputs.mapillaryOrganizationId.selected',
defaultMessage: 'Organization',
},
noMappingEditor: {
id: 'projects.formInputs.noMappingEditor',
defaultMessage: 'At least one editor must be enabled for mapping',
Expand Down
27 changes: 26 additions & 1 deletion frontend/src/components/rapidEditor.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@ export default function RapidEditor({
imagery,
gpxUrl,
powerUser = false,
earliestStreetImagery,
imageCaptureMode,
}) {
const dispatch = useDispatch();
const session = useSelector((state) => state.auth.get('session'));
Expand Down Expand Up @@ -113,8 +115,31 @@ export default function RapidEditor({
setDisable(false);
}
});

if (imageCaptureMode) {
if (earliestStreetImagery) {
RapiDContext.photos().setDateFilter(
'fromDate',
earliestStreetImagery.substr(0, 10),
false,
);
}

window.location.href =
window.location.href + '&photo_overlay=mapillary,mapillary-map-features,mapillary-signs';
}
}
}, [session, RapiDContext, setDisable, presets, locale, gpxUrl, powerUser]);
}, [
session,
RapiDContext,
setDisable,
presets,
locale,
gpxUrl,
powerUser,
earliestStreetImagery,
imageCaptureMode,
]);

return <div className="w-100 vh-minus-77-ns" id="rapid-container"></div>;
}
Loading

0 comments on commit bf5b0e2

Please sign in to comment.