Skip to content

Commit 9d070ad

Browse files
committed
Get time coverage from dataset metadata
1 parent 9b4dcd4 commit 9d070ad

File tree

6 files changed

+60
-50
lines changed

6 files changed

+60
-50
lines changed

docs/configuring-datasets.md

Lines changed: 12 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -22,9 +22,9 @@ as a function of time, stored in a [CSV file][csv].
2222

2323
```
2424
#time,value
25-
2018-01-01T00:00:00Z,0
26-
2018-01-01T00:00:01Z,1
27-
2018-01-01T00:00:02Z,2
25+
2018-01-01,0
26+
2018-01-02,1
27+
2018-01-03,2
2828
```
2929

3030
We will need to provide an FDML file that describes this dataset.
@@ -170,15 +170,16 @@ implemented by the `latis.time.Time` class.
170170
Finally, we need to add metadata for our dataset. HAPI requires that
171171
we define the following:
172172

173+
- time coverage
173174
- units for all variables
174-
- coverage for time variables
175175

176176
This is how we specify the required metadata in FDML:
177177

178178
```xml
179179
<?xml version="1.0" encoding="UTF-8"?>
180180

181181
<dataset id="simple_dataset"
182+
temporalCoverage="2018-01-01/2018-01-03"
182183
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
183184
xsi:noNamespaceSchemaLocation="http://latis-data.io/schemas/1.0/fdml-with-text-adapter.xsd">
184185

@@ -192,21 +193,22 @@ This is how we specify the required metadata in FDML:
192193
<scalar id="time"
193194
type="string"
194195
class="latis.time.Time"
195-
units="yyyy-MM-dd'T'hh:mm:ss'Z'"
196-
coverage="2018-01-01T00:00:00Z/2018-01-01T00:00:02Z"/>
196+
units="yyyy-MM-dd"/>
197197
<scalar id="value" type="int" units="units"/>
198198
</function>
199199
</dataset>
200200
```
201201

202-
We can add metadata to the model by adding attributes:
202+
We can add metadata to the dataset by adding attributes:
203203

204+
- `temporalCoverage` : Adds time coverage (specified as `<start>/<stop>`)
205+
- The `start` and `stop` times must be in ISO 8601 format. If the `stop`
206+
time is not present, the current date will be used.
204207
- `units` : Adds units
205208
- For times represented by formatted text, this is a [time format
206-
string][java-sdf]. This is so LaTiS can read and convert times.
209+
string][java-dtf]. This is so LaTiS can read and convert times.
207210
The server will report "UTC" as the units as required by the HAPI
208211
specification.
209-
- `coverage` : Adds coverage (specified as `<start>/<stop>`)
210212

211213
The snippet above is a complete FDML file that describes the simple
212214
CSV data. Both the FDML and the CSV files are available in the
@@ -233,7 +235,7 @@ If the FDML file is invalid, you'll get a message with the error.
233235
[fdm]: https://github.com/latis-data/latis/wiki/LaTiS-Data-Model
234236
[fdml]: ../examples/fdml/simple_dataset.fdml
235237
[hapi-spec]: https://github.com/hapi-server/data-specification/blob/master/hapi-2.1.0/HAPI-data-access-spec-2.1.0.md
236-
[java-sdf]: https://docs.oracle.com/en/java/javase/11/docs/api/java.base/java/text/SimpleDateFormat.html
238+
[java-dtf]: https://docs.oracle.com/en/java/javase/17/docs/api/java.base/java/time/format/DateTimeFormatter.html
237239
[latis-3]: https://github.com/latis-data/latis3
238240
[readme]: ../README.md
239241
[releases]: https://github.com/lasp/hapi-server/releases

examples/fdml/simple_dataset.csv

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,3 @@
1-
2018-01-01T00:00:00Z,0
2-
2018-01-01T00:00:01Z,1
3-
2018-01-01T00:00:02Z,2
1+
2018-01-01,0
2+
2018-01-02,1
3+
2018-01-03,2

examples/fdml/simple_dataset.fdml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
<?xml version="1.0" encoding="UTF-8"?>
22

33
<dataset id="simple_dataset"
4+
temporalCoverage="2018-01-01/2018-01-03"
45
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
56
xsi:noNamespaceSchemaLocation="http://latis-data.io/schemas/1.0/fdml-with-text-adapter.xsd">
67

@@ -14,8 +15,7 @@
1415
<scalar id="time"
1516
type="string"
1617
class="latis.time.Time"
17-
units="yyyy-MM-dd'T'hh:mm:ss'Z'"
18-
coverage="2018-01-01T00:00:00Z/2018-01-01T00:00:02Z"/>
18+
units="yyyy-MM-dd"/>
1919
<scalar id="value" type="int" units="units"/>
2020
</function>
2121
</dataset>

src/main/scala/latis/service/hapi/Latis3Interpreter.scala

