Skip to content

Commit ffd8dc1

Browse files
committed
#390 support feature catalogue production
1 parent f6f0678 commit ffd8dc1

File tree

3 files changed

+234
-3
lines changed

3 files changed

+234
-3
lines changed

R/geoflow_action.R

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -523,11 +523,18 @@ register_actions <- function(){
523523
geoflow_action$new(
524524
id="metadataeditr-create-project",
525525
types = list("Metadata publication"),
526-
def = "Create and publish a geospatial project in the World bank metadata editor",
526+
def = "Create and publish a geospatial project in the World Bank metadata editor",
527527
target = "entity",
528528
target_dir = "metadata",
529529
packages = list("metadataeditr"),
530530
available_options = list(
531+
fc = list(def = "Whether the feature catalog has to be produced", class = "logical", default = TRUE),
532+
fc_exclude_attributes = list(def = "Attributes that should be excluded from the ISO 19110 production", class = "character", choices = list(), add_choices = TRUE, multiple = TRUE, default = c()),
533+
fc_exclude_attributes_not_in_dictionary = list(def = "Feature catalog - Enable to exclude all attributes/variables not referenced as dictionary/featuretype", class="logical", default = FALSE),
534+
fc_exclude_values_for_attributes = list(def = "Feature catalog - Attribute names for which listed values should not be produced", class = "character", choices = list(), add_choices = TRUE, multiple = TRUE, default = c()),
535+
fc_extra_attributes = list(def = "Feature catalog - Extra attributes to add as feature catalog attributes although not in data", class = "character", choices = list(), add_choices = TRUE, multiple = TRUE, default = c()),
536+
fc_default_min_occurs = list(def = "Feature catalog - The default min occurs value for feature attributes cardinality", class = "integer", default = 0L),
537+
fc_default_max_occurs = list(def = "Feature catalog - The default max occurs value for feature attribute cardinality", class = "numeric", default = Inf),
531538
depositWithFiles = list(def = "Indicates if the action is uploading files", class = "logical", default = TRUE),
532539
depositDataPattern = list(def = "A regular expression to filter data files to upload in metadata editor", class = "character", default = ""),
533540
depositMetadataPattern = list(def = "A regular expression to filter metadata files to upload in metadata editor", class = "character", default = "")

inst/actions/metadataeditr_create_project.R

Lines changed: 214 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,10 +16,51 @@ function(action, entity, config){
1616
}
1717

1818
#options
19+
#feature catalog related options
20+
fc <- action$getOption("fc")
21+
fc_exclude_attributes <- action$getOption("fc_exclude_attributes")
22+
fc_exclude_attributes_not_in_dictionary <- action$getOption("fc_exclude_attributes_not_in_dictionary")
23+
fc_exclude_values_for_attributes <- action$getOption("fc_exclude_values_for_attributes")
24+
fc_extra_attributes <- action$getOption("fc_extra_attributes")
25+
fc_default_min_occurs <- action$getOption("fc_default_min_occurs")
26+
fc_default_max_occurs <- action$getOption("fc_default_max_occurs")
27+
#file upload related options
1928
depositWithFiles <- action$getOption("depositWithFiles")
2029
depositDataPattern <- action$getOption("depositDataPattern")
2130
depositMetadataPattern <- action$getOption("depositMetadataPattern")
2231

32+
33+
#features if any
34+
build_catalog_from_features = TRUE
35+
if(fc){
36+
#manage multiple sources (supposes a common data structure to expose as FC)
37+
data_objects <- list()
38+
if(is.null(entity$data$dir)){
39+
data_objects <- list(entity$data)
40+
}else{
41+
data_objects <- entity$data$getData()
42+
}
43+
features = do.call("rbind", lapply(data_objects, function(data_object){data_object$features}))
44+
if(is.null(features)){
45+
if(!skipEnrichWithData){
46+
warnMsg <- sprintf("No data features associated to entity '%s' and global option 'skipEnrichWithData' is false. Skip feature catalogue creation", entity$identifiers[["id"]])
47+
config$logger.warn(warnMsg)
48+
fc = FALSE
49+
}else{
50+
fto <- entity$data$featureTypeObj
51+
if(!is.null(fto)){
52+
infoMsg <- "Global option 'skipEnrichWithData' is true. Feature catalogue will be created based on the dictionary only"
53+
config$logger.info(infoMsg)
54+
build_catalog_from_features = FALSE
55+
}else{
56+
warnMsg <- "Global option 'skipEnrichWithData' is true, but no dictionary available. Skip feature catalogue creation"
57+
config$logger.warn(warnMsg)
58+
fc = FALSE
59+
}
60+
}
61+
}
62+
}
63+
2364
#basic function to map a geoflow_contact to a metadata editor contact
2465
produce_md_contact = function(x){
2566

@@ -313,8 +354,180 @@ function(action, entity, config){
313354

314355
#description/contentInfo
315356
#description/feature_catalogue (common to all metadata standards?)
357+
if(fc){
358+
project$description$feature_catalogue = list()
359+
project$description$feature_catalogue$name = paste0(entity$titles[["title"]], " - Feature Catalogue")
360+
project$description$feature_catalogue$fieldOfApplication = list("FAIR") #to map
361+
versionDate <- as.POSIXct(Sys.time())
362+
project$description$feature_catalogue$versionNumber <- format(versionDate, "%Y%m%dT%H%M%S")
363+
project$description$feature_catalogue$versionDate = list(
364+
date = versionDate,
365+
type = "publication"
366+
)
367+
project$description$feature_catalogue$producer = produce_md_contact(producers[[1]])
368+
project$description$feature_catalogue$functionalLanguage = entity$language
369+
#featuretype
370+
ft = list(
371+
typeName = entity$identifiers$id,
372+
definition = entity$titles[["title"]],
373+
code = entity$identifiers$id,
374+
isAbstract = FALSE,
375+
carrierOfCharacteristics = list()
376+
)
377+
378+
columns <- if(build_catalog_from_features){
379+
#from data features
380+
c(colnames(features), unlist(fc_extra_attributes))
381+
}else{
382+
#from dictionary
383+
fto <- entity$data$featureTypeObj
384+
sapply(fto$getMembers(), function(x){x$id})
385+
}
386+
for(featureAttrName in columns){
387+
388+
if(featureAttrName %in% fc_exclude_attributes){
389+
config$logger.warn(sprintf("Feature Attribute '%s' is listed in 'fc_exclude_attributes'. Discarding it...", featureAttrName))
390+
next
391+
}
392+
393+
fat_attr_register <- NULL
394+
395+
#create attribute
396+
fat <- list()
397+
#default name (from data)
398+
memberName <- featureAttrName
399+
400+
fat_attr <- NULL
401+
fat_attr_desc <- NULL
402+
fto <- entity$data$featureTypeObj
403+
if(!is.null(fto)) fat_attr <- fto$getMemberById(featureAttrName)
404+
if(!is.null(fat_attr)){
405+
fat_attr_desc <- fat_attr$name
406+
registerId <- fat_attr$registerId
407+
if(!is.null(registerId)) if(!is.na(registerId)){
408+
registers <- config$registers
409+
if(length(registers)>0) registers <- registers[sapply(registers, function(x){x$id == registerId})]
410+
if(length(registers)==0){
411+
warnMsg <- sprintf("Unknown register '%s'. Ignored for creating feature catalogue", registerId)
412+
config$logger.warn(warnMsg)
413+
}else{
414+
fat_attr_register <- registers[[1]]
415+
}
416+
}
417+
if(!is.null(fat_attr_desc)) memberName <- fat_attr_desc
418+
}else{
419+
if(fc_exclude_attributes_not_in_dictionary){
420+
config$logger.warn(sprintf("Feature Attribute '%s' not referenced in dictionary and 'fc_exclude_attributes_not_in_dictionary' option is enabled. Discarding it...", featureAttrName))
421+
next
422+
}
423+
}
424+
fat$memberName = memberName
425+
fat$definition = fat_attr$def
426+
#cardinality
427+
minOccurs <- fc_default_min_occurs; if(!is.null(fat_attr)) minOccurs <- fat_attr$minOccurs
428+
maxOccurs <- fc_default_max_occurs; if(!is.null(fat_attr)) maxOccurs <- fat_attr$maxOccurs
429+
if(is.null(minOccurs)) minOccurs <- fc_default_min_occurs
430+
if(is.null(maxOccurs)) maxOccurs <- fc_default_max_occurs
431+
if(maxOccurs == "Inf") maxOccurs <- Inf
432+
fat$cardinality = list(lower = minOccurs, upper = maxOccurs)
433+
#code
434+
fat$code = featureAttrName
435+
#uom
436+
fat$valueMeasurementUnit = fat_attr$uom
437+
#add listed values
438+
featureAttrValues <- fat_attr_register$data$code
439+
if(build_catalog_from_features) if(featureAttrName %in% colnames(features)){
440+
featureAttrValues <- switch(class(features)[1],
441+
"sf" = features[,featureAttrName][[1]],
442+
"data.frame" = features[,featureAttrName]
443+
)
444+
}
445+
addValues <- TRUE
446+
if(is(featureAttrValues, "sfc")){
447+
addValues <- FALSE
448+
}else if(featureAttrName %in% fc_exclude_values_for_attributes){
449+
addValues <- FALSE
450+
}else{
451+
if(is.null(fat_attr)){
452+
addValues <- FALSE
453+
}else{
454+
if(fat_attr$type == "variable") addValues <- FALSE
455+
}
456+
}
457+
fat$listedValue = list()
458+
if(!is.null(featureAttrValues) & addValues){
459+
config$logger.info(sprintf("Listing values for feature Attribute '%s'...", featureAttrName))
460+
featureAttrValues <- unique(featureAttrValues)
461+
featureAttrValues <- featureAttrValues[order(featureAttrValues)]
462+
for(featureAttrValue in featureAttrValues){
463+
if(!is.na(featureAttrValue)){
464+
val <- list(label = "", code = "", definition = "")
465+
if(!is(featureAttrValue, "character")) featureAttrValue <- as(featureAttrValue, "character")
466+
val$code = featureAttrValue
467+
if(!is.null(fat_attr_register)){
468+
reg_item <- fat_attr_register$data[fat_attr_register$data$code == featureAttrValue,]
469+
if(nrow(reg_item)>0){
470+
val$code = featureAttrValue
471+
val$label = if(!is.na(reg_item[1L,"label"])) reg_item[1L,"label"] else ""
472+
val$definition = if(!is.na(reg_item[1L, "definition"])) reg_item[1L, "definition"] else ""
473+
}
474+
}
475+
fat$listedValue[[length(fat$listedValue)+1]] = val
476+
}
477+
}
478+
}else{
479+
config$logger.warn(sprintf("Skip listing values for feature Attribute '%s'...", featureAttrName))
480+
}
481+
482+
#add primitive type + data type (attribute or variable) as valueType
483+
fat_type <- if(build_catalog_from_features & !is.null(featureAttrValues[1])){
484+
switch(class(featureAttrValues[1])[1],
485+
"integer" = "xsd:int",
486+
"numeric" = "xsd:decimal",
487+
"character" = "xsd:string",
488+
"logical" = "xsd:boolean",
489+
"Date" = "xsd:date",
490+
"POSIXct" = "xsd:datetime",
491+
"sfc_POINT" = "gml:PointPropertyType",
492+
"sfc_MULTIPOINT" = "gml:MultiPointPropertyType",
493+
"sfc_LINESTRING" = "gml:LineStringPropertyType",
494+
"sfc_MULTILINESTRING" = "gml:MultiLineStringPropertyType",
495+
"sfc_POLYGON" = "gml:PolygonPropertyType",
496+
"sfc_MULTIPOLYGON" = "gml:MultiPolygonPropertyType"
497+
)
498+
}else{
499+
type = if(!is.null(fto)) fto$getMemberById(featureAttrName)$type else "attribute"
500+
switch(type,
501+
"attribute" = "xsd:string",
502+
"variable" = "xsd:decimal",
503+
type
504+
)
505+
}
506+
config$logger.info(sprintf("Set primitive type '%s' for feature Attribute '%s'...", fat_type, featureAttrName))
507+
fat_generic_type <- if(build_catalog_from_features){
508+
switch(class(featureAttrValues[1])[1],
509+
"integer" = "variable",
510+
"numeric" = "variable",
511+
"attribute"
512+
)
513+
}else{
514+
if(!is.null(fto)) fto$getMemberById(featureAttrName)$type else "attribute"
515+
}
516+
config$logger.info(sprintf("Feature member generic type for '%s': %s", featureAttrName, fat_generic_type))
517+
if(!is.null(fat_attr)) fat_generic_type <- fat_attr$type
518+
fat_type_anchor <- fat_type #ISOAnchor$new(name = fat_type, href = fat_generic_type)
519+
fat$valueType = fat_type_anchor
520+
521+
#add feature attribute as carrierOfCharacteristic
522+
config$logger.info(sprintf("Add carrier of characteristics for feature Attribute '%s'...", featureAttrName))
523+
ft$carrierOfCharacteristics[[length(ft$carrierOfCharacteristics)+1]] = fat
524+
}
525+
526+
project$description$feature_catalogue$featureType = list(ft)
527+
}
316528

317529
#creation
530+
#-----------------------------------------------------------------------------
318531
output = metadataeditr::create_project(
319532
type = "geospatial",
320533
idno = entity$identifiers[["id"]],
@@ -329,6 +542,7 @@ function(action, entity, config){
329542
}
330543

331544
#add resources
545+
#-----------------------------------------------------------------------------
332546
#first remove existing resources
333547
reslist = metadataeditr::resources_list(entity$identifiers[["id"]])
334548
if(reslist$status_code==200){

inst/extdata/workflows/config_metadataeditr_from_gsheets.json

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,13 +10,19 @@
1010
"entities": [
1111
{
1212
"handler": "gsheet",
13-
"source": "https://docs.google.com/spreadsheets/d/1KSc_IrM86GrNpUASCIVfq6L9cmML77iwpcSbliynYZk/edit?gid=1962881097#gid=1962881097"
13+
"source": "https://docs.google.com/spreadsheets/d/1sRB4CcVRmGMYlXXy-pibzYm4c1ldGeV1D9YxOGwIRVA/edit?gid=1962881097#gid=1962881097"
1414
}
1515
],
1616
"contacts" : [
1717
{
1818
"handler": "gsheet",
19-
"source": "https://docs.google.com/spreadsheets/d/1BqlXwA2fKiRuozNAQhBb_PbQVSPTCfl8_Q9rfM8E2ws/edit?usp=sharing"
19+
"source": "https://docs.google.com/spreadsheets/d/1sRB4CcVRmGMYlXXy-pibzYm4c1ldGeV1D9YxOGwIRVA/edit?gid=1809507591#gid=1809507591"
20+
}
21+
],
22+
"dictionary" : [
23+
{
24+
"handler": "gsheet",
25+
"source": "https://docs.google.com/spreadsheets/d/1sRB4CcVRmGMYlXXy-pibzYm4c1ldGeV1D9YxOGwIRVA/edit?gid=227860508#gid=227860508"
2026
}
2127
]
2228
},
@@ -40,6 +46,10 @@
4046
"id": "geometa-create-iso-19115",
4147
"run": true
4248
},
49+
{
50+
"id": "geometa-create-iso-19110",
51+
"run": true
52+
},
4353
{
4454
"id": "metadataeditr-create-project",
4555
"run": true

0 commit comments

Comments
 (0)