Skip to content

Commit

Permalink
Merge pull request #944 from AtlasOfLivingAustralia/feature/issue941
Browse files Browse the repository at this point in the history
fauna plot #941
  • Loading branch information
chrisala authored May 16, 2024
2 parents 0b9aeed + e815077 commit 3593d15
Show file tree
Hide file tree
Showing 5 changed files with 171 additions and 78 deletions.
10 changes: 10 additions & 0 deletions grails-app/domain/au/org/ala/ecodata/Site.groovy
Original file line number Diff line number Diff line change
Expand Up @@ -181,4 +181,14 @@ class Site {
status != Status.DELETED
}.find()
}

static List<Site> findAllByExternalId(ExternalId.IdType idType, String externalId, Map params) {
where {
externalIds {
idType == idType
externalId == externalId
}
status != Status.DELETED
}.list(params)
}
}
80 changes: 47 additions & 33 deletions grails-app/services/au/org/ala/ecodata/ParatooService.groovy
Original file line number Diff line number Diff line change
Expand Up @@ -535,35 +535,39 @@ class ParatooService {
// used by protocols like bird survey where a point represents a sight a bird has been observed in a
// bird survey plot
def location = output[model.name]
if (location instanceof Map) {
output[model.name] = [
type : 'Feature',
geometry : [
type : 'Point',
coordinates: [location.lng, location.lat]
],
properties: [
name : "Point ${formName}-${featureId}",
externalId: location.id,
id: "${formName}-${featureId}"
]
]
}
else if (location instanceof List) {
String name
switch (config?.geometryType) {
case "LineString":
name = "LineString ${formName}-${featureId}"
output[model.name] = ParatooProtocolConfig.createLineStringFeatureFromGeoJSON (location, name, null, name)
break
default:
name = "Polygon ${formName}-${featureId}"
output[model.name] = ParatooProtocolConfig.createFeatureFromGeoJSON (location, name, null, name)
break
if (location) {
if (location instanceof Map) {
output[model.name] = [
type : 'Feature',
geometry : [
type : 'Point',
coordinates: [location.lng, location.lat]
],
properties: [
name : "Point ${formName}-${featureId}",
externalId: location.id,
id : "${formName}-${featureId}"
]
]
} else if (location instanceof List) {
String name
switch (config?.geometryType) {
case "LineString":
name = "LineString ${formName}-${featureId}"
output[model.name] = ParatooProtocolConfig.createLineStringFeatureFromGeoJSON(location, name, null, name)
break
default:
name = "Polygon ${formName}-${featureId}"
output[model.name] = ParatooProtocolConfig.createFeatureFromGeoJSON(location, name, null, name)
break
}
}
}

featureId ++
featureId ++
}
else {
output[model.name] = null
}
break
case "image":
case "document":
Expand All @@ -588,7 +592,10 @@ class ParatooService {
List features = geoJson?.features ?: []
geoJson.remove('features')
siteProps.features = features
siteProps.type = Site.TYPE_SURVEY_AREA
if (features)
siteProps.type = Site.TYPE_COMPOUND
else
siteProps.type = Site.TYPE_SURVEY_AREA
siteProps.publicationStatus = PublicationStatus.PUBLISHED
siteProps.projects = [project.projectId]
String externalId = geoJson.properties?.externalId
Expand All @@ -598,18 +605,19 @@ class ParatooService {
Site site
// create new site for every non-plot submission
if (config.usesPlotLayout) {
site = Site.findByExternalId(ExternalId.IdType.MONITOR_PLOT_GUID, externalId)
if (site?.features) {
siteProps.features?.addAll(site.features)
}
List sites = Site.findAllByExternalId(ExternalId.IdType.MONITOR_PLOT_GUID, externalId, [sort: "lastUpdated", order: "desc"])
if (sites)
site = sites.first()
}

Map result
if (!site) {
// If the plot layout has been updated, create a new site
if (!site || isUpdatedPlotLayout(site, observation, config)) {
result = siteService.create(siteProps)
} else {
result = [siteId: site.siteId]
}

if (result.error) {
// Don't treat this as a fatal error for the purposes of responding to the paratoo request
log.error("Error creating a site for survey " + collection.orgMintedUUID + ", project " + project.projectId + ": " + result.error)
Expand All @@ -619,6 +627,12 @@ class ParatooService {
[siteId:siteId, name:siteProps?.name]
}

private static boolean isUpdatedPlotLayout (Site site, Map observation, ParatooProtocolConfig config) {
Date localSiteUpdated = site.lastUpdated
Date plotLayoutUpdated = config.getPlotLayoutUpdatedAt(observation)
plotLayoutUpdated?.after(localSiteUpdated) ?: false
}

private Map syncParatooProtocols(List<Map> protocols) {
Map result = [errors: [], messages: []]
List guids = []
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,8 @@ import org.locationtech.jts.geom.Geometry
@Slf4j
@JsonIgnoreProperties(['metaClass', 'errors', 'expandoMetaClass'])
class ParatooProtocolConfig {

static final String FAUNA_PLOT = 'Fauna plot'
static final String CORE_PLOT = 'Core monitoring plot'
String name
String apiEndpoint
boolean usesPlotLayout = true
Expand All @@ -26,7 +27,9 @@ class ParatooProtocolConfig {
String plotVisitPath = 'plot_visit'
String plotLayoutPath = "${plotVisitPath}.plot_layout"
String plotLayoutIdPath = "${plotLayoutPath}.id"
String plotLayoutUpdatedAtPath = "${plotLayoutPath}.updatedAt"
String plotLayoutPointsPath = "${plotLayoutPath}.plot_points"
String faunaPlotPointPath = "${plotLayoutPath}.fauna_plot_point"
String plotSelectionPath = "${plotLayoutPath}.plot_selection"
String plotLayoutDimensionLabelPath = "${plotLayoutPath}.plot_dimensions.label"
String plotLayoutTypeLabelPath = "${plotLayoutPath}.plot_type.label"
Expand Down Expand Up @@ -79,6 +82,16 @@ class ParatooProtocolConfig {
return removeMilliseconds(date)
}

Date getPlotLayoutUpdatedAt(Map surveyData) {
def date = getProperty(surveyData, plotLayoutUpdatedAtPath)
if (!date) {
date = getPropertyFromSurvey(surveyData, plotLayoutUpdatedAtPath)
}

date = getFirst(date)
date ? DateUtil.parseWithMilliseconds(date) : null
}

Map getSurveyId(Map surveyData) {
if(surveyIdPath == null || surveyData == null) {
return null
Expand Down Expand Up @@ -135,7 +148,8 @@ class ParatooProtocolConfig {
List features = []
paths.each { String name, node ->
if (node instanceof Boolean) {
features.add(output[name])
if (output[name])
features.add(output[name])
// todo later: add featureIds and modelId for compliance with feature behaviour of reports
}

Expand Down Expand Up @@ -201,31 +215,17 @@ class ParatooProtocolConfig {
Map geoJson = null
if (usesPlotLayout) {
geoJson = extractSiteDataFromPlotVisit(output)
// get list of all features associated with observation
if (geoJson && form && output) {
geoJson.features = extractFeatures(output, form)
}
}
else if (geometryPath) {
geoJson = extractSiteDataFromPath(output)
}
else if (form && output) {
List features = extractFeatures(output, form)
if (features) {
List featureGeometries = features.collect { it.geometry }
Geometry geometry = GeometryUtils.getFeatureCollectionConvexHull(featureGeometries)
String startDateInString = getStartDate(output)
startDateInString = DateUtil.convertUTCDateToStringInTimeZone(startDateInString, clientTimeZone?:TimeZone.default)
String name = "${form.name} site - ${startDateInString}"
geoJson = [
type: 'Feature',
geometry: GeometryUtils.geometryToGeoJsonMap(geometry),
properties: [
name: name,
description: "${name} (convex hull of all features)",
],
features: features
]
geoJson = createConvexHullGeoJSON(features, name)
}
}

Expand Down Expand Up @@ -307,12 +307,13 @@ class ParatooProtocolConfig {

private Map extractSiteDataFromPlotVisit(Map survey) {
Map surveyData = getSurveyData(survey)
def plotLayoutId = getProperty(surveyData, plotLayoutIdPath) // Currently an int, may become uuid?
String plotLayoutId = getProperty(surveyData, plotLayoutIdPath) // Currently an int, may become uuid?

if (!plotLayoutId) {
log.warn("No plot_layout found in survey at path ${plotLayoutIdPath}")
return null
}

List plotLayoutPoints = getProperty(surveyData, plotLayoutPointsPath)
Map plotSelection = getProperty(surveyData, plotSelectionPath)
Map plotSelectionGeoJson = plotSelectionToGeoJson(plotSelection)
Expand All @@ -322,22 +323,41 @@ class ParatooProtocolConfig {

String name = plotSelectionGeoJson.properties.name + ' - ' + plotLayoutTypeLabel + ' (' + plotLayoutDimensionLabel + ')'

Map plotGeoJson = createFeatureFromGeoJSON(plotLayoutPoints, name, plotLayoutId, plotSelectionGeoJson?.properties?.notes)

//Map faunaPlotGeoJson = toGeometry(plotLayout.fauna_plot_point)
Map plotGeoJson = createFeatureFromGeoJSON(plotLayoutPoints, name, plotLayoutId, "${CORE_PLOT} ${plotSelectionGeoJson?.properties?.notes?:""}")
List faunaPlotPoints = getProperty(surveyData, faunaPlotPointPath)

// TODO maybe turn this into a feature with properties to distinguish the fauna plot?
// Or a multi-polygon?
if (faunaPlotPoints) {
Map faunaPlotGeoJson = createFeatureFromGeoJSON(faunaPlotPoints, name, plotLayoutId, "${FAUNA_PLOT} ${plotSelectionGeoJson?.properties?.notes?:""}")
List features = [plotGeoJson, faunaPlotGeoJson]
plotGeoJson = createConvexHullGeoJSON(features, name, plotLayoutId, plotGeoJson.properties.notes)
}

plotGeoJson
}

static Map createFeatureFromGeoJSON(List plotLayoutPoints, String name, def plotLayoutId, String notes = "") {
static Map createConvexHullGeoJSON (List features, String name, String externalId = "", String notes = "") {
features = features.findAll { it.geometry != null }
List featureGeometries = features.collect { it.geometry }
Geometry geometry = GeometryUtils.getFeatureCollectionConvexHull(featureGeometries)
[
type: 'Feature',
geometry: GeometryUtils.geometryToGeoJsonMap(geometry),
properties: [
name: name,
externalId: externalId,
notes: notes,
description: "${name} (convex hull of all features)",
],
features: features
]
}

static Map createFeatureFromGeoJSON(List plotLayoutPoints, String name, String plotLayoutId, String notes = "") {
Map plotGeometry = toGeometry(plotLayoutPoints)
createFeatureObject(plotGeometry, name, plotLayoutId, notes)
}

static Map createFeatureObject(Map plotGeometry, String name, plotLayoutId, String notes = "") {
static Map createFeatureObject(Map plotGeometry, String name, String plotLayoutId, String notes = "") {
[
type : 'Feature',
geometry : plotGeometry,
Expand All @@ -362,7 +382,7 @@ class ParatooProtocolConfig {
plotGeometry
}

static Map createLineStringFeatureFromGeoJSON (List plotLayoutPoints, String name, def plotLayoutId, String notes = "") {
static Map createLineStringFeatureFromGeoJSON (List plotLayoutPoints, String name, String plotLayoutId, String notes = "") {
Map plotGeometry = toLineStringGeometry(plotLayoutPoints)
createFeatureObject(plotGeometry, name, plotLayoutId, notes)
}
Expand Down
6 changes: 3 additions & 3 deletions src/test/groovy/au/org/ala/ecodata/ParatooServiceSpec.groovy
Original file line number Diff line number Diff line change
Expand Up @@ -339,9 +339,9 @@ class ParatooServiceSpec extends MongoSpec implements ServiceUnitTest<ParatooSer

and:
site.name == "SATFLB0001 - Control (100 x 100)"
site.description == "SATFLB0001 - Control (100 x 100)"
site.notes == "some comment"
site.type == "surveyArea"
site.description == "SATFLB0001 - Control (100 x 100) (convex hull of all features)"
site.notes == "Core monitoring plot some comment"
site.type == "compound"
site.publicationStatus == "published"
site.externalIds[0].externalId == "2"
site.externalIds[0].idType == ExternalId.IdType.MONITOR_PLOT_GUID
Expand Down
Loading

0 comments on commit 3593d15

Please sign in to comment.