Lines changed: 3 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -126,15 +126,11 @@ class Latis3Interpreter(catalog: Catalog) extends HapiInterpreter[IO] {
126126
NonEmptyList.fromList(params),
127127
UnsupportedDataset("")
128128
)
129-
tFmt <- Either.fromOption(
130-
tVar.metadata.getProperty("units"),
131-
MetadataError("Time parameter missing metadata property 'units'")
132-
)
133129
tCov <- Either.fromOption(
134-
tVar.metadata.getProperty("coverage"),
135-
MetadataError("Time parameter missing metadata property 'coverage'")
130+
ds.metadata.getProperty("temporalCoverage"),
131+
MetadataError("Dataset missing metadata property 'temporalCoverage'")
136132
)
137-
tCovP <- parseCoverage(tCov, tFmt)
133+
tCovP <- parseCoverage(tCov)
138134
} yield Metadata(
139135
psNel,
140136
tCovP._1,
Lines changed: 31 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -1,34 +1,43 @@
11
package latis.util
22

3-
import cats.implicits._
3+
import cats.syntax.all._
44

55
import latis.service.hapi._
66
import latis.time.TimeFormat
7-
import latis.time.TimeScale
8-
import latis.units.UnitConverter
97

108
object HapiUtils {
119

12-
/** Convert a time value to a HAPI time given its original units. */
13-
def toHapiTime(fmt: String, value: String): Either[InfoError, String] = for {
14-
fromTime <- TimeFormat.fromExpression(fmt)
15-
.flatMap(_.parse(value).map(_.toDouble))
16-
.orElse(Either.catchNonFatal(value.toDouble))
17-
.leftMap(_ => MetadataError("Unsupported time units"))
18-
scale <- TimeScale.fromExpression(fmt)
19-
.leftMap(_ => MetadataError("Unsupported time units"))
20-
converter = UnitConverter(scale, TimeScale.Default)
21-
toTime = converter.convert(fromTime).toLong
22-
} yield TimeFormat.formatIso(toTime)
23-
24-
/** Parse coverage metadata from FDML and convert to HAPI time. */
25-
def parseCoverage(coverage: String, fmt: String): Either[InfoError, (String, String)] = {
10+
/**
11+
* Parse temporalCoverage from Dataset metadata to be used for HAPI info.
12+
*
13+
* The coverage is expected to be "/" separated ISO 8601 times. These will
14+
* be converted to the form yyyy-MM-ddZ for the HAPI info response. A coverage
15+
* without a value after the "/" will use the current date as the end time.
16+
*/
17+
def parseCoverage(coverage: String): Either[InfoError, (String, String)] = {
2618
coverage.split('/').toList match {
27-
case s :: e :: Nil if s.nonEmpty && e.nonEmpty => for {
28-
sF <- toHapiTime(fmt, s)
29-
eF <- toHapiTime(fmt, e)
30-
} yield (sF, eF)
31-
case _ => MetadataError("Invalid coverage").asLeft
19+
case s :: e :: Nil if s.nonEmpty && e.nonEmpty =>
20+
(for {
21+
targetFomat <- TimeFormat.fromExpression("yyyy-MM-dd'Z'")
22+
startMillis <- TimeFormat.parseIso(s)
23+
endMillis <- TimeFormat.parseIso(e)
24+
startDate = targetFomat.format(startMillis)
25+
endDate = targetFomat.format(endMillis)
26+
} yield (startDate, endDate)).leftMap { _ =>
27+
MetadataError(s"Invalid temporalCoverage: $coverage")
28+
}
29+
// Replace open end (e.g. "2000-01-01/") with current date
30+
case s :: Nil if s.nonEmpty && coverage.endsWith("/") =>
31+
(for {
32+
targetFomat <- TimeFormat.fromExpression("yyyy-MM-dd'Z'")
33+
startMillis <- TimeFormat.parseIso(s)
34+
endMillis = System.currentTimeMillis() //TODO: beware side effect
35+
startDate = targetFomat.format(startMillis)
36+
endDate = targetFomat.format(endMillis)
37+
} yield (startDate, endDate)).leftMap { _ =>
38+
MetadataError(s"Invalid temporalCoverage: $coverage")
39+
}
40+
case _ => MetadataError(s"Invalid temporalCoverage: $coverage").asLeft
3241
}
3342
}
3443
}

src/test/scala/latis/service/hapi/DataServiceSuite.scala

Lines changed: 9 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -29,14 +29,13 @@ import latis.util.Identifier._
2929

3030
class DataServiceSuite extends CatsEffectSuite {
3131

32-
/** Build a simple test DataService[IO] with a time -> int dataset using the Latis3Interpreter*/
32+
/** Build a simple test DataService[IO] with a time -> int dataset using the Latis3Interpreter */
3333
private lazy val dataset = (for {
3434
time <- Time.fromMetadata(
3535
Metadata(
3636
"id" -> "time",
3737
"type" -> "string",
38-
"units" -> "yyyy-MM-dd",
39-
"coverage" -> "2000-01-01/2000-01-05"
38+
"units" -> "yyyy-MM-dd"
4039
)
4140
)
4241
disp <- Scalar.fromMetadata(
@@ -54,7 +53,11 @@ class DataServiceSuite extends CatsEffectSuite {
5453
Sample(DomainData("2000-01-04"), RangeData(2)),
5554
Sample(DomainData("2000-01-05"), RangeData(0)),
5655
))
57-
dataset = new MemoizedDataset(Metadata("id" -> "testdataset"), model, data)
56+
md = Metadata(
57+
"id" -> "testdataset",
58+
"temporalCoverage" -> "2000-01-01/2000-01-05"
59+
)
60+
dataset = new MemoizedDataset(md, model, data)
5861
} yield dataset).fold(err => fail(err.message), identity)
5962
private lazy val latisInterp = new Latis3Interpreter(Catalog(dataset))
6063
private lazy val dataService = new DataService[IO](latisInterp).service
@@ -261,8 +264,8 @@ class DataServiceSuite extends CatsEffectSuite {
261264
("fill", Json.Null),
262265
),
263266
)),
264-
("startDate", Json.fromString("2000-01-01T00:00:00.000Z")),
265-
("stopDate", Json.fromString("2000-01-05T00:00:00.000Z")),
267+
("startDate", Json.fromString("2000-01-01Z")),
268+
("stopDate", Json.fromString("2000-01-05Z")),
266269
("format", Json.fromString("json")),
267270
("data", Json.arr(
268271
Json.arr(Json.fromString("2000-01-01T00:00:00.000Z"), Json.fromInt(1)),

0 commit comments

Comments
 (0)