diff --git a/CODE_OF_CONDUCT.md b/CODE_OF_CONDUCT.md index aaf77a3775e86614190d55088b21a86a76faa373..ce3bee95b1f20cb2d14f1dd3a009cc91d2475af9 100644 --- a/CODE_OF_CONDUCT.md +++ b/CODE_OF_CONDUCT.md @@ -3,4 +3,4 @@ All contributions to and interactions surrounding this project will abide by the [USGS Code of Scientific Conduct][1]. -[1]: https://www.usgs.gov/about/organization/science-support/office-science-quality-and-integrity/code-scientific-conduct +[1]: https://www.usgs.gov/office-of-science-quality-and-integrity/fundamental-science-practices diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 13b639a18358d12bbe5ca7633a95d27ea47e0cad..677ba76f9a3905b72323889a3a0de7bc62e8bb89 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -11,5 +11,5 @@ into the project you can [fork this repository][2] and [submit a merge request][3] for review. [1]: https://code.usgs.gov/ghsc/nshmp/nshmp-haz/-/issues -[2]: https://docs.gitlab.com/ee/gitlab-basics/fork-project.html -[3]: https://docs.gitlab.com/ee/gitlab-basics/add-merge-request.html +[2]: https://docs.gitlab.com/ee/user/project/repository/forking_workflow.html#creating-a-fork +[3]: https://docs.gitlab.com/ee/user/project/merge_requests/creating_merge_requests.html diff --git a/build.gradle b/build.gradle index d501fb01b2424d06d35114748b54e062a5afa56f..c3b7e91b1a32ed3db5170289cb249e73cdcd848e 100644 --- a/build.gradle +++ b/build.gradle @@ -1,32 +1,3 @@ -/* - * In order to build and run services locally, - * one needs to include the following directorie(s) in the root project (they - * are ignored by git): - * - * models/ - * ak/ - * 2007/ - * wus/ - * 2008/ - * 2014/ - * 2014b/ - * 2018/ - * ceus/ - * 2008/ - * 2014/ - * 2018/ - * - * ...with each 'year' directory being an alias to the corresponding - * git repository. One only needs to include the model they will run in the services. - * - * Then build the JAR file: ./gradlew assemble - * - * One can then specify the model to run in the services: - * java -jar build/libs/nshmp-haz.jar --models=/path/to/models - * - * Where <MODEL> is one of the model enums in nshmp.www.Model - */ - plugins { id "application" id "com.diffplug.spotless" version "${spotlessVersion}" diff --git a/etc/nshm/wus-ceus-attenuation-boundary-2023.geojson b/etc/nshm/wus-ceus-attenuation-boundary-2023.geojson new file mode 100644 index 0000000000000000000000000000000000000000..a69f5638ce724f86f93f6c48f068855d871f2c95 --- /dev/null +++ b/etc/nshm/wus-ceus-attenuation-boundary-2023.geojson @@ -0,0 +1,57 @@ +{ + "type": "Feature", + "geometry": { + "type": "LineString", + "coordinates": [ + [-113.348, 48.997], + [-112.200, 47.550], + [-110.600, 46.850], + [-110.500, 46.450], + [-110.350, 45.750], + [-110.000, 45.500], + [-109.900, 44.900], + [-110.050, 44.150], + [-110.500, 43.000], + [-110.500, 42.180], + [-110.750, 41.000], + [-110.200, 40.000], + [-111.350, 39.550], + [-112.250, 38.000], + [-112.450, 37.000], + [-112.300, 36.200], + [-112.300, 35.700], + [-112.300, 35.250], + [-111.000, 34.500], + [-110.000, 34.400], + [-109.000, 34.250], + [-108.000, 34.600], + [-107.450, 35.000], + [-107.450, 36.500], + [-108.200, 37.000], + [-108.250, 38.000], + [-108.200, 38.750], + [-108.000, 39.100], + [-107.500, 39.550], + [-107.000, 39.650], + [-106.500, 39.800], + [-106.000, 39.850], + [-105.400, 39.800], + [-104.800, 38.450], + [-105.100, 37.900], + [-104.500, 37.100], + [-104.600, 36.950], + [-105.120, 36.810], + [-105.170, 36.590], + [-105.300, 35.950], + [-105.250, 34.450], + [-105.550, 33.850], + [-105.350, 33.250], + [-104.500, 31.700], + [-102.361, 29.828] + ] + }, + "properties": { + "stroke": "#FC4445", + "title": "WUS-CEUS Attenuation Boundary (2023)" + } +} diff --git a/etc/peer/models/Set1-Case1/model-info.json b/etc/peer/models/Set1-Case1/model-info.json index 3b829666c543624ee777c66c48f4f407b1e4b3c6..7df15efda610e7d8193a2a1f96fd8cb44a6414a2 100644 --- a/etc/peer/models/Set1-Case1/model-info.json +++ b/etc/peer/models/Set1-Case1/model-info.json @@ -1,4 +1,10 @@ { "name": "PEER Set1-Case1", - "site-class-vs30": { "BC": 760 } + "site-class-vs30": { "BC": 760 }, + "bounds": { + "min-latitude": 36.0, + "min-longitude": -124.0, + "max-latitude": 40.0, + "max-longitude": -118.0 + } } diff --git a/etc/peer/models/Set1-Case10-fast/model-info.json b/etc/peer/models/Set1-Case10-fast/model-info.json index 694ee43da3ff65e65d7be33b0f516302e9107ed2..c5fb0564afaa886d3c648c3a9697ef1de318d4c4 100644 --- a/etc/peer/models/Set1-Case10-fast/model-info.json +++ b/etc/peer/models/Set1-Case10-fast/model-info.json @@ -1,4 +1,10 @@ { "name": "PEER Set1-Case10-fast", - "site-class-vs30": { "BC": 760 } + "site-class-vs30": { "BC": 760 }, + "bounds": { + "min-latitude": 36.0, + "min-longitude": -124.0, + "max-latitude": 40.0, + "max-longitude": -118.0 + } } diff --git a/etc/peer/models/Set1-Case10/model-info.json b/etc/peer/models/Set1-Case10/model-info.json index de753fa4c1575fc4f90fd8724a0fad0cec39ef57..fe35bed24b011a14d8cde17a72f315dc1dd720d2 100644 --- a/etc/peer/models/Set1-Case10/model-info.json +++ b/etc/peer/models/Set1-Case10/model-info.json @@ -1,4 +1,10 @@ { "name": "PEER Set1-Case10", - "site-class-vs30": { "BC": 760 } + "site-class-vs30": { "BC": 760 }, + "bounds": { + "min-latitude": 36.0, + "min-longitude": -124.0, + "max-latitude": 40.0, + "max-longitude": -118.0 + } } diff --git a/etc/peer/models/Set1-Case11-fast/model-info.json b/etc/peer/models/Set1-Case11-fast/model-info.json index 2dacce72ac9a11ebb82894fcf6227a1f661e514f..cfd96ad84b0779309aa4d1585935ca3c3445d603 100644 --- a/etc/peer/models/Set1-Case11-fast/model-info.json +++ b/etc/peer/models/Set1-Case11-fast/model-info.json @@ -1,4 +1,10 @@ { "name": "PEER Set1-Case11-fast", - "site-class-vs30": { "BC": 760 } + "site-class-vs30": { "BC": 760 }, + "bounds": { + "min-latitude": 36.0, + "min-longitude": -124.0, + "max-latitude": 40.0, + "max-longitude": -118.0 + } } diff --git a/etc/peer/models/Set1-Case11/model-info.json b/etc/peer/models/Set1-Case11/model-info.json index 409dfa0b57238c2dcb29a9c0579000e7d637ac88..448442e2f2a14b9ab8f7306809ade2e4d5e6af62 100644 --- a/etc/peer/models/Set1-Case11/model-info.json +++ b/etc/peer/models/Set1-Case11/model-info.json @@ -1,4 +1,10 @@ { "name": "PEER Set1-Case11", - "site-class-vs30": { "BC": 760 } + "site-class-vs30": { "BC": 760 }, + "bounds": { + "min-latitude": 36.0, + "min-longitude": -124.0, + "max-latitude": 40.0, + "max-longitude": -118.0 + } } diff --git a/etc/peer/models/Set1-Case2-fast/model-info.json b/etc/peer/models/Set1-Case2-fast/model-info.json index 9370e654df21f5ee1d52871c0ce6076c0ac67cba..3a9a2be596ceafaf0a21afdd2abbab11ed4482be 100644 --- a/etc/peer/models/Set1-Case2-fast/model-info.json +++ b/etc/peer/models/Set1-Case2-fast/model-info.json @@ -1,4 +1,10 @@ { "name": "PEER Set1-Case2-fast", - "site-class-vs30": { "BC": 760 } + "site-class-vs30": { "BC": 760 }, + "bounds": { + "min-latitude": 36.0, + "min-longitude": -124.0, + "max-latitude": 40.0, + "max-longitude": -118.0 + } } diff --git a/etc/peer/models/Set1-Case2/model-info.json b/etc/peer/models/Set1-Case2/model-info.json index b3d26fbdd80d5cf69664e3cb0e555749f0ec2a80..70bc644a33064e124e67bd11fc5f79ceebf0abc1 100644 --- a/etc/peer/models/Set1-Case2/model-info.json +++ b/etc/peer/models/Set1-Case2/model-info.json @@ -1,4 +1,10 @@ { "name": "PEER Set1-Case2", - "site-class-vs30": { "BC": 760 } + "site-class-vs30": { "BC": 760 }, + "bounds": { + "min-latitude": 36.0, + "min-longitude": -124.0, + "max-latitude": 40.0, + "max-longitude": -118.0 + } } diff --git a/etc/peer/models/Set1-Case3-fast/model-info.json b/etc/peer/models/Set1-Case3-fast/model-info.json index d4eeae0b2fd3582181b117346de1e3f5d7ae6d8d..a89a4123a6b3ea15496eb853ca585c0579b632c1 100644 --- a/etc/peer/models/Set1-Case3-fast/model-info.json +++ b/etc/peer/models/Set1-Case3-fast/model-info.json @@ -1,4 +1,10 @@ { "name": "PEER Set1-Case3-fast", - "site-class-vs30": { "BC": 760 } + "site-class-vs30": { "BC": 760 }, + "bounds": { + "min-latitude": 36.0, + "min-longitude": -124.0, + "max-latitude": 40.0, + "max-longitude": -118.0 + } } diff --git a/etc/peer/models/Set1-Case3/model-info.json b/etc/peer/models/Set1-Case3/model-info.json index d4eeae0b2fd3582181b117346de1e3f5d7ae6d8d..a89a4123a6b3ea15496eb853ca585c0579b632c1 100644 --- a/etc/peer/models/Set1-Case3/model-info.json +++ b/etc/peer/models/Set1-Case3/model-info.json @@ -1,4 +1,10 @@ { "name": "PEER Set1-Case3-fast", - "site-class-vs30": { "BC": 760 } + "site-class-vs30": { "BC": 760 }, + "bounds": { + "min-latitude": 36.0, + "min-longitude": -124.0, + "max-latitude": 40.0, + "max-longitude": -118.0 + } } diff --git a/etc/peer/models/Set1-Case4-fast/model-info.json b/etc/peer/models/Set1-Case4-fast/model-info.json index 10ef01740fc44dacf402246d3a9c7c5cff1214d9..e6d0a7318a89120c9a0a9f0b81772c74e9158a34 100644 --- a/etc/peer/models/Set1-Case4-fast/model-info.json +++ b/etc/peer/models/Set1-Case4-fast/model-info.json @@ -1,4 +1,10 @@ { "name": "PEER Set1-Case4-fast", - "site-class-vs30": { "BC": 760 } + "site-class-vs30": { "BC": 760 }, + "bounds": { + "min-latitude": 36.0, + "min-longitude": -124.0, + "max-latitude": 40.0, + "max-longitude": -118.0 + } } diff --git a/etc/peer/models/Set1-Case4/model-info.json b/etc/peer/models/Set1-Case4/model-info.json index c21277c9ca57f672a405432eb535a17ab14e6fbf..f36badfadf82b8ea17f7619b1144219b800dba97 100644 --- a/etc/peer/models/Set1-Case4/model-info.json +++ b/etc/peer/models/Set1-Case4/model-info.json @@ -1,4 +1,10 @@ { "name": "PEER Set1-Case4", - "site-class-vs30": { "BC": 760 } + "site-class-vs30": { "BC": 760 }, + "bounds": { + "min-latitude": 36.0, + "min-longitude": -124.0, + "max-latitude": 40.0, + "max-longitude": -118.0 + } } diff --git a/etc/peer/models/Set1-Case5-fast/model-info.json b/etc/peer/models/Set1-Case5-fast/model-info.json index 853a2a13628992c654794ae55446e6252bdd8848..26f0d06516538ee3ae220071cc1c1316f2857f52 100644 --- a/etc/peer/models/Set1-Case5-fast/model-info.json +++ b/etc/peer/models/Set1-Case5-fast/model-info.json @@ -1,4 +1,10 @@ { "name": "PEER Set1-Case5-fast", - "site-class-vs30": { "BC": 760 } + "site-class-vs30": { "BC": 760 }, + "bounds": { + "min-latitude": 36.0, + "min-longitude": -124.0, + "max-latitude": 40.0, + "max-longitude": -118.0 + } } diff --git a/etc/peer/models/Set1-Case5/model-info.json b/etc/peer/models/Set1-Case5/model-info.json index 853a2a13628992c654794ae55446e6252bdd8848..26f0d06516538ee3ae220071cc1c1316f2857f52 100644 --- a/etc/peer/models/Set1-Case5/model-info.json +++ b/etc/peer/models/Set1-Case5/model-info.json @@ -1,4 +1,10 @@ { "name": "PEER Set1-Case5-fast", - "site-class-vs30": { "BC": 760 } + "site-class-vs30": { "BC": 760 }, + "bounds": { + "min-latitude": 36.0, + "min-longitude": -124.0, + "max-latitude": 40.0, + "max-longitude": -118.0 + } } diff --git a/etc/peer/models/Set1-Case6-fast/model-info.json b/etc/peer/models/Set1-Case6-fast/model-info.json index 796efa335615b9d152f54fe86af2601d2215bfc9..6d30fa7484831cbfe320bf8fabba3de6a06403bc 100644 --- a/etc/peer/models/Set1-Case6-fast/model-info.json +++ b/etc/peer/models/Set1-Case6-fast/model-info.json @@ -1,4 +1,10 @@ { "name": "PEER Set1-Case6-fast", - "site-class-vs30": { "BC": 760 } + "site-class-vs30": { "BC": 760 }, + "bounds": { + "min-latitude": 36.0, + "min-longitude": -124.0, + "max-latitude": 40.0, + "max-longitude": -118.0 + } } diff --git a/etc/peer/models/Set1-Case6/model-info.json b/etc/peer/models/Set1-Case6/model-info.json index 796efa335615b9d152f54fe86af2601d2215bfc9..6d30fa7484831cbfe320bf8fabba3de6a06403bc 100644 --- a/etc/peer/models/Set1-Case6/model-info.json +++ b/etc/peer/models/Set1-Case6/model-info.json @@ -1,4 +1,10 @@ { "name": "PEER Set1-Case6-fast", - "site-class-vs30": { "BC": 760 } + "site-class-vs30": { "BC": 760 }, + "bounds": { + "min-latitude": 36.0, + "min-longitude": -124.0, + "max-latitude": 40.0, + "max-longitude": -118.0 + } } diff --git a/etc/peer/models/Set1-Case7-fast/model-info.json b/etc/peer/models/Set1-Case7-fast/model-info.json index 89050cd2b33e3960e66cff4fa40bf732b27720e2..89447337c78aa29ced226a5a1cb8137f67f7d8ad 100644 --- a/etc/peer/models/Set1-Case7-fast/model-info.json +++ b/etc/peer/models/Set1-Case7-fast/model-info.json @@ -1,4 +1,10 @@ { "name": "PEER Set1-Case7-fast", - "site-class-vs30": { "BC": 760 } + "site-class-vs30": { "BC": 760 }, + "bounds": { + "min-latitude": 36.0, + "min-longitude": -124.0, + "max-latitude": 40.0, + "max-longitude": -118.0 + } } diff --git a/etc/peer/models/Set1-Case7/model-info.json b/etc/peer/models/Set1-Case7/model-info.json index 89050cd2b33e3960e66cff4fa40bf732b27720e2..89447337c78aa29ced226a5a1cb8137f67f7d8ad 100644 --- a/etc/peer/models/Set1-Case7/model-info.json +++ b/etc/peer/models/Set1-Case7/model-info.json @@ -1,4 +1,10 @@ { "name": "PEER Set1-Case7-fast", - "site-class-vs30": { "BC": 760 } + "site-class-vs30": { "BC": 760 }, + "bounds": { + "min-latitude": 36.0, + "min-longitude": -124.0, + "max-latitude": 40.0, + "max-longitude": -118.0 + } } diff --git a/etc/peer/models/Set1-Case8a/model-info.json b/etc/peer/models/Set1-Case8a/model-info.json index abb6a5ec286edf83e2bbd51a543c5483f2bfe935..bcb8f124a9b76e9f1e5f46e089e00a405c17d3ae 100644 --- a/etc/peer/models/Set1-Case8a/model-info.json +++ b/etc/peer/models/Set1-Case8a/model-info.json @@ -1,4 +1,10 @@ { "name": "PEER Set1-Case8a", - "site-class-vs30": { "BC": 760 } + "site-class-vs30": { "BC": 760 }, + "bounds": { + "min-latitude": 36.0, + "min-longitude": -124.0, + "max-latitude": 40.0, + "max-longitude": -118.0 + } } diff --git a/etc/peer/models/Set1-Case8b/model-info.json b/etc/peer/models/Set1-Case8b/model-info.json index 4aad25644fe75856adb6116af27b6932503981f3..b02d84ea62f5f8329c81774aa0500baee7e912ca 100644 --- a/etc/peer/models/Set1-Case8b/model-info.json +++ b/etc/peer/models/Set1-Case8b/model-info.json @@ -1,4 +1,10 @@ { "name": "PEER Set1-Case8b", - "site-class-vs30": { "BC": 760 } + "site-class-vs30": { "BC": 760 }, + "bounds": { + "min-latitude": 36.0, + "min-longitude": -124.0, + "max-latitude": 40.0, + "max-longitude": -118.0 + } } diff --git a/etc/peer/models/Set1-Case8c/model-info.json b/etc/peer/models/Set1-Case8c/model-info.json index 4aad25644fe75856adb6116af27b6932503981f3..b02d84ea62f5f8329c81774aa0500baee7e912ca 100644 --- a/etc/peer/models/Set1-Case8c/model-info.json +++ b/etc/peer/models/Set1-Case8c/model-info.json @@ -1,4 +1,10 @@ { "name": "PEER Set1-Case8b", - "site-class-vs30": { "BC": 760 } + "site-class-vs30": { "BC": 760 }, + "bounds": { + "min-latitude": 36.0, + "min-longitude": -124.0, + "max-latitude": 40.0, + "max-longitude": -118.0 + } } diff --git a/etc/peer/models/Set2-Case2a-fast/model-info.json b/etc/peer/models/Set2-Case2a-fast/model-info.json index 8cc0e12f4d8c1ebd2010ac401a5ef0ae684b9c4b..f8f95470ce2133dd43b33805bcbcc6df73345afa 100644 --- a/etc/peer/models/Set2-Case2a-fast/model-info.json +++ b/etc/peer/models/Set2-Case2a-fast/model-info.json @@ -1,4 +1,10 @@ { "name": "PEER Set2-Case2a-fast", - "site-class-vs30": { "BC": 760 } + "site-class-vs30": { "BC": 760 }, + "bounds": { + "min-latitude": -5.0, + "min-longitude": -70.0, + "max-latitude": 5.0, + "max-longitude": -50.0 + } } diff --git a/etc/peer/models/Set2-Case2a/model-info.json b/etc/peer/models/Set2-Case2a/model-info.json index 43538f814b659dad721afae06c3908f2e933cbfa..f4e4ce90d67c59ce1eba873085965a330a1d7c23 100644 --- a/etc/peer/models/Set2-Case2a/model-info.json +++ b/etc/peer/models/Set2-Case2a/model-info.json @@ -1,4 +1,10 @@ { "name": "PEER Set2-Case2a", - "site-class-vs30": { "BC": 760 } + "site-class-vs30": { "BC": 760 }, + "bounds": { + "min-latitude": -5.0, + "min-longitude": -70.0, + "max-latitude": 5.0, + "max-longitude": -50.0 + } } diff --git a/etc/peer/models/Set2-Case2b-fast/model-info.json b/etc/peer/models/Set2-Case2b-fast/model-info.json index f6794d3c6f1f7e6a4829d6ab3067b0cc8fa99235..5453bac3d20a3a41e2a1021378d4adcf6eb32107 100644 --- a/etc/peer/models/Set2-Case2b-fast/model-info.json +++ b/etc/peer/models/Set2-Case2b-fast/model-info.json @@ -1,4 +1,10 @@ { "name": "PEER Set2-Case2b-fast", - "site-class-vs30": { "BC": 760 } + "site-class-vs30": { "BC": 760 }, + "bounds": { + "min-latitude": -5.0, + "min-longitude": -70.0, + "max-latitude": 5.0, + "max-longitude": -50.0 + } } diff --git a/etc/peer/models/Set2-Case2b/model-info.json b/etc/peer/models/Set2-Case2b/model-info.json index f6794d3c6f1f7e6a4829d6ab3067b0cc8fa99235..5453bac3d20a3a41e2a1021378d4adcf6eb32107 100644 --- a/etc/peer/models/Set2-Case2b/model-info.json +++ b/etc/peer/models/Set2-Case2b/model-info.json @@ -1,4 +1,10 @@ { "name": "PEER Set2-Case2b-fast", - "site-class-vs30": { "BC": 760 } + "site-class-vs30": { "BC": 760 }, + "bounds": { + "min-latitude": -5.0, + "min-longitude": -70.0, + "max-latitude": 5.0, + "max-longitude": -50.0 + } } diff --git a/etc/peer/models/Set2-Case2c-fast/model-info.json b/etc/peer/models/Set2-Case2c-fast/model-info.json index fb52af86627159752d45eea41d2f1427cc793e4f..6a7b86f90c82246a80086783333a1e307545a595 100644 --- a/etc/peer/models/Set2-Case2c-fast/model-info.json +++ b/etc/peer/models/Set2-Case2c-fast/model-info.json @@ -1,4 +1,10 @@ { "name": "PEER Set2-Case2c-fast", - "site-class-vs30": { "BC": 760 } + "site-class-vs30": { "BC": 760 }, + "bounds": { + "min-latitude": -5.0, + "min-longitude": -70.0, + "max-latitude": 5.0, + "max-longitude": -50.0 + } } diff --git a/etc/peer/models/Set2-Case2c/model-info.json b/etc/peer/models/Set2-Case2c/model-info.json index fb52af86627159752d45eea41d2f1427cc793e4f..6a7b86f90c82246a80086783333a1e307545a595 100644 --- a/etc/peer/models/Set2-Case2c/model-info.json +++ b/etc/peer/models/Set2-Case2c/model-info.json @@ -1,4 +1,10 @@ { "name": "PEER Set2-Case2c-fast", - "site-class-vs30": { "BC": 760 } + "site-class-vs30": { "BC": 760 }, + "bounds": { + "min-latitude": -5.0, + "min-longitude": -70.0, + "max-latitude": 5.0, + "max-longitude": -50.0 + } } diff --git a/etc/peer/models/Set2-Case2d-fast/model-info.json b/etc/peer/models/Set2-Case2d-fast/model-info.json index 3bba48c392296f1d2973a36b2a410224083c3339..5175131265c70c6ca879c7ce3fdbcd605a70cec3 100644 --- a/etc/peer/models/Set2-Case2d-fast/model-info.json +++ b/etc/peer/models/Set2-Case2d-fast/model-info.json @@ -1,4 +1,10 @@ { "name": "PEER Set2-Case2d-fast", - "site-class-vs30": { "BC": 760 } + "site-class-vs30": { "BC": 760 }, + "bounds": { + "min-latitude": -5.0, + "min-longitude": -70.0, + "max-latitude": 5.0, + "max-longitude": -50.0 + } } diff --git a/etc/peer/models/Set2-Case2d/model-info.json b/etc/peer/models/Set2-Case2d/model-info.json index 3bba48c392296f1d2973a36b2a410224083c3339..5175131265c70c6ca879c7ce3fdbcd605a70cec3 100644 --- a/etc/peer/models/Set2-Case2d/model-info.json +++ b/etc/peer/models/Set2-Case2d/model-info.json @@ -1,4 +1,10 @@ { "name": "PEER Set2-Case2d-fast", - "site-class-vs30": { "BC": 760 } + "site-class-vs30": { "BC": 760 }, + "bounds": { + "min-latitude": -5.0, + "min-longitude": -70.0, + "max-latitude": 5.0, + "max-longitude": -50.0 + } } diff --git a/etc/peer/models/Set2-Case3a-fast/model-info.json b/etc/peer/models/Set2-Case3a-fast/model-info.json index 07da7d081f527bcddbb86afad8bce6d35a732d8a..6bb7beb0eb34db76447f69179018880dcaec0ce3 100644 --- a/etc/peer/models/Set2-Case3a-fast/model-info.json +++ b/etc/peer/models/Set2-Case3a-fast/model-info.json @@ -1,4 +1,10 @@ { "name": "PEER Set2-Case3a-fast", - "site-class-vs30": { "BC": 760 } + "site-class-vs30": { "BC": 760 }, + "bounds": { + "min-latitude": -5.0, + "min-longitude": -70.0, + "max-latitude": 5.0, + "max-longitude": -50.0 + } } diff --git a/etc/peer/models/Set2-Case3a/model-info.json b/etc/peer/models/Set2-Case3a/model-info.json index 42cd89f3572129d5036a59c69f9a99bb9e1fcea2..55edbc36e031880f3fd30eb463f826566762c298 100644 --- a/etc/peer/models/Set2-Case3a/model-info.json +++ b/etc/peer/models/Set2-Case3a/model-info.json @@ -1,4 +1,10 @@ { "name": "PEER Set2-Case3a", - "site-class-vs30": { "BC": 760 } + "site-class-vs30": { "BC": 760 }, + "bounds": { + "min-latitude": -5.0, + "min-longitude": -70.0, + "max-latitude": 5.0, + "max-longitude": -50.0 + } } diff --git a/etc/peer/models/Set2-Case3b-fast/model-info.json b/etc/peer/models/Set2-Case3b-fast/model-info.json index bb0c7e5e1656ec429f3a33c96e571e979e697218..d4ae4c5cf05c9f089c906cd7e743115c9c905799 100644 --- a/etc/peer/models/Set2-Case3b-fast/model-info.json +++ b/etc/peer/models/Set2-Case3b-fast/model-info.json @@ -1,4 +1,10 @@ { "name": "PEER Set2-Case3b-fast", - "site-class-vs30": { "BC": 760 } + "site-class-vs30": { "BC": 760 }, + "bounds": { + "min-latitude": -5.0, + "min-longitude": -70.0, + "max-latitude": 5.0, + "max-longitude": -50.0 + } } diff --git a/etc/peer/models/Set2-Case3b/model-info.json b/etc/peer/models/Set2-Case3b/model-info.json index f5443c8314ff7f48069ada7ae4c5276e3e66acdb..df2040827b244b131e9ebb2a1ff1b00f9bda6555 100644 --- a/etc/peer/models/Set2-Case3b/model-info.json +++ b/etc/peer/models/Set2-Case3b/model-info.json @@ -1,4 +1,10 @@ { "name": "PEER Set2-Case3b", - "site-class-vs30": { "BC": 760 } + "site-class-vs30": { "BC": 760 }, + "bounds": { + "min-latitude": -5.0, + "min-longitude": -70.0, + "max-latitude": 5.0, + "max-longitude": -50.0 + } } diff --git a/etc/peer/models/Set2-Case3c-fast/model-info.json b/etc/peer/models/Set2-Case3c-fast/model-info.json index 21c15e1b383847efd844db9c68e2eb947e066eb0..64c155eff5de01189bcfad6d243acf9e69c3d68a 100644 --- a/etc/peer/models/Set2-Case3c-fast/model-info.json +++ b/etc/peer/models/Set2-Case3c-fast/model-info.json @@ -1,4 +1,10 @@ { "name": "PEER Set2-Case3c-fast", - "site-class-vs30": { "BC": 760 } + "site-class-vs30": { "BC": 760 }, + "bounds": { + "min-latitude": -5.0, + "min-longitude": -70.0, + "max-latitude": 5.0, + "max-longitude": -50.0 + } } diff --git a/etc/peer/models/Set2-Case3c/model-info.json b/etc/peer/models/Set2-Case3c/model-info.json index 13a04b53aee8d984dbb0a0960f4430fbd76a00b4..39fa646e2fa6f049a6510b5646d824a8a23f36f6 100644 --- a/etc/peer/models/Set2-Case3c/model-info.json +++ b/etc/peer/models/Set2-Case3c/model-info.json @@ -1,4 +1,10 @@ { "name": "PEER Set2-Case3c", - "site-class-vs30": { "BC": 760 } + "site-class-vs30": { "BC": 760 }, + "bounds": { + "min-latitude": -5.0, + "min-longitude": -70.0, + "max-latitude": 5.0, + "max-longitude": -50.0 + } } diff --git a/etc/peer/models/Set2-Case3d-fast/model-info.json b/etc/peer/models/Set2-Case3d-fast/model-info.json index 0ef2a77cee30d46af9abd639bf8359e9e8e15da1..0df777e6e8743d2fde686db5ba417a0a27784b06 100644 --- a/etc/peer/models/Set2-Case3d-fast/model-info.json +++ b/etc/peer/models/Set2-Case3d-fast/model-info.json @@ -1,4 +1,10 @@ { "name": "PEER Set2-Case3d-fast", - "site-class-vs30": { "BC": 760 } + "site-class-vs30": { "BC": 760 }, + "bounds": { + "min-latitude": -5.0, + "min-longitude": -70.0, + "max-latitude": 5.0, + "max-longitude": -50.0 + } } diff --git a/etc/peer/models/Set2-Case3d/model-info.json b/etc/peer/models/Set2-Case3d/model-info.json index 72e9a02e53a9e22be04fc1ee7bf6bf96b7143f92..70ce5f198a493112e65a4703eff3c9aefa1b57f2 100644 --- a/etc/peer/models/Set2-Case3d/model-info.json +++ b/etc/peer/models/Set2-Case3d/model-info.json @@ -1,4 +1,10 @@ { "name": "PEER Set2-Case3d", - "site-class-vs30": { "BC": 760 } + "site-class-vs30": { "BC": 760 }, + "bounds": { + "min-latitude": -5.0, + "min-longitude": -70.0, + "max-latitude": 5.0, + "max-longitude": -50.0 + } } diff --git a/etc/peer/models/Set2-Case4a-fast/model-info.json b/etc/peer/models/Set2-Case4a-fast/model-info.json index b5430f610d32672ea45f7b77bf0d2ad1a2c8e66c..287e070f0d380440d1fc0a4ade95ec08045e7227 100644 --- a/etc/peer/models/Set2-Case4a-fast/model-info.json +++ b/etc/peer/models/Set2-Case4a-fast/model-info.json @@ -1,4 +1,10 @@ { "name": "PEER Set2-Case4a-fast", - "site-class-vs30": { "BC": 760 } + "site-class-vs30": { "BC": 760 }, + "bounds": { + "min-latitude": -5.0, + "min-longitude": -70.0, + "max-latitude": 5.0, + "max-longitude": -50.0 + } } diff --git a/etc/peer/models/Set2-Case4a/model-info.json b/etc/peer/models/Set2-Case4a/model-info.json index 06323a365edd947ce2fa39e605598b44676e6999..f025d77804101070bfaf5be8cdedbd27d8728344 100644 --- a/etc/peer/models/Set2-Case4a/model-info.json +++ b/etc/peer/models/Set2-Case4a/model-info.json @@ -1,4 +1,10 @@ { "name": "PEER Set2-Case4a", - "site-class-vs30": { "BC": 760 } + "site-class-vs30": { "BC": 760 }, + "bounds": { + "min-latitude": -5.0, + "min-longitude": -70.0, + "max-latitude": 5.0, + "max-longitude": -50.0 + } } diff --git a/etc/peer/models/Set2-Case4b-fast/model-info.json b/etc/peer/models/Set2-Case4b-fast/model-info.json index 76594bccbb5f30831790bedbcb14515cbd7746d1..e2fd9dc5b96c29693de5322c92530cd07b28a065 100644 --- a/etc/peer/models/Set2-Case4b-fast/model-info.json +++ b/etc/peer/models/Set2-Case4b-fast/model-info.json @@ -1,4 +1,10 @@ { "name": "PEER Set2-Case4b-fast", - "site-class-vs30": { "BC": 760 } + "site-class-vs30": { "BC": 760 }, + "bounds": { + "min-latitude": -5.0, + "min-longitude": -70.0, + "max-latitude": 5.0, + "max-longitude": -50.0 + } } diff --git a/etc/peer/models/Set2-Case4b/model-info.json b/etc/peer/models/Set2-Case4b/model-info.json index 67785f30573f4741f608bee6ffb550961047d531..ac2e148bb2e5c1cf6321296e0ec587cad674fa07 100644 --- a/etc/peer/models/Set2-Case4b/model-info.json +++ b/etc/peer/models/Set2-Case4b/model-info.json @@ -1,4 +1,10 @@ { "name": "PEER Set2-Case4b", - "site-class-vs30": { "BC": 760 } + "site-class-vs30": { "BC": 760 }, + "bounds": { + "min-latitude": -5.0, + "min-longitude": -70.0, + "max-latitude": 5.0, + "max-longitude": -50.0 + } } diff --git a/etc/peer/models/Set2-Case5a/model-info.json b/etc/peer/models/Set2-Case5a/model-info.json index b802ee2bdd5ebde3cea1591308347a07dcc95440..014aef2f2dded9fc0c91d681323f216a6f9d2e5c 100644 --- a/etc/peer/models/Set2-Case5a/model-info.json +++ b/etc/peer/models/Set2-Case5a/model-info.json @@ -1,4 +1,10 @@ { "name": "PEER Set2-Case5a", - "site-class-vs30": { "BC": 760 } + "site-class-vs30": { "BC": 760 }, + "bounds": { + "min-latitude": -5.0, + "min-longitude": -70.0, + "max-latitude": 5.0, + "max-longitude": -50.0 + } } diff --git a/etc/peer/models/Set2-Case5b/model-info.json b/etc/peer/models/Set2-Case5b/model-info.json index a452ae4494b6d8fc08d7027dd95ea43ac9c4f550..ebd21d8adeeb365064fe4ba3ce35ab2158f83b2b 100644 --- a/etc/peer/models/Set2-Case5b/model-info.json +++ b/etc/peer/models/Set2-Case5b/model-info.json @@ -1,4 +1,10 @@ { "name": "PEER Set2-Case5b", - "site-class-vs30": { "BC": 760 } + "site-class-vs30": { "BC": 760 }, + "bounds": { + "min-latitude": -5.0, + "min-longitude": -70.0, + "max-latitude": 5.0, + "max-longitude": -50.0 + } } diff --git a/gradle.properties b/gradle.properties index 336a4f52df56f4229dbb3fbfee730f845e55d1b0..3f48835359cf223a3a469c892eb0c628d133a517 100644 --- a/gradle.properties +++ b/gradle.properties @@ -10,7 +10,7 @@ micronautVersion = 3.2.3 micronautRxVersion = 2.1.1 nodePluginVersion = 3.0.1 nodeVersion = 16.3.0 -nshmpLibVersion = 1.1.5 +nshmpLibVersion = 1.1.8 nshmpWsUtilsVersion = 0.3.11 openApiVersion = 4.0.0 shadowVersion = 7.1.2 diff --git a/package-lock.json b/package-lock.json index dc72624ba1f9b5786382c12356add218736ccc4b..fd03b78e351745759edce1f4fbeb016a832b3d8b 100644 --- a/package-lock.json +++ b/package-lock.json @@ -5,7 +5,6 @@ "requires": true, "packages": { "": { - "name": "nshmp-haz", "version": "2.0.0", "devDependencies": { "markdownlint-cli": "^0.31.1", diff --git a/src/main/java/gov/usgs/earthquake/nshmp/HazardMaps.java b/src/main/java/gov/usgs/earthquake/nshmp/HazardMaps.java index b4580ef94e478e0733909b9f055b73b29b8a4229..bb5733e2f5a2bface8609b837c11ce001a6d092e 100644 --- a/src/main/java/gov/usgs/earthquake/nshmp/HazardMaps.java +++ b/src/main/java/gov/usgs/earthquake/nshmp/HazardMaps.java @@ -15,7 +15,6 @@ import java.util.stream.Collectors; import java.util.stream.Stream; import gov.usgs.earthquake.nshmp.data.Interpolator; -import gov.usgs.earthquake.nshmp.internal.Parsing; /** * Utility class to create hazard map datasets from a hazard curve results. @@ -38,7 +37,7 @@ public class HazardMaps { private static final String PROGRAM = HazardMaps.class.getSimpleName(); private static final String VALUE_FMT = "%.8e"; private static final Function<Double, String> VALUE_FORMATTER = - Parsing.formatDoubleFunction(VALUE_FMT); + Text.formatDoubleFunction(VALUE_FMT); private HazardMaps() {} diff --git a/src/main/java/gov/usgs/earthquake/nshmp/www/ServletUtil.java b/src/main/java/gov/usgs/earthquake/nshmp/www/ServletUtil.java index 8eaadb3536dc61b8c90d66af93bed58b041eb2ad..134c57ef8377ded5a6dfa889679214b93797a197 100644 --- a/src/main/java/gov/usgs/earthquake/nshmp/www/ServletUtil.java +++ b/src/main/java/gov/usgs/earthquake/nshmp/www/ServletUtil.java @@ -49,6 +49,7 @@ import jakarta.inject.Singleton; @Singleton public class ServletUtil { + @Deprecated public static final Gson GSON; public static final Gson GSON2; diff --git a/src/main/java/gov/usgs/earthquake/nshmp/www/services/SwaggerController.java b/src/main/java/gov/usgs/earthquake/nshmp/www/SwaggerController.java similarity index 66% rename from src/main/java/gov/usgs/earthquake/nshmp/www/services/SwaggerController.java rename to src/main/java/gov/usgs/earthquake/nshmp/www/SwaggerController.java index cae6aed6c15ff666f4baa26ab71459615a6ce4ff..ad2efec941029e4f8fd21b6298cd8897cc9396cd 100644 --- a/src/main/java/gov/usgs/earthquake/nshmp/www/services/SwaggerController.java +++ b/src/main/java/gov/usgs/earthquake/nshmp/www/SwaggerController.java @@ -1,17 +1,10 @@ -package gov.usgs.earthquake.nshmp.www.services; +package gov.usgs.earthquake.nshmp.www; import java.util.List; -import java.util.Map; -import java.util.logging.Logger; -import java.util.stream.Collectors; import org.slf4j.LoggerFactory; -import gov.usgs.earthquake.nshmp.geo.Location; import gov.usgs.earthquake.nshmp.model.HazardModel; -import gov.usgs.earthquake.nshmp.www.NshmpMicronautServlet; -import gov.usgs.earthquake.nshmp.www.ServletUtil; -import gov.usgs.earthquake.nshmp.www.SwaggerUtils; import io.micronaut.http.HttpRequest; import io.micronaut.http.HttpResponse; @@ -57,13 +50,12 @@ public class SwaggerController { HttpRequest<?> request, HazardModel model) { var openApi = new OpenAPIV3Parser().read("META-INF/swagger/nshmp-haz.yml"); - var bounds = new Bounds(model.bounds()); + var bounds = model.bounds(); SwaggerUtils.addLocationBounds(openApi, bounds.min, bounds.max); var components = openApi.getComponents(); var schemas = components.getSchemas(); SwaggerUtils.siteClassSchema(schemas, List.copyOf(model.siteClasses().keySet())); - SwaggerUtils.imtSchema(schemas, - model.config().hazard.imts.stream().collect(Collectors.toList())); + SwaggerUtils.imtSchema(schemas, List.copyOf(model.config().hazard.imts)); openApi.servers(null); openApi.getInfo().setTitle(model.name() + " Web Services"); @@ -74,18 +66,4 @@ public class SwaggerController { return openApi; } - private static class Bounds { - final Location min; - final Location max; - - Bounds(Map<String, Double> bounds) { - var log = Logger.getAnonymousLogger(); - - bounds.entrySet().forEach(entry -> { - log.info(entry.getKey() + ", " + entry.getValue()); - }); - min = Location.create(bounds.get("min-longitude"), bounds.get("min-latitude")); - max = Location.create(bounds.get("max-longitude"), bounds.get("max-latitude")); - } - } } diff --git a/src/main/java/gov/usgs/earthquake/nshmp/www/hazard/DisaggController.java b/src/main/java/gov/usgs/earthquake/nshmp/www/hazard/DisaggController.java index 3f2fdc3f1256ff8c773eb8adaf0370652dea9c2e..ba7c7c07059aa39cc3795150291521130b7d193f 100644 --- a/src/main/java/gov/usgs/earthquake/nshmp/www/hazard/DisaggController.java +++ b/src/main/java/gov/usgs/earthquake/nshmp/www/hazard/DisaggController.java @@ -35,11 +35,17 @@ import jakarta.inject.Inject; * Micronaut web service controller for disaggregation of probabilistic seismic * hazard. * + * <p>See src/main/resources/application.yml nshmp-haz.model-path for installed + * model. + * + * <p>To run the Micronaut jar file with a model: java -jar + * path/to/nshmp-haz.jar --model=<path/to/model> + * * @author U.S. Geological Survey */ @Tag( - name = "Disaggregation", - description = "USGS NSHMP hazard disaggregation service") + name = DisaggService.NAME, + description = "USGS NSHM hazard disaggregation service") @Controller("/disagg") public class DisaggController { @@ -68,9 +74,9 @@ public class DisaggController { } /** - * @param longitude Longitude in the range - * @param latitude Latitude in the range - * @param vs30 Site Vs30 value in the range [150..3000] m/s. + * @param longitude Longitude in decimal degrees in the range + * @param latitude Latitude in decimal degrees in the range + * @param vs30 Site Vs30 value in the range [150..3000] m/s * @param returnPeriod The return period of the target ground motion, or * intensity measure level (IML), in the range [1..20000] years. * @param imt Optional IMTs at which to compute hazard. If none are supplied, @@ -80,7 +86,8 @@ public class DisaggController { */ @Operation( summary = "Disaggregate hazard at a specified return period", - description = "Returns a hazard disaggregation computed from the installed model") + description = "Returns a hazard disaggregation computed from the installed model", + operationId = "disagg-calc-rp") @ApiResponse( description = "Disaggregation", responseCode = "200", @@ -105,10 +112,7 @@ public class DisaggController { Set<Imt> imts = HazardService.readImts(http); Set<DataType> dataTypes = HazardService.readDataTypes(http); DisaggService.RequestRp request = new DisaggService.RequestRp( - http, - longitude, latitude, vs30, imts, - returnPeriod, - dataTypes); + http, longitude, latitude, vs30, imts, returnPeriod, dataTypes); return DisaggService.getDisaggRp(request); } catch (Exception e) { return ServletUtil.error( @@ -119,14 +123,16 @@ public class DisaggController { } /** - * @param longitude Longitude in the range - * @param latitude Latitude in decimal degrees - * @param vs30 Site Vs30 value in the range [150..3000] m/s. + * @param longitude Longitude in decimal degrees in the range + * @param latitude Latitude in decimal degrees in the range + * @param vs30 Site Vs30 value in the range [150..3000] m/s + * @param imls Mapping of IMTs to disaggregation intensity measure levels * @param out The data types to output */ @Operation( summary = "Disaggregate hazard at specified IMLs", - description = "Returns a hazard disaggregation computed from the installed model") + description = "Returns a hazard disaggregation computed from the installed model", + operationId = "disagg-calc-iml") @ApiResponse( description = "Disaggregation", responseCode = "200", @@ -150,10 +156,7 @@ public class DisaggController { checkArgument(!imtImlMap.isEmpty(), "No IMLs supplied"); Set<DataType> dataTypes = HazardService.readDataTypes(http); DisaggService.RequestIml request = new DisaggService.RequestIml( - http, - longitude, latitude, vs30, - imtImlMap, - dataTypes); + http, longitude, latitude, vs30, imtImlMap, dataTypes); return DisaggService.getDisaggIml(request); } catch (Exception e) { return ServletUtil.error( @@ -163,12 +166,12 @@ public class DisaggController { } } - // For Swagger schema + // Swagger schema private static class DisaggResponseIml extends ResponseBody<RequestIml, Response> {} - // For Swagger schema + // Swagger schema private static class DisaggResponseReturnPeriod extends ResponseBody<RequestRp, Response> {} - // For Swagger schema + // Swagger schema private static class MetadataResponse extends ResponseBody<String, Metadata> {}; } diff --git a/src/main/java/gov/usgs/earthquake/nshmp/www/hazard/DisaggService.java b/src/main/java/gov/usgs/earthquake/nshmp/www/hazard/DisaggService.java index abd46fdc89ac8b523365d3dad427869ed14a5f76..f9b065a6620a1a52eda1c6f7708742c87bff486b 100644 --- a/src/main/java/gov/usgs/earthquake/nshmp/www/hazard/DisaggService.java +++ b/src/main/java/gov/usgs/earthquake/nshmp/www/hazard/DisaggService.java @@ -33,8 +33,8 @@ import gov.usgs.earthquake.nshmp.www.ResponseBody; import gov.usgs.earthquake.nshmp.www.ResponseMetadata; import gov.usgs.earthquake.nshmp.www.ServletUtil; import gov.usgs.earthquake.nshmp.www.ServletUtil.Server; -import gov.usgs.earthquake.nshmp.www.hazard.HazardService.HazardRequest; -import gov.usgs.earthquake.nshmp.www.hazard.HazardService.Metadata; +import gov.usgs.earthquake.nshmp.www.hazard.HazardService.BaseRequest; +import gov.usgs.earthquake.nshmp.www.meta.DoubleParameter; import gov.usgs.earthquake.nshmp.www.meta.Parameter; import io.micronaut.http.HttpRequest; @@ -57,9 +57,10 @@ public final class DisaggService { * OR imt=iml pairs */ - static final String NAME = "Disaggregation Service"; + static final String NAME = "Hazard Disaggregation"; static final Logger LOG = LoggerFactory.getLogger(DisaggService.class); + // TODO range check return periods and imls private static Range<Double> rpRange = Range.closed(1.0, 20000.0); private static Range<Double> imlRange = Range.closed(0.0001, 8.0); @@ -70,7 +71,6 @@ public final class DisaggService { DISAGG_DATA; } - /** HazardController.doGetMetadata() handler. */ static HttpResponse<String> getMetadata(HttpRequest<?> request) { var url = request.getUri().toString(); var usage = new Metadata(ServletUtil.model()); @@ -85,7 +85,6 @@ public final class DisaggService { return HttpResponse.ok(svcResponse); } - /** HazardController.doGetDisaggIml() handler. */ static HttpResponse<String> getDisaggIml(RequestIml request) throws InterruptedException, ExecutionException { var stopwatch = Stopwatch.createStarted(); @@ -106,7 +105,6 @@ public final class DisaggService { return HttpResponse.ok(svcResponse); } - /** HazardController.doGetDisaggRp() handler. */ static HttpResponse<String> getDisaggRp(RequestRp request) throws InterruptedException, ExecutionException { var stopwatch = Stopwatch.createStarted(); @@ -132,7 +130,6 @@ public final class DisaggService { * * If disaggIml, we need to do the calculation for single XySeqs if disaggRp, * we don't know the imls so must compute hazard over the full curve - * */ private static Disaggregation calcDisaggIml(RequestIml request) @@ -204,7 +201,7 @@ public final class DisaggService { return disagg; } - static final class RequestIml extends HazardRequest { + static final class RequestIml extends BaseRequest { final Map<Imt, Double> imls; final Set<DataType> dataTypes; @@ -230,7 +227,7 @@ public final class DisaggService { } } - static final class RequestRp extends HazardRequest { + static final class RequestRp extends BaseRequest { final double returnPeriod; final Set<DataType> dataTypes; final Set<Imt> imts; @@ -266,15 +263,15 @@ public final class DisaggService { } static final class Response { - final Response.Metadata metadata; + final ResponseMetadata metadata; final List<ImtDisagg> disaggs; - Response(Response.Metadata metadata, List<ImtDisagg> disaggs) { + Response(ResponseMetadata metadata, List<ImtDisagg> disaggs) { this.metadata = metadata; this.disaggs = disaggs; } - public Response.Metadata getMetadata() { + public ResponseMetadata getMetadata() { return metadata; } @@ -282,14 +279,14 @@ public final class DisaggService { return disaggs; } - private static final class Metadata { + private static final class ResponseMetadata { final Server server; final String rlabel = "Closest Distance, rRup (km)"; final String mlabel = "Magnitude (Mw)"; final String εlabel = "% Contribution to Hazard"; final Object εbins; - Metadata(Server server, Object εbins) { + ResponseMetadata(Server server, Object εbins) { this.server = server; this.εbins = εbins; } @@ -364,7 +361,7 @@ public final class DisaggService { var server = ServletUtil.serverData(ServletUtil.THREAD_COUNT, timer); return new Response( - new Response.Metadata(server, disagg.εBins()), + new ResponseMetadata(server, disagg.εBins()), disaggs); } } @@ -389,4 +386,33 @@ public final class DisaggService { return data; } } + + private static class Metadata extends HazardService.Metadata { + final DoubleParameter iml; + final DoubleParameter returnPeriod; + + Metadata(HazardModel model) { + super(model); + + iml = new DoubleParameter( + "Intensity Measure Level", + "", + imlRange.lowerEndpoint(), + imlRange.upperEndpoint()); + + returnPeriod = new DoubleParameter( + "Return Period", + "yr", + rpRange.lowerEndpoint(), + rpRange.upperEndpoint()); + } + + public DoubleParameter getIml() { + return iml; + } + + public DoubleParameter getReturnPeriod() { + return returnPeriod; + } + } } diff --git a/src/main/java/gov/usgs/earthquake/nshmp/www/hazard/HazardController.java b/src/main/java/gov/usgs/earthquake/nshmp/www/hazard/HazardController.java index e6680a3a98992e56e5894dd12e451b31b9c92bcb..261b62b69b173fd23e98c07eaab8fc17050737c7 100644 --- a/src/main/java/gov/usgs/earthquake/nshmp/www/hazard/HazardController.java +++ b/src/main/java/gov/usgs/earthquake/nshmp/www/hazard/HazardController.java @@ -29,11 +29,17 @@ import jakarta.inject.Inject; * Micronaut web service controller for probabilistic seismic hazard * calculations. * + * <p>See src/main/resources/application.yml nshmp-haz.model-path for installed + * model. + * + * <p>To run the Micronaut jar file with a model: java -jar + * path/to/nshmp-haz.jar --model=<path/to/model> + * * @author U.S. Geological Survey */ @Tag( - name = "Hazard", - description = "USGS NSHMP hazard calculation service") + name = HazardService.NAME, + description = "USGS NSHM hazard calculation service") @Controller("/hazard") public class HazardController { @@ -41,7 +47,7 @@ public class HazardController { private NshmpMicronautServlet servlet; @Operation( - summary = "Hazard model and service metadata", + summary = "Hazard calculation model and service metadata", description = "Returns details of the installed model and service request parameters", operationId = "hazard-metadata") @ApiResponse( @@ -62,18 +68,19 @@ public class HazardController { } /** - * @param longitude Longitude in decimal degrees - * @param latitude Latitude in decimal degrees - * @param vs30 Site Vs30 value in m/s [150..3000] - * @param truncate Truncate curves at return periods below ~10,000 years - * @param maxdir Apply max-direction scaling + * @param longitude Longitude in decimal degrees in the range + * @param latitude Latitude in decimal degrees in the range + * @param vs30 Site Vs30 value in the range [150..3000] m/s + * @param truncate Truncate curves at return periods below ~10,000 years. + * @param maxdir Apply max-direction scaling. * @param imt Optional IMTs at which to compute hazard. If none are supplied, * then the supported set for the installed model is used. Responses * for numerous IMT's are quite large, on the order of MB. */ @Operation( summary = "Compute probabilisitic hazard at a site", - description = "Returns hazard curves computed from the installed model") + description = "Returns hazard curves computed from the installed model", + operationId = "hazard-calc") @ApiResponse( description = "Hazard curves", responseCode = "200", @@ -97,10 +104,7 @@ public class HazardController { try { Set<Imt> imts = HazardService.readImts(http); HazardService.Request request = new HazardService.Request( - http, - longitude, latitude, vs30, - imts, - truncate, maxdir); + http, longitude, latitude, vs30, imts, truncate, maxdir); return HazardService.getHazard(request); } catch (Exception e) { return ServletUtil.error( @@ -110,9 +114,9 @@ public class HazardController { } } - // For Swagger schemas + // Swagger schema private static class HazardResponse extends ResponseBody<Request, Response> {} - // For Swagger schemas + // Swagger schema private static class MetadataResponse extends ResponseBody<String, Metadata> {}; } diff --git a/src/main/java/gov/usgs/earthquake/nshmp/www/hazard/HazardService.java b/src/main/java/gov/usgs/earthquake/nshmp/www/hazard/HazardService.java index 3f06ebd44b15cc80a8d86eb2a1fbcff0c59165a5..691d27f40ee9189e6062c90ba07e941266951776 100644 --- a/src/main/java/gov/usgs/earthquake/nshmp/www/hazard/HazardService.java +++ b/src/main/java/gov/usgs/earthquake/nshmp/www/hazard/HazardService.java @@ -1,6 +1,5 @@ package gov.usgs.earthquake.nshmp.www.hazard; -import static com.google.common.base.Preconditions.checkState; import static gov.usgs.earthquake.nshmp.calc.HazardExport.curvesBySource; import static gov.usgs.earthquake.nshmp.data.DoubleData.checkInRange; import static gov.usgs.earthquake.nshmp.geo.Coordinates.checkLatitude; @@ -29,7 +28,6 @@ import gov.usgs.earthquake.nshmp.calc.HazardCalcs; import gov.usgs.earthquake.nshmp.calc.Site; import gov.usgs.earthquake.nshmp.data.MutableXySequence; import gov.usgs.earthquake.nshmp.data.XySequence; -import gov.usgs.earthquake.nshmp.geo.Coordinates; import gov.usgs.earthquake.nshmp.geo.Location; import gov.usgs.earthquake.nshmp.gmm.Imt; import gov.usgs.earthquake.nshmp.model.HazardModel; @@ -41,7 +39,7 @@ import gov.usgs.earthquake.nshmp.www.ServletUtil; import gov.usgs.earthquake.nshmp.www.ServletUtil.Server; import gov.usgs.earthquake.nshmp.www.meta.DoubleParameter; import gov.usgs.earthquake.nshmp.www.meta.Parameter; -import gov.usgs.earthquake.nshmp.www.services.SourceServices.SourceModel; +import gov.usgs.earthquake.nshmp.www.source.SourceService.SourceModel; import io.micronaut.http.HttpRequest; import io.micronaut.http.HttpResponse; @@ -56,12 +54,11 @@ import jakarta.inject.Singleton; @Singleton public final class HazardService { - static final String NAME = "Hazard Service"; + static final String NAME = "Hazard Curves"; static final Logger LOG = LoggerFactory.getLogger(HazardService.class); private static final String TOTAL_KEY = "Total"; - /** HazardController.doGetUsage() handler. */ public static HttpResponse<String> getMetadata(HttpRequest<?> request) { var url = request.getUri().toString(); var usage = new Metadata(ServletUtil.model()); @@ -76,7 +73,6 @@ public final class HazardService { return HttpResponse.ok(json); } - /** HazardController.doGetHazard() handler. */ public static HttpResponse<String> getHazard(Request request) throws InterruptedException, ExecutionException { var stopwatch = Stopwatch.createStarted(); @@ -106,7 +102,7 @@ public final class HazardService { * apply truncation and scaling on the client. */ - public static Hazard calcHazard(Request request) + static Hazard calcHazard(Request request) throws InterruptedException, ExecutionException { HazardModel model = ServletUtil.model(); @@ -137,18 +133,17 @@ public final class HazardService { Metadata(HazardModel model) { this.model = new SourceModel(model); - // should get min max from model longitude = new DoubleParameter( "Longitude", "°", - Coordinates.LON_RANGE.lowerEndpoint(), - Coordinates.LON_RANGE.upperEndpoint()); + model.bounds().min.longitude, + model.bounds().max.longitude); latitude = new DoubleParameter( "Latitude", "°", - Coordinates.LAT_RANGE.lowerEndpoint(), - Coordinates.LAT_RANGE.upperEndpoint()); + model.bounds().min.latitude, + model.bounds().max.latitude); vs30 = new DoubleParameter( "Vs30", @@ -174,13 +169,14 @@ public final class HazardService { } } - static class HazardRequest { + /* Base request class for both hazard and disagg. */ + static class BaseRequest { final transient HttpRequest<?> http; final double longitude; final double latitude; final double vs30; - public HazardRequest( + public BaseRequest( HttpRequest<?> http, double longitude, double latitude, @@ -204,7 +200,7 @@ public final class HazardService { } } - static final class Request extends HazardRequest { + static final class Request extends BaseRequest { final boolean truncate; final boolean maxdir; final Set<Imt> imts; @@ -296,12 +292,8 @@ public final class HazardService { } Builder hazard(Hazard hazard) { - // necessary?? - checkState(totalMap == null, "Hazard has already been added to this builder"); - componentMaps = new EnumMap<>(Imt.class); totalMap = new EnumMap<>(Imt.class); - var typeTotalMaps = curvesBySource(hazard); for (var imt : hazard.curves().keySet()) { @@ -322,7 +314,6 @@ public final class HazardService { XySequence.addToMap(type, componentMap, typeTotalMap.get(type)); } } - return this; } @@ -442,7 +433,7 @@ public final class HazardService { .collect(toCollection(() -> EnumSet.noneOf(Imt.class))); } - /* Read the 'imt' query values; can be comma-delimited. */ + /* Read the 'out'put type query values; can be comma-delimited. */ static Set<DataType> readDataTypes(HttpRequest<?> http) { return http.getParameters().getAll("out").stream() .map(s -> s.split(",")) diff --git a/src/main/java/gov/usgs/earthquake/nshmp/www/services/RateController.java b/src/main/java/gov/usgs/earthquake/nshmp/www/services/RateController.java deleted file mode 100644 index 48961477728e95ef794d508ba627eac4311b6696..0000000000000000000000000000000000000000 --- a/src/main/java/gov/usgs/earthquake/nshmp/www/services/RateController.java +++ /dev/null @@ -1,223 +0,0 @@ -package gov.usgs.earthquake.nshmp.www.services; - -import java.util.Optional; - -import gov.usgs.earthquake.nshmp.www.NshmpMicronautServlet; -import gov.usgs.earthquake.nshmp.www.ResponseBody; -import gov.usgs.earthquake.nshmp.www.services.RateService.ProbabilityParameters; -import gov.usgs.earthquake.nshmp.www.services.RateService.Query; -import gov.usgs.earthquake.nshmp.www.services.RateService.RateParameters; -import gov.usgs.earthquake.nshmp.www.services.RateService.RequestData; -import gov.usgs.earthquake.nshmp.www.services.RateService.ResponseData; -import gov.usgs.earthquake.nshmp.www.services.RateService.Service; -import gov.usgs.earthquake.nshmp.www.services.RateService.Usage; - -import io.micronaut.core.annotation.Nullable; -import io.micronaut.http.HttpRequest; -import io.micronaut.http.HttpResponse; -import io.micronaut.http.MediaType; -import io.micronaut.http.annotation.Controller; -import io.micronaut.http.annotation.Get; -import io.micronaut.http.annotation.PathVariable; -import io.micronaut.http.annotation.QueryValue; -import io.swagger.v3.oas.annotations.Operation; -import io.swagger.v3.oas.annotations.media.Content; -import io.swagger.v3.oas.annotations.media.Schema; -import io.swagger.v3.oas.annotations.responses.ApiResponse; -import jakarta.inject.Inject; - -/** - * Micronaut controller for rate and probability services. - * - * @see RateService - * - * @author U.S. Geological Survey - */ -@Controller("/") -public class RateController { - - @Inject - private NshmpMicronautServlet servlet; - - /** - * GET method to compute annual-rate, query based. - * - * @param request The HTTP request - * @param longitude Longitude (in decimal degrees) - * @param latitude Latitude (in decimal degrees) - * @param distance Cutoff distance (in km) ([0.01, 1000]) - */ - @Operation( - summary = "Compute earthquake annual-rates", - description = "Compute incremental earthquake annual-rates at a location", - operationId = "rate_doGetRate", - tags = { "Rate Service" }) - @ApiResponse( - description = "Earthquake annual-rates service metadata", - responseCode = "20x", - content = { - @Content( - schema = @Schema(implementation = RateMetadataResponse.class)) - }) - @ApiResponse( - description = "Earthquake annual-rates calculation response", - responseCode = "200", - content = { - @Content( - schema = @Schema(implementation = CalcResponse.class)) - }) - @Get( - uri = "/rate{?longitude,latitude,distance}", - produces = MediaType.APPLICATION_JSON) - public HttpResponse<String> doGetRate( - HttpRequest<?> request, - @Schema( - required = true) @QueryValue @Nullable Double longitude, - @Schema( - required = true) @QueryValue @Nullable Double latitude, - @Schema( - required = true, - minimum = "0.01", - maximum = "1000") @QueryValue @Nullable Double distance) { - var service = Service.RATE; - var query = new Query(service, longitude, latitude, distance, Optional.empty()); - return RateService.handleDoGetCalc(request, query); - } - - /** - * GET method to compute annual-rate, slash based - * - * @param request The HTTP request - * @param longitude Longitude (in decimal degrees) - * @param latitude Latitude (in decimal degrees) - * @param distance Cutoff distance (in km) ([0.01, 1000]) - */ - @Operation( - summary = "Compute earthquake annual-rates", - description = "Compute incremental earthquake annual-rates at a location", - operationId = "rate_doGetRateSlash", - tags = { "Rate Service" }) - @ApiResponse( - description = "Earthquake annual-rates calculation response", - responseCode = "200", - content = @Content( - schema = @Schema(implementation = CalcResponse.class))) - @Get( - uri = "/rate{/longitude}{/latitude}{/distance}", - produces = MediaType.APPLICATION_JSON) - public HttpResponse<String> doGetRateSlash( - HttpRequest<?> request, - @Schema( - required = true) @PathVariable @Nullable Double longitude, - @Schema( - required = true) @PathVariable @Nullable Double latitude, - @Schema( - required = true, - minimum = "0.01", - maximum = "1000") @PathVariable @Nullable Double distance) { - var service = Service.RATE; - var query = new Query(service, longitude, latitude, distance, Optional.empty()); - return RateService.handleDoGetCalc(request, query); - } - - /** - * GET method to compute probability, query based. - * - * @param request The HTTP request - * @param longitude Longitude (in decimal degrees) - * @param latitude Latitude (in decimal degrees) - * @param distance Cutoff distance (in km) ([0.01, 1000]) - * @param timespan Forcast time span (in years) ([1, 10000]) - */ - @Operation( - summary = "Compute earthquake probabilities", - description = "Compute cumulative earthquake probabilities P(M ≥ x) at a location", - operationId = "probability_doGetProbability", - tags = { "Probability Service" }) - @ApiResponse( - description = "Earthquake probabilities service metadata", - responseCode = "20x", - content = { - @Content( - schema = @Schema(implementation = ProbMetadataResponse.class)) - }) - @ApiResponse( - description = "Earthquake probabilities calculation response", - responseCode = "200", - content = { - @Content( - schema = @Schema( - implementation = CalcResponse.class)) - }) - @Get( - uri = "/probability{?longitude,latitude,distance,timespan}", - produces = MediaType.APPLICATION_JSON) - public HttpResponse<String> doGetProbability( - HttpRequest<?> request, - @Schema( - required = true) @QueryValue @Nullable Double longitude, - @Schema( - required = true) @QueryValue @Nullable Double latitude, - @Schema( - required = true, - minimum = "0.01", - maximum = "1000") @QueryValue @Nullable Double distance, - @Schema( - required = true, - minimum = "1", - maximum = "10000") @QueryValue @Nullable Double timespan) { - var service = Service.PROBABILITY; - var query = new Query(service, longitude, latitude, distance, Optional.ofNullable(timespan)); - return RateService.handleDoGetCalc(request, query); - } - - /** - * GET method to compute probability, slash based - * - * @param request The HTTP request - * @param longitude Longitude (in decimal degrees) - * @param latitude Latitude (in decimal degrees) - * @param distance Cutoff distance (in km) ([0.01, 1000]) - * @param timespan Forecast time span (in years) ([1, 10000]) - */ - @Operation( - summary = "Compute earthquake probabilities", - description = "Compute cumulative earthquake probabilities P(M ≥ x) at a location", - operationId = "probability_doGetProbabilitySlash", - tags = { "Probability Service" }) - @ApiResponse( - description = "Earthquake probabilities calculation response", - responseCode = "200", - content = @Content( - schema = @Schema( - implementation = CalcResponse.class))) - @Get( - uri = "/probability{/longitude}{/latitude}{/distance}{/timespan}", - produces = MediaType.APPLICATION_JSON) - public HttpResponse<String> doGetProbabilitySlash( - HttpRequest<?> request, - @Schema( - required = true) @PathVariable @Nullable Double longitude, - @Schema( - required = true) @PathVariable @Nullable Double latitude, - @Schema( - required = true, - minimum = "0.01", - maximum = "1000") @PathVariable @Nullable Double distance, - @Schema( - required = true, - minimum = "1", - maximum = "10000") @PathVariable @Nullable Double timespan) { - var service = Service.PROBABILITY; - var query = new Query(service, longitude, latitude, distance, Optional.ofNullable(timespan)); - return RateService.handleDoGetCalc(request, query); - } - - // Swagger schemas - private static class CalcResponse extends ResponseBody<RequestData, ResponseData> {} - - private static class RateMetadataResponse extends ResponseBody<String, Usage<RateParameters>> {}; - - private static class ProbMetadataResponse extends - ResponseBody<String, Usage<ProbabilityParameters>> {}; -} diff --git a/src/main/java/gov/usgs/earthquake/nshmp/www/services/RateService.java b/src/main/java/gov/usgs/earthquake/nshmp/www/services/RateService.java deleted file mode 100644 index a084a6a0aa74963840b7f4697b54fd34a6f8b64b..0000000000000000000000000000000000000000 --- a/src/main/java/gov/usgs/earthquake/nshmp/www/services/RateService.java +++ /dev/null @@ -1,433 +0,0 @@ -package gov.usgs.earthquake.nshmp.www.services; - -import java.util.ArrayList; -import java.util.List; -import java.util.Optional; -import java.util.concurrent.ExecutionException; -import java.util.stream.Collectors; - -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import com.google.common.base.Stopwatch; -import com.google.common.util.concurrent.ListenableFuture; - -import gov.usgs.earthquake.nshmp.Maths; -import gov.usgs.earthquake.nshmp.calc.CalcConfig; -import gov.usgs.earthquake.nshmp.calc.EqRate; -import gov.usgs.earthquake.nshmp.calc.Site; -import gov.usgs.earthquake.nshmp.geo.Location; -import gov.usgs.earthquake.nshmp.model.HazardModel; -import gov.usgs.earthquake.nshmp.www.HazVersion; -import gov.usgs.earthquake.nshmp.www.ResponseBody; -import gov.usgs.earthquake.nshmp.www.ResponseMetadata; -import gov.usgs.earthquake.nshmp.www.ServicesUtil.Key; -import gov.usgs.earthquake.nshmp.www.ServicesUtil.ServiceQueryData; -import gov.usgs.earthquake.nshmp.www.ServicesUtil.ServiceRequestData; -import gov.usgs.earthquake.nshmp.www.ServletUtil; -import gov.usgs.earthquake.nshmp.www.ServletUtil.Server; -import gov.usgs.earthquake.nshmp.www.WsUtils; -import gov.usgs.earthquake.nshmp.www.meta.DoubleParameter; -import gov.usgs.earthquake.nshmp.www.meta.Metadata.DefaultParameters; - -import io.micronaut.http.HttpRequest; -import io.micronaut.http.HttpResponse; -import jakarta.inject.Singleton; - -/** - * Earthquake probability and rate calculation handler service for - * {@link RateController}. - * - * @author U.S. Geological Survey - */ -@Singleton -public final class RateService { - - static final Logger LOG = LoggerFactory.getLogger(RateService.class); - - /* - * Developer notes: - * - * The RateService is currently single-threaded and does not submit jobs to a - * request queue; see HazardService. However, jobs are placed on a thread in - * the CALC_EXECUTOR thread pool to handle parallel calculation of CEUS and - * WUS models. - */ - - private static final String TOTAL_KEY = "Total"; - - /** - * Handler for {@link RateController#doGetProbability}, - * {@link RateController#doGetProbabilitySlash}, - * {@link RateController#doGetRate}, and {@link RateController#doGetRateSlash} - * - * @param service The service - * @param query The query - * @param urlHelper The url helper - * @return - */ - static HttpResponse<String> handleDoGetCalc(HttpRequest<?> request, Query query) { - var service = query.service; - - try { - - if (query.isNull()) { - return handleDoGetUsage(request, service); - } - - query.checkValues(); - var requestData = new RequestData(query); - var response = processRequest(request, service, requestData); - var svcResponse = ServletUtil.GSON.toJson(response); - return HttpResponse.ok(svcResponse); - } catch (Exception e) { - return ServletUtil.error(LOG, e, service.name, request.getUri().getPath()); - } - } - - static ResponseBody<String, Usage<DefaultParameters>> metadata(HttpRequest<?> request, - Service service) { - var parameters = service == Service.RATE ? new RateParameters() : new ProbabilityParameters(); - var usage = new Usage<DefaultParameters>(service, parameters); - var url = request.getUri().getPath(); - return ResponseBody.<String, Usage<DefaultParameters>> usage() - .name(service.name) - .url(url) - .metadata(new ResponseMetadata(HazVersion.appVersions())) - .request(url) - .response(usage) - .build(); - } - - static ResponseBody<RequestData, ResponseData> processRequest( - HttpRequest<?> request, - Service service, - RequestData data) throws InterruptedException, ExecutionException { - var timer = Stopwatch.createStarted(); - var rates = calc(service, data); - var responseData = new ResponseData(new ServiceResponseMetadata(service, data), rates, timer); - return ResponseBody.<RequestData, ResponseData> success() - .name(service.name) - .request(data) - .metadata(new ResponseMetadata(HazVersion.appVersions())) - .response(responseData) - .url(request.getUri().getPath()) - .build(); - } - - private static EqRate calc(Service service, RequestData data) - throws InterruptedException, ExecutionException { - var location = Location.create(data.longitude, data.latitude); - var site = Site.builder().location(location).build(); - var futureRates = new ArrayList<ListenableFuture<EqRate>>(); - - /* - * Because we need to combine model results, intially calculate incremental - * annual rates and only convert to cumulative probabilities at the end if - * probability service has been called. - */ - - var model = ServletUtil.model(); - var rate = process(service, model, site, data.distance, data.timespan); - futureRates.add(rate); - - var rates = futureRates.stream() - .map((future) -> { - try { - return future.get(); - } catch (InterruptedException | ExecutionException e) { - throw new RuntimeException(e); - } - }).collect(Collectors.toList()) - .toArray(new EqRate[] {}); - - var ratesCombined = EqRate.combine(rates); - - if (service == Service.PROBABILITY) { - ratesCombined = EqRate.toCumulative(ratesCombined); - ratesCombined = EqRate.toPoissonProbability(ratesCombined, data.timespan.get()); - } - - return ratesCombined; - } - - private static HttpResponse<String> handleDoGetUsage(HttpRequest<?> request, Service service) { - var response = metadata(request, service); - var json = ServletUtil.GSON.toJson(response); - return HttpResponse.ok(json); - } - - private static ListenableFuture<EqRate> process( - Service service, - HazardModel model, - Site site, - double distance, - Optional<Double> timespan) { - var configBuilder = CalcConfig.copyOf(model.config()).distance(distance); - if (service == Service.PROBABILITY) { - /* Also sets value format to Poisson probability. */ - configBuilder.timespan(timespan.get()); - } - var config = configBuilder.build(); - var task = EqRate.callable(model, config, site); - return ServletUtil.CALC_EXECUTOR.submit(task); - } - - public static enum Service { - RATE( - "Earthquake Rate Service", - "Compute incremental earthquake annual-rates at a location", - List.of( - "%srate/{longitude}/{latitude}/{distance}", - "%srate?longitude={longitude}&latitude={latitude}&distance={distance}")), - - PROBABILITY( - "Earthquake Probability Service", - "Compute cumulative earthquake probabilities P(M ≥ x) at a location", - List.of( - "%sprobability/{longitude}/{latitude}/{distance}/{timespan}", - "%sprobability?longitude=<double>&latitude=<double>&distance=<double>×pan=<double>")); - - private final String name; - private final String description; - private final List<String> syntax; - - private Service(String name, String description, List<String> syntax) { - this.name = name; - this.description = description; - this.syntax = syntax; - } - } - - public static class Query extends ServiceQueryData { - final Double distance; - final Optional<Double> timespan; - final Service service; - - public Query( - Service service, - Double longitude, - Double latitude, - Double distance, - Optional<Double> timespan) { - super(longitude, latitude); - this.service = service; - this.distance = distance; - this.timespan = timespan; - } - - @Override - public boolean isNull() { - return super.isNull() && distance == null && - ((service == Service.PROBABILITY && timespan.isEmpty()) || service == Service.RATE); - } - - @Override - public void checkValues() { - super.checkValues(); - WsUtils.checkValue(Key.DISTANCE, distance); - if (service == Service.PROBABILITY) { - WsUtils.checkValue(Key.TIMESPAN, timespan.get()); - } - } - } - - static final class RequestData extends ServiceRequestData { - final double distance; - final Optional<Double> timespan; - - RequestData(Query query) { - super(query); - this.distance = query.distance; - this.timespan = query.timespan; - } - - public double getDistance() { - return distance; - } - - public Optional<Double> getTimespan() { - return timespan; - } - } - - private static final class ServiceResponseMetadata { - final double latitude; - final double longitude; - final double distance; - final Double timespan; - - final String xlabel = "Magnitude (Mw)"; - final String ylabel; - - ServiceResponseMetadata(Service service, RequestData request) { - var isProbability = service == Service.PROBABILITY; - this.longitude = request.longitude; - this.latitude = request.latitude; - this.distance = request.distance; - this.ylabel = isProbability ? "Probability" : "Annual Rate (yrâ»Â¹)"; - this.timespan = request.timespan.orElse(null); - } - - public double getLatitude() { - return latitude; - } - - public double getLongitude() { - return longitude; - } - - public double getDistance() { - return distance; - } - - public Double getTimespan() { - return timespan; - } - - public String getXlabel() { - return xlabel; - } - - public String getYLabel() { - return ylabel; - } - } - - static final class ResponseData { - final Server server; - final ServiceResponseMetadata metadata; - final List<Sequence> data; - - ResponseData(ServiceResponseMetadata metadata, EqRate rates, Stopwatch timer) { - server = ServletUtil.serverData(ServletUtil.THREAD_COUNT, timer); - this.metadata = metadata; - this.data = buildSequence(rates); - } - - public Server getServer() { - return server; - } - - public ServiceResponseMetadata getMetadata() { - return metadata; - } - - public List<Sequence> getData() { - return data; - } - - List<Sequence> buildSequence(EqRate rates) { - var sequences = new ArrayList<Sequence>(); - - /* Total mfd. */ - var total = (!rates.totalMfd.isClear()) ? rates.totalMfd.trim() : rates.totalMfd; - var totalOut = new Sequence( - TOTAL_KEY, - total.xValues().boxed().collect(Collectors.toList()), - total.yValues().boxed().collect(Collectors.toList())); - sequences.add(totalOut); - - /* Source type mfds. */ - for (var entry : rates.typeMfds.entrySet()) { - var type = entry.getValue(); - if (type.isClear()) { - continue; - } - type = type.trim(); - var typeOut = new Sequence( - entry.getKey().toString(), - type.xValues().boxed().collect(Collectors.toList()), - type.yValues().boxed().collect(Collectors.toList())); - sequences.add(typeOut); - } - - return List.copyOf(sequences); - } - } - - /* - * Would rather use this a general container for mfds and hazard curves. See - * HazardService.Curve - */ - private static class Sequence { - final String component; - final List<Double> xvalues; - final List<Double> yvalues; - - Sequence(String component, List<Double> xvalues, List<Double> yvalues) { - this.component = component; - this.xvalues = xvalues; - this.yvalues = yvalues; - } - - public String getComponent() { - return component; - } - - public List<Double> getXvalues() { - return xvalues; - } - - public List<Double> getYvalues() { - return yvalues; - } - } - - static class Usage<T extends DefaultParameters> { - final String description; - final List<String> syntax; - final T parameters; - - private Usage(Service service, T parameters) { - description = service.description; - this.syntax = service.syntax; - this.parameters = parameters; - } - - public String getDescription() { - return description; - } - - public List<String> getSyntax() { - return syntax; - } - - public T getParameters() { - return parameters; - } - } - - static class RateParameters extends DefaultParameters { - final DoubleParameter distance; - - RateParameters() { - super(); - distance = new DoubleParameter( - "Cutoff distance", - "km", - 0.01, - 1000.0); - } - - public DoubleParameter getDistance() { - return distance; - } - } - - static class ProbabilityParameters extends RateParameters { - final DoubleParameter timespan; - - ProbabilityParameters() { - timespan = new DoubleParameter( - "Forecast time span", - "years", - Maths.TIMESPAN_RANGE.lowerEndpoint(), - Maths.TIMESPAN_RANGE.upperEndpoint()); - } - - public DoubleParameter getTimespan() { - return timespan; - } - } -} diff --git a/src/main/java/gov/usgs/earthquake/nshmp/www/services/SourceLogicTreesController.java b/src/main/java/gov/usgs/earthquake/nshmp/www/services/SourceLogicTreesController.java deleted file mode 100644 index 253647039a1e2df82441b24e5b4e5ffa05c2d150..0000000000000000000000000000000000000000 --- a/src/main/java/gov/usgs/earthquake/nshmp/www/services/SourceLogicTreesController.java +++ /dev/null @@ -1,72 +0,0 @@ -package gov.usgs.earthquake.nshmp.www.services; - -import gov.usgs.earthquake.nshmp.www.NshmpMicronautServlet; -import gov.usgs.earthquake.nshmp.www.ResponseBody; -import gov.usgs.earthquake.nshmp.www.services.SourceLogicTreesService.Metadata; -import gov.usgs.earthquake.nshmp.www.services.SourceLogicTreesService.RequestData; - -import io.micronaut.http.HttpRequest; -import io.micronaut.http.HttpResponse; -import io.micronaut.http.annotation.Controller; -import io.micronaut.http.annotation.Get; -import io.micronaut.http.annotation.PathVariable; -import io.swagger.v3.oas.annotations.Operation; -import io.swagger.v3.oas.annotations.media.Content; -import io.swagger.v3.oas.annotations.media.Schema; -import io.swagger.v3.oas.annotations.responses.ApiResponse; -import io.swagger.v3.oas.annotations.tags.Tag; -import jakarta.inject.Inject; - -/** - * Micronaut controller for NSHM source logic trees. - * - * @see SourceLogicTreesService - * @author U.S. Geological Survey - */ -@Tag( - name = "Source Logic Trees", - description = "NSHM source logic trees service") -@Controller("/trees") -public class SourceLogicTreesController { - - @Inject - private NshmpMicronautServlet servlet; - - @Operation( - description = "Returns the tectonic setting to source logic trees in the NSHM", - operationId = "trees_doGetMetadata", - summary = "Hazard model source logic trees") - @ApiResponse( - description = "Source logic trees metadata", - responseCode = "200", - content = @Content( - schema = @Schema( - implementation = MetadataResponse.class))) - @Get - public HttpResponse<String> doGetMetadata(HttpRequest<?> request) { - return SourceLogicTreesService.handleDoGetMetadata(request); - } - - /** - * @param id Source tree id - */ - @Operation( - description = "Returns the source logic tree for an id", - operationId = "trees_goGetTrees", - summary = "Get NSHM source logic tree") - @ApiResponse( - description = "NSHM source logic tree", - responseCode = "200", - content = @Content( - schema = @Schema(implementation = TreeResponse.class))) - @Get(uri = "/{id}") - public HttpResponse<String> doGetTrees(HttpRequest<?> request, @PathVariable int id) { - return SourceLogicTreesService.handleDoGetTrees(request, id); - } - - // For Swagger schemas - private static class MetadataResponse extends ResponseBody<String, Metadata> {} - - // For Swagger schemas - private static class TreeResponse extends ResponseBody<RequestData, Object> {} -} diff --git a/src/main/java/gov/usgs/earthquake/nshmp/www/services/SourceLogicTreesService.java b/src/main/java/gov/usgs/earthquake/nshmp/www/services/SourceLogicTreesService.java deleted file mode 100644 index 97de1486d9c158d1c1a7a44dc7af62006ba54d31..0000000000000000000000000000000000000000 --- a/src/main/java/gov/usgs/earthquake/nshmp/www/services/SourceLogicTreesService.java +++ /dev/null @@ -1,98 +0,0 @@ -package gov.usgs.earthquake.nshmp.www.services; - -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import gov.usgs.earthquake.nshmp.model.HazardModel; -import gov.usgs.earthquake.nshmp.model.Models; -import gov.usgs.earthquake.nshmp.www.HazVersion; -import gov.usgs.earthquake.nshmp.www.ResponseBody; -import gov.usgs.earthquake.nshmp.www.ResponseMetadata; -import gov.usgs.earthquake.nshmp.www.ServletUtil; -import gov.usgs.earthquake.nshmp.www.services.SourceServices.SourceModel; - -import io.micronaut.http.HttpRequest; -import io.micronaut.http.HttpResponse; -import jakarta.inject.Singleton; - -/** - * Source model tree handler for {@link SourceLogicTreesController} - * - * @author U.S. Geological Survey - */ -@Singleton -public class SourceLogicTreesService { - - static final Logger LOG = LoggerFactory.getLogger(SourceLogicTreesService.class); - - private static final String NAME = "Source Logic Trees"; - - /** SourceLogicTreesController.doGetMetadata() handler */ - public static HttpResponse<String> handleDoGetMetadata(HttpRequest<?> request) { - var url = request.getUri().getPath(); - - try { - var metadata = new Metadata(ServletUtil.model()); - var response = ResponseBody.usage() - .name(NAME) - .url(url) - .metadata(new ResponseMetadata(HazVersion.appVersions())) - .request(url) - .response(metadata) - .build(); - return HttpResponse.ok(ServletUtil.GSON.toJson(response)); - } catch (Exception e) { - return ServletUtil.error(LOG, e, NAME, url); - } - } - - /** SourceLogicTreesController.doGetTrees() handler */ - public static HttpResponse<String> handleDoGetTrees(HttpRequest<?> request, Integer id) { - var url = request.getUri().getPath(); - - try { - var tree = Models.tree(ServletUtil.model(), id); - var requestData = new RequestData(id); - var response = ResponseBody.success() - .name(NAME) - .url(url) - .metadata(new ResponseMetadata(HazVersion.appVersions())) - .request(requestData) - .response(tree) - .build(); - return HttpResponse.ok(ServletUtil.GSON.toJson(response)); - } catch (Exception e) { - return ServletUtil.error(LOG, e, NAME, url); - } - } - - static class RequestData { - final int id; - - RequestData(int id) { - this.id = id; - } - - public int getId() { - return id; - } - } - - static class Metadata { - final SourceModel model; - final Object trees; - - Metadata(HazardModel model) { - this.model = new SourceModel(model); - trees = Models.trees(model); - } - - public SourceModel getModel() { - return model; - } - - public Object getTrees() { - return trees; - } - } -} diff --git a/src/main/java/gov/usgs/earthquake/nshmp/www/source/ProbabilityController.java b/src/main/java/gov/usgs/earthquake/nshmp/www/source/ProbabilityController.java new file mode 100644 index 0000000000000000000000000000000000000000..33dbd1b172b7d58bbcaab09b6675a442fb536cd1 --- /dev/null +++ b/src/main/java/gov/usgs/earthquake/nshmp/www/source/ProbabilityController.java @@ -0,0 +1,110 @@ +package gov.usgs.earthquake.nshmp.www.source; + +import gov.usgs.earthquake.nshmp.www.NshmpMicronautServlet; +import gov.usgs.earthquake.nshmp.www.ResponseBody; +import gov.usgs.earthquake.nshmp.www.ServletUtil; +import gov.usgs.earthquake.nshmp.www.source.RateService.ProbMetadata; +import gov.usgs.earthquake.nshmp.www.source.RateService.Request; +import gov.usgs.earthquake.nshmp.www.source.RateService.Response; + +import io.micronaut.http.HttpRequest; +import io.micronaut.http.HttpResponse; +import io.micronaut.http.MediaType; +import io.micronaut.http.annotation.Controller; +import io.micronaut.http.annotation.Get; +import io.micronaut.http.annotation.PathVariable; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.media.Content; +import io.swagger.v3.oas.annotations.media.Schema; +import io.swagger.v3.oas.annotations.responses.ApiResponse; +import io.swagger.v3.oas.annotations.tags.Tag; +import jakarta.inject.Inject; + +/** + * Micronaut web service controller for probability calcuations. + * + * <p>See src/main/resources/application.yml nshmp-haz.model-path for installed + * model. + * + * <p>To run the Micronaut jar file with a model: java -jar + * path/to/nshmp-haz.jar --model=<path/to/model> + * + * @author U.S. Geological Survey + */ +@Tag( + name = RateService.NAME_PROBABILITY, + description = "USGS NSHM earthquake probability calculation service") +@Controller("/probability") +public class ProbabilityController { + + @Inject + private NshmpMicronautServlet servlet; + + @Operation( + summary = "Earthquake probability calculation model and service metadata", + description = "Returns details of the installed model and service request parameters", + operationId = "probability-metadata") + @ApiResponse( + description = "Probability service metadata", + responseCode = "200", + content = @Content( + schema = @Schema(implementation = ProbabilityMetadata.class))) + @Get(produces = MediaType.APPLICATION_JSON) + public HttpResponse<String> doGetProbabilityMetadata(HttpRequest<?> http) { + try { + return RateService.getProbMetadata(http); + } catch (Exception e) { + return ServletUtil.error( + RateService.LOG, e, + RateService.NAME_PROBABILITY, + http.getUri().toString()); + } + } + + /** + * @param longitude Longitude in decimal degrees in the range + * @param latitude Latitude in decimal degrees in the range + * @param distance Cutoff distance in the range [0.01..1000] km. + * @param timespan Forecast time span in the range [1..10000] years. + */ + @Operation( + summary = "Compute earthquake probabilities", + description = "Compute cumulative earthquake probabilities P(M ≥ x) at a location", + operationId = "probability-calc") + @ApiResponse( + description = "Earthquake probability calculation response", + responseCode = "200", + content = @Content( + schema = @Schema( + implementation = ProbabilityResponse.class))) + @Get( + uri = "/{longitude}/{latitude}/{distance}/{timespan}", + produces = MediaType.APPLICATION_JSON) + public HttpResponse<String> doGetProbability( + HttpRequest<?> http, + @PathVariable double longitude, + @PathVariable double latitude, + @Schema( + minimum = "0.01", + maximum = "1000") @PathVariable double distance, + @Schema( + minimum = "1", + maximum = "10000") @PathVariable double timespan) { + try { + RateService.ProbRequest request = new RateService.ProbRequest( + http, longitude, latitude, distance, timespan); + return RateService.getProbability(request); + } catch (Exception e) { + return ServletUtil.error( + RateService.LOG, e, + RateService.NAME_PROBABILITY, + http.getUri().toString()); + } + } + + // Swagger schema + private static class ProbabilityResponse extends ResponseBody<Request, Response> {} + + // Swagger schema + private static class ProbabilityMetadata extends ResponseBody<String, ProbMetadata> {}; +} diff --git a/src/main/java/gov/usgs/earthquake/nshmp/www/source/RateController.java b/src/main/java/gov/usgs/earthquake/nshmp/www/source/RateController.java new file mode 100644 index 0000000000000000000000000000000000000000..8dffb0c1fd894303271ee5ac796ea16a9c2ea78d --- /dev/null +++ b/src/main/java/gov/usgs/earthquake/nshmp/www/source/RateController.java @@ -0,0 +1,105 @@ +package gov.usgs.earthquake.nshmp.www.source; + +import gov.usgs.earthquake.nshmp.www.NshmpMicronautServlet; +import gov.usgs.earthquake.nshmp.www.ResponseBody; +import gov.usgs.earthquake.nshmp.www.ServletUtil; +import gov.usgs.earthquake.nshmp.www.source.RateService.Metadata; +import gov.usgs.earthquake.nshmp.www.source.RateService.Request; +import gov.usgs.earthquake.nshmp.www.source.RateService.Response; + +import io.micronaut.http.HttpRequest; +import io.micronaut.http.HttpResponse; +import io.micronaut.http.MediaType; +import io.micronaut.http.annotation.Controller; +import io.micronaut.http.annotation.Get; +import io.micronaut.http.annotation.PathVariable; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.media.Content; +import io.swagger.v3.oas.annotations.media.Schema; +import io.swagger.v3.oas.annotations.responses.ApiResponse; +import io.swagger.v3.oas.annotations.tags.Tag; +import jakarta.inject.Inject; + +/** + * Micronaut web service controller for rate calcuations. + * + * <p>See src/main/resources/application.yml nshmp-haz.model-path for installed + * model. + * + * <p>To run the Micronaut jar file with a model: java -jar + * path/to/nshmp-haz.jar --model=<path/to/model> + * + * @author U.S. Geological Survey + */ +@Tag( + name = RateService.NAME_RATE, + description = "USGS NSHM earthquake rate calculation service") +@Controller("/rate") +public class RateController { + + @Inject + private NshmpMicronautServlet servlet; + + @Operation( + summary = "Earthquake rate calculation model and service metadata", + description = "Returns details of the installed model and service request parameters", + operationId = "rate-metadata") + @ApiResponse( + description = "Rate service metadata", + responseCode = "200", + content = @Content( + schema = @Schema(implementation = RateMetadata.class))) + @Get(produces = MediaType.APPLICATION_JSON) + public HttpResponse<String> doGetRateMetadata(HttpRequest<?> http) { + try { + return RateService.getRateMetadata(http); + } catch (Exception e) { + return ServletUtil.error( + RateService.LOG, e, + RateService.NAME_RATE, + http.getUri().toString()); + } + } + + /** + * @param longitude Longitude in decimal degrees in the range + * @param latitude Latitude in decimal degrees in the range + * @param distance Cutoff distance in the range [0.01, 1000] km. + */ + @Operation( + summary = "Compute annual earthquake rates", + description = "Compute incremental annual earthquake rates at a location", + operationId = "rate-calc") + @ApiResponse( + description = "Earthquake annual rate calculation response", + responseCode = "200", + content = @Content( + schema = @Schema(implementation = RateResponse.class))) + @Get( + uri = "/{longitude}/{latitude}/{distance}", + produces = MediaType.APPLICATION_JSON) + public HttpResponse<String> doGetRate( + HttpRequest<?> http, + @PathVariable double longitude, + @PathVariable double latitude, + @Schema( + minimum = "0.01", + maximum = "1000") @PathVariable double distance) { + try { + RateService.Request request = new RateService.Request( + http, longitude, latitude, distance); + return RateService.getRate(request); + } catch (Exception e) { + return ServletUtil.error( + RateService.LOG, e, + RateService.NAME_RATE, + http.getUri().toString()); + } + } + + // Swagger schema + private static class RateResponse extends ResponseBody<Request, Response> {} + + // Swagger schema + private static class RateMetadata extends ResponseBody<String, Metadata> {}; +} diff --git a/src/main/java/gov/usgs/earthquake/nshmp/www/source/RateService.java b/src/main/java/gov/usgs/earthquake/nshmp/www/source/RateService.java new file mode 100644 index 0000000000000000000000000000000000000000..b610a993541d31d71cee1cac50d9311e20b1c95e --- /dev/null +++ b/src/main/java/gov/usgs/earthquake/nshmp/www/source/RateService.java @@ -0,0 +1,425 @@ +package gov.usgs.earthquake.nshmp.www.source; + +import static gov.usgs.earthquake.nshmp.data.DoubleData.checkInRange; +import static gov.usgs.earthquake.nshmp.geo.Coordinates.checkLatitude; +import static gov.usgs.earthquake.nshmp.geo.Coordinates.checkLongitude; + +import java.util.ArrayList; +import java.util.List; +import java.util.OptionalDouble; +import java.util.concurrent.ExecutionException; +import java.util.stream.Collectors; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import com.google.common.base.Stopwatch; +import com.google.common.collect.Range; +import com.google.common.util.concurrent.ListenableFuture; + +import gov.usgs.earthquake.nshmp.calc.CalcConfig; +import gov.usgs.earthquake.nshmp.calc.EqRate; +import gov.usgs.earthquake.nshmp.calc.Site; +import gov.usgs.earthquake.nshmp.geo.Location; +import gov.usgs.earthquake.nshmp.model.HazardModel; +import gov.usgs.earthquake.nshmp.www.HazVersion; +import gov.usgs.earthquake.nshmp.www.ResponseBody; +import gov.usgs.earthquake.nshmp.www.ResponseMetadata; +import gov.usgs.earthquake.nshmp.www.ServletUtil; +import gov.usgs.earthquake.nshmp.www.ServletUtil.Server; +import gov.usgs.earthquake.nshmp.www.meta.DoubleParameter; +import gov.usgs.earthquake.nshmp.www.source.SourceService.SourceModel; + +import io.micronaut.http.HttpRequest; +import io.micronaut.http.HttpResponse; +import jakarta.inject.Singleton; + +/** + * Earthquake probability and rate calculation handler service for + * {@link RateController}. + * + * @author U.S. Geological Survey + */ +@Singleton +public final class RateService { + + static final String NAME_RATE = "Earthquake Rates"; + static final String NAME_PROBABILITY = "Earthquake Probabilities"; + static final Logger LOG = LoggerFactory.getLogger(RateService.class); + + /* + * Developer notes: + * + * The RateService is currently single-threaded and does not submit jobs to a + * request queue; see HazardService. However, jobs are placed on a thread in + * the CALC_EXECUTOR thread pool to handle parallel calculation of CEUS and + * WUS models. + */ + + private static final String TOTAL_KEY = "Total"; + + public static HttpResponse<String> getRate(Request request) { + var stopwatch = Stopwatch.createStarted(); + var rates = calcRate(request); + var response = new Response.Builder() + .timer(stopwatch) + .request(request) + .rates(rates) + .build(); + var body = ResponseBody.success() + .name(NAME_RATE) + .url(request.http.getUri().toString()) + .metadata(new ResponseMetadata(HazVersion.appVersions())) + .request(request) + .response(response) + .build(); + String json = ServletUtil.GSON2.toJson(body); + return HttpResponse.ok(json); + } + + public static HttpResponse<String> getProbability(ProbRequest request) { + var stopwatch = Stopwatch.createStarted(); + var rates = calcProbability(request); + var response = new Response.Builder() + .timer(stopwatch) + .request(request) + .rates(rates) + .build(); + var body = ResponseBody.success() + .name(NAME_PROBABILITY) + .url(request.http.getUri().toString()) + .metadata(new ResponseMetadata(HazVersion.appVersions())) + .request(request) + .response(response) + .build(); + String json = ServletUtil.GSON2.toJson(body); + return HttpResponse.ok(json); + } + + private static EqRate calcRate(Request request) { + return calc(request, OptionalDouble.empty()); + } + + private static EqRate calcProbability(ProbRequest request) { + return calc(request, OptionalDouble.of(request.timespan)); + } + + private static EqRate calc(Request request, OptionalDouble timespan) { + var location = Location.create(request.longitude, request.latitude); + var site = Site.builder().location(location).build(); + var futureRates = new ArrayList<ListenableFuture<EqRate>>(); + + /* + * Because we need to combine model results, intially calculate incremental + * annual rates and only convert to cumulative probabilities at the end if + * probability service has been called. + */ + + var model = ServletUtil.model(); + var rate = process(model, site, request.distance, timespan); + futureRates.add(rate); + + var rates = futureRates.stream() + .map((future) -> { + try { + return future.get(); + } catch (InterruptedException | ExecutionException e) { + throw new RuntimeException(e); + } + }).collect(Collectors.toList()) + .toArray(new EqRate[] {}); + + var ratesCombined = EqRate.combine(rates); + + if (timespan.isPresent()) { + ratesCombined = EqRate.toCumulative(ratesCombined); + ratesCombined = EqRate.toPoissonProbability(ratesCombined, timespan.orElseThrow()); + } + + return ratesCombined; + } + + private static ListenableFuture<EqRate> process( + HazardModel model, + Site site, + double distance, + OptionalDouble timespan) { + var configBuilder = CalcConfig.copyOf(model.config()).distance(distance); + if (timespan.isPresent()) { + /* Also sets value format to Poisson probability. */ + configBuilder.timespan(timespan.getAsDouble()); + } + var config = configBuilder.build(); + var task = EqRate.callable(model, config, site); + return ServletUtil.CALC_EXECUTOR.submit(task); + } + + static class Response { + + final Metadata metadata; + final List<Sequence> data; + + Response(Metadata metadata, List<Sequence> data) { + this.metadata = metadata; + this.data = data; + } + + public Metadata getMetadata() { + return metadata; + } + + public List<Sequence> getData() { + return data; + } + + private static final class Metadata { + final Server server; + final String xlabel = "Ground Motion (g)"; + final String ylabel = "Annual Frequency of Exceedance"; + + Metadata(Server server) { + this.server = server; + } + + public Server getServer() { + return server; + } + + public String getXLabel() { + return xlabel; + } + + public String getYLabel() { + return ylabel; + } + } + + private static final class Builder { + + Stopwatch timer; + Request request; + List<Sequence> data; + + Builder timer(Stopwatch timer) { + this.timer = timer; + return this; + } + + Builder request(Request request) { + this.request = request; + return this; + } + + Builder rates(EqRate rates) { + this.data = buildSequences(rates); + return this; + } + + private List<Sequence> buildSequences(EqRate rates) { + var sequences = new ArrayList<Sequence>(); + + /* Total mfd. */ + var total = (!rates.totalMfd.isClear()) ? rates.totalMfd.trim() : rates.totalMfd; + var totalOut = new Sequence( + TOTAL_KEY, + total.xValues().boxed().collect(Collectors.toList()), + total.yValues().boxed().collect(Collectors.toList())); + sequences.add(totalOut); + + /* Source type mfds. */ + for (var entry : rates.typeMfds.entrySet()) { + var type = entry.getValue(); + if (type.isClear()) { + continue; + } + type = type.trim(); + var typeOut = new Sequence( + entry.getKey().toString(), + type.xValues().boxed().collect(Collectors.toList()), + type.yValues().boxed().collect(Collectors.toList())); + sequences.add(typeOut); + } + + return List.copyOf(sequences); + } + + Response build() { + Server server = ServletUtil.serverData(ServletUtil.THREAD_COUNT, timer); + return new Response(new Response.Metadata(server), data); + } + } + } + + /* + * Would rather use this a general container for mfds and hazard curves. See + * HazardService.Curve + */ + private static class Sequence { + final String component; + final List<Double> xvalues; + final List<Double> yvalues; + + Sequence(String component, List<Double> xvalues, List<Double> yvalues) { + this.component = component; + this.xvalues = xvalues; + this.yvalues = yvalues; + } + + public String getComponent() { + return component; + } + + public List<Double> getXvalues() { + return xvalues; + } + + public List<Double> getYvalues() { + return yvalues; + } + } + + private static final Range<Double> DISTANCE_RANGE = Range.closed(0.01, 1000.0); + private static final Range<Double> TIMESPAN_RANGE = Range.closed(1.0, 10000.0); + + static class Request { + + final transient HttpRequest<?> http; + final double longitude; + final double latitude; + final double distance; + + public Request( + HttpRequest<?> http, + double longitude, + double latitude, + double distance) { + this.http = http; + this.longitude = checkLongitude(longitude); + this.latitude = checkLatitude(latitude); + this.distance = checkInRange(DISTANCE_RANGE, "Distance cutoff", distance); + } + + public double getLongitude() { + return longitude; + } + + public double getLatitude() { + return latitude; + } + + public double getDistance() { + return distance; + } + } + + static class ProbRequest extends Request { + + final double timespan; + + public ProbRequest( + HttpRequest<?> http, + double longitude, + double latitude, + double distance, + double timespan) { + super(http, longitude, latitude, distance); + this.timespan = checkInRange(TIMESPAN_RANGE, "Forecast timespan", timespan); + } + + public double getTimespan() { + return timespan; + } + } + + public static HttpResponse<String> getRateMetadata(HttpRequest<?> request) { + var url = request.getUri().toString(); + var usage = new Metadata(ServletUtil.model()); + var body = ResponseBody.usage() + .name(NAME_RATE) + .url(url) + .metadata(new ResponseMetadata(HazVersion.appVersions())) + .request(url) + .response(usage) + .build(); + var json = ServletUtil.GSON2.toJson(body); + return HttpResponse.ok(json); + } + + public static HttpResponse<String> getProbMetadata(HttpRequest<?> request) { + var url = request.getUri().toString(); + var usage = new ProbMetadata(ServletUtil.model()); + var body = ResponseBody.usage() + .name(NAME_PROBABILITY) + .url(url) + .metadata(new ResponseMetadata(HazVersion.appVersions())) + .request(url) + .response(usage) + .build(); + var json = ServletUtil.GSON2.toJson(body); + return HttpResponse.ok(json); + } + + static class Metadata { + + final SourceModel model; + final DoubleParameter longitude; + final DoubleParameter latitude; + final DoubleParameter distance; + + Metadata(HazardModel model) { + this.model = new SourceModel(model); + longitude = new DoubleParameter( + "Longitude", + "°", + model.bounds().min.longitude, + model.bounds().max.longitude); + + latitude = new DoubleParameter( + "Latitude", + "°", + model.bounds().min.latitude, + model.bounds().max.latitude); + + distance = new DoubleParameter( + "Cutoff distance", + "km", + 0.01, + 1000.0); + } + + public SourceModel getModel() { + return model; + } + + public DoubleParameter getLongitude() { + return longitude; + } + + public DoubleParameter getLatitude() { + return latitude; + } + + public DoubleParameter getDistance() { + return distance; + } + } + + static class ProbMetadata extends Metadata { + + final DoubleParameter timespan; + + ProbMetadata(HazardModel model) { + super(model); + timespan = new DoubleParameter( + "Forecast timespan", + "years", + 1.0, + 10000.0); + } + + public DoubleParameter getTimespan() { + return timespan; + } + } + +} diff --git a/src/main/java/gov/usgs/earthquake/nshmp/www/services/SourceController.java b/src/main/java/gov/usgs/earthquake/nshmp/www/source/SourceController.java similarity index 54% rename from src/main/java/gov/usgs/earthquake/nshmp/www/services/SourceController.java rename to src/main/java/gov/usgs/earthquake/nshmp/www/source/SourceController.java index b6fe870b5d8f6ddc6359971fcbda57f0b4a774c4..2bd63ae10a410fcd0de8e2ee2bc7b482f935322a 100644 --- a/src/main/java/gov/usgs/earthquake/nshmp/www/services/SourceController.java +++ b/src/main/java/gov/usgs/earthquake/nshmp/www/source/SourceController.java @@ -1,8 +1,9 @@ -package gov.usgs.earthquake.nshmp.www.services; +package gov.usgs.earthquake.nshmp.www.source; import gov.usgs.earthquake.nshmp.www.NshmpMicronautServlet; import gov.usgs.earthquake.nshmp.www.ResponseBody; -import gov.usgs.earthquake.nshmp.www.services.SourceServices.ResponseData; +import gov.usgs.earthquake.nshmp.www.ServletUtil; +import gov.usgs.earthquake.nshmp.www.source.SourceService.ResponseData; import io.micronaut.http.HttpRequest; import io.micronaut.http.HttpResponse; @@ -17,34 +18,36 @@ import io.swagger.v3.oas.annotations.tags.Tag; import jakarta.inject.Inject; /** - * Source model service to return the current installed model. + * Micronaut web service controller to return metadata for the current installed + * model. * - * <p> See src/main/resources/applicaiton.yml nshmp-haz.installed-model for - * default model used - * - * <p> To run the Micronaut jar file with a model: java -jar - * path/to/nshmp-haz.jar -model=<{@code Model}> + * <p>See src/main/resources/application.yml nshmp-haz.model-path for installed + * model. * + * <p>To run the Micronaut jar file with a model: java -jar + * path/to/nshmp-haz.jar --model=<path/to/model> * * @author U.S. Geological Survey */ -@Tag(name = "Source Model") +@Tag( + name = SourceService.NAME, + description = "USGS NSHM source model metadata service") @Controller("/source") public class SourceController { + // TODO consider renaming to /model + @Inject private NshmpMicronautServlet servlet; /** - * GET method to return the source model usage - * * @param request The HTTP request */ @Operation( - summary = "Returns the metadata about the current installed NSHM", + summary = "Returns metadata about the current installed NSHM", description = "Returns the install National Hazard Model with supported:\n" + - "* Intensity measure types (IMT)\n * VS30\n * Region bounds\n * Return period", - operationId = "source_doGetUsage") + "* Intensity measure types (IMT)\n * Vs30\n * Region bounds\n * Return period", + operationId = "source-model-metadata") @ApiResponse( description = "Installed source model", responseCode = "200", @@ -52,10 +55,17 @@ public class SourceController { schema = @Schema( implementation = MetadataResponse.class))) @Get(produces = MediaType.APPLICATION_JSON) - public HttpResponse<String> doGetUsage(HttpRequest<?> request) { - return SourceServices.handleDoGetUsage(request); + public HttpResponse<String> doGetMetadata(HttpRequest<?> http) { + try { + return SourceService.getMetadata(http); + } catch (Exception e) { + return ServletUtil.error( + SourceService.LOG, e, + SourceService.NAME, + http.getUri().toString()); + } } - // For Swagger schemas + // Swagger schema private static class MetadataResponse extends ResponseBody<String, ResponseData> {} } diff --git a/src/main/java/gov/usgs/earthquake/nshmp/www/source/SourceLogicTreesController.java b/src/main/java/gov/usgs/earthquake/nshmp/www/source/SourceLogicTreesController.java new file mode 100644 index 0000000000000000000000000000000000000000..971c66e80bbc529918c3c7b3427bbf83235870ce --- /dev/null +++ b/src/main/java/gov/usgs/earthquake/nshmp/www/source/SourceLogicTreesController.java @@ -0,0 +1,93 @@ +package gov.usgs.earthquake.nshmp.www.source; + +import gov.usgs.earthquake.nshmp.www.NshmpMicronautServlet; +import gov.usgs.earthquake.nshmp.www.ResponseBody; +import gov.usgs.earthquake.nshmp.www.ServletUtil; +import gov.usgs.earthquake.nshmp.www.source.SourceLogicTreesService.Metadata; +import gov.usgs.earthquake.nshmp.www.source.SourceLogicTreesService.RequestData; + +import io.micronaut.http.HttpRequest; +import io.micronaut.http.HttpResponse; +import io.micronaut.http.annotation.Controller; +import io.micronaut.http.annotation.Get; +import io.micronaut.http.annotation.PathVariable; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.media.Content; +import io.swagger.v3.oas.annotations.media.Schema; +import io.swagger.v3.oas.annotations.responses.ApiResponse; +import io.swagger.v3.oas.annotations.tags.Tag; +import jakarta.inject.Inject; + +/** + * Micronaut web service controller for the source logic trees in the current + * installed model. + * + * <p>See src/main/resources/application.yml nshmp-haz.model-path for installed + * model. + * + * <p>To run the Micronaut jar file with a model: java -jar + * path/to/nshmp-haz.jar --model=<path/to/model> + * + * @author U.S. Geological Survey + */ +@Tag( + name = SourceLogicTreesService.NAME, + description = "USGS NSHM source model logic tree service") +@Controller("/trees") +public class SourceLogicTreesController { + + @Inject + private NshmpMicronautServlet servlet; + + @Operation( + summary = "Source model logic tree listing", + description = "Returns the ID's of logic trees in the model", + operationId = "source-tree-metadata") + @ApiResponse( + description = "Source logic tree metadata", + responseCode = "200", + content = @Content( + schema = @Schema( + implementation = MetadataResponse.class))) + @Get + public HttpResponse<String> doGetMetadata(HttpRequest<?> http) { + try { + return SourceLogicTreesService.getMetadata(http); + } catch (Exception e) { + return ServletUtil.error( + SourceLogicTreesService.LOG, e, + SourceLogicTreesService.NAME, + http.getUri().toString()); + } + } + + /** + * @param id Source tree id + */ + @Operation( + summary = "Get a source model MFD logic tree", + description = "Returns the logic tree of MFDs for the supplied ID", + operationId = "source-tree-mfds") + @ApiResponse( + description = "NSHM source logic tree", + responseCode = "200", + content = @Content( + schema = @Schema(implementation = TreeResponse.class))) + @Get(uri = "/{id}") + public HttpResponse<String> doGetTree(HttpRequest<?> http, @PathVariable int id) { + try { + return SourceLogicTreesService.getTree(http, id); + } catch (Exception e) { + return ServletUtil.error( + SourceLogicTreesService.LOG, e, + SourceLogicTreesService.NAME, + http.getUri().toString()); + } + } + + // Swagger schema + private static class MetadataResponse extends ResponseBody<String, Metadata> {} + + // Swagger schema + private static class TreeResponse extends ResponseBody<RequestData, Object> {} +} diff --git a/src/main/java/gov/usgs/earthquake/nshmp/www/source/SourceLogicTreesService.java b/src/main/java/gov/usgs/earthquake/nshmp/www/source/SourceLogicTreesService.java new file mode 100644 index 0000000000000000000000000000000000000000..3f0fe6541bf47166bda144fe12484924935ffe6c --- /dev/null +++ b/src/main/java/gov/usgs/earthquake/nshmp/www/source/SourceLogicTreesService.java @@ -0,0 +1,85 @@ +package gov.usgs.earthquake.nshmp.www.source; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import gov.usgs.earthquake.nshmp.model.HazardModel; +import gov.usgs.earthquake.nshmp.model.Models; +import gov.usgs.earthquake.nshmp.www.HazVersion; +import gov.usgs.earthquake.nshmp.www.ResponseBody; +import gov.usgs.earthquake.nshmp.www.ResponseMetadata; +import gov.usgs.earthquake.nshmp.www.ServletUtil; +import gov.usgs.earthquake.nshmp.www.source.SourceService.SourceModel; + +import io.micronaut.http.HttpRequest; +import io.micronaut.http.HttpResponse; +import jakarta.inject.Singleton; + +/** + * Source model tree handler for {@link SourceLogicTreesController} + * + * @author U.S. Geological Survey + */ +@Singleton +public class SourceLogicTreesService { + + static final String NAME = "Model Logic Trees"; + static final Logger LOG = LoggerFactory.getLogger(SourceLogicTreesService.class); + + public static HttpResponse<String> getMetadata(HttpRequest<?> request) { + var url = request.getUri().toString(); + var metadata = new Metadata(ServletUtil.model()); + var response = ResponseBody.usage() + .name(NAME) + .url(url) + .metadata(new ResponseMetadata(HazVersion.appVersions())) + .request(url) + .response(metadata) + .build(); + return HttpResponse.ok(ServletUtil.GSON2.toJson(response)); + } + + public static HttpResponse<String> getTree(HttpRequest<?> request, Integer id) { + var url = request.getUri().toString(); + var tree = Models.tree(ServletUtil.model(), id); + var requestData = new RequestData(id); + var response = ResponseBody.success() + .name(NAME) + .url(url) + .metadata(new ResponseMetadata(HazVersion.appVersions())) + .request(requestData) + .response(tree) + .build(); + return HttpResponse.ok(ServletUtil.GSON2.toJson(response)); + } + + static class RequestData { + final int id; + + RequestData(int id) { + this.id = id; + } + + public int getId() { + return id; + } + } + + static class Metadata { + final SourceModel model; + final Object trees; + + Metadata(HazardModel model) { + this.model = new SourceModel(model); + trees = Models.trees(model); + } + + public SourceModel getModel() { + return model; + } + + public Object getTrees() { + return trees; + } + } +} diff --git a/src/main/java/gov/usgs/earthquake/nshmp/www/services/SourceServices.java b/src/main/java/gov/usgs/earthquake/nshmp/www/source/SourceService.java similarity index 53% rename from src/main/java/gov/usgs/earthquake/nshmp/www/services/SourceServices.java rename to src/main/java/gov/usgs/earthquake/nshmp/www/source/SourceService.java index 525ed76b346e7485281ebdfe972049f4627a4d87..6cedce847c694e6243773f74bde87e91ba7be2f5 100644 --- a/src/main/java/gov/usgs/earthquake/nshmp/www/services/SourceServices.java +++ b/src/main/java/gov/usgs/earthquake/nshmp/www/source/SourceService.java @@ -1,26 +1,22 @@ -package gov.usgs.earthquake.nshmp.www.services; +package gov.usgs.earthquake.nshmp.www.source; import static java.util.stream.Collectors.toList; import java.util.List; import java.util.Map; import java.util.Set; +import java.util.stream.DoubleStream; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import com.google.gson.Gson; -import com.google.gson.GsonBuilder; - import gov.usgs.earthquake.nshmp.gmm.Gmm; -import gov.usgs.earthquake.nshmp.gmm.Imt; import gov.usgs.earthquake.nshmp.gmm.NehrpSiteClass; import gov.usgs.earthquake.nshmp.model.HazardModel; import gov.usgs.earthquake.nshmp.www.HazVersion; import gov.usgs.earthquake.nshmp.www.ResponseBody; import gov.usgs.earthquake.nshmp.www.ResponseMetadata; import gov.usgs.earthquake.nshmp.www.ServletUtil; -import gov.usgs.earthquake.nshmp.www.WsUtils; import gov.usgs.earthquake.nshmp.www.meta.Parameter; import io.micronaut.http.HttpRequest; @@ -28,44 +24,29 @@ import io.micronaut.http.HttpResponse; import jakarta.inject.Singleton; /** - * Entry point for services related to source models. Current services: - * <ul><li>/source/</li></ul> + * Source model service. * * @author U.S. Geological Survey */ @Singleton -public class SourceServices { - - private static final String NAME = "Source Model"; - - static final Logger LOG = LoggerFactory.getLogger(RateService.class); - - public static final Gson GSON; - - static { - GSON = new GsonBuilder() - .registerTypeAdapter(Imt.class, new WsUtils.EnumSerializer<Imt>()) - .disableHtmlEscaping() - .serializeNulls() - .setPrettyPrinting() - .create(); - } - - static HttpResponse<String> handleDoGetUsage(HttpRequest<?> request) { - var url = request.getUri().getPath(); - try { - var response = ResponseBody.usage() - .name(NAME) - .url(url) - .metadata(new ResponseMetadata(HazVersion.appVersions())) - .request(url) - .response(new ResponseData()) - .build(); - var json = GSON.toJson(response); - return HttpResponse.ok(json); - } catch (Exception e) { - return ServletUtil.error(LOG, e, NAME, url); - } +public class SourceService { + + static final String NAME = "Model Contents"; + static final Logger LOG = LoggerFactory.getLogger(SourceService.class); + + static HttpResponse<String> getMetadata(HttpRequest<?> request) { + var url = request.getUri().toString(); + var response = ResponseBody.usage() + .name(NAME) + .url(url) + .metadata(new ResponseMetadata(HazVersion.appVersions())) + .request(url) + .response(new ResponseData()) + .build(); + // TODO check other services for url) and + // request() passing in the same url obj + var json = ServletUtil.GSON2.toJson(response); + return HttpResponse.ok(json); } static class ResponseData { @@ -91,6 +72,7 @@ public class SourceServices { final Set<Gmm> gmms; final Map<NehrpSiteClass, Double> siteClasses; final List<Parameter> imts; + final List<Double> bounds; public SourceModel(HazardModel model) { name = model.name(); @@ -103,6 +85,7 @@ public class SourceServices { .sorted() .map(imt -> new Parameter(ServletUtil.imtShortLabel(imt), imt.name())) .collect(toList()); + bounds = DoubleStream.of(model.bounds().toArray()).boxed().collect(toList()); } public String getName() { @@ -120,36 +103,9 @@ public class SourceServices { public List<Parameter> getImts() { return imts; } - } - - enum Attributes { - /* Source model service */ - MODEL, - - /* Serializing */ - ID, - VALUE, - DISPLAY, - DISPLAYORDER, - YEAR, - PATH, - REGION, - IMT, - VS30, - SUPPORTS, - MINLATITUDE, - MINLONGITUDE, - MAXLATITUDE, - MAXLONGITUDE; - - /** Return upper case string */ - String toUpperCase() { - return name().toUpperCase(); - } - /** Return lower case string */ - String toLowerCase() { - return name().toLowerCase(); + public List<Double> getBounds() { + return bounds; } } } diff --git a/src/test/java/gov/usgs/earthquake/nshmp/model/peer/PeerTest.java b/src/test/java/gov/usgs/earthquake/nshmp/model/peer/PeerTest.java index b18913f725d0367f5b9354842db1b0730cec99b1..d4fea07779a619d6836a05974fd9625003c0aa4e 100644 --- a/src/test/java/gov/usgs/earthquake/nshmp/model/peer/PeerTest.java +++ b/src/test/java/gov/usgs/earthquake/nshmp/model/peer/PeerTest.java @@ -2,7 +2,7 @@ package gov.usgs.earthquake.nshmp.model.peer; import static com.google.common.base.Preconditions.checkArgument; import static com.google.common.base.Preconditions.checkState; -import static gov.usgs.earthquake.nshmp.internal.Parsing.Delimiter.COMMA; +import static gov.usgs.earthquake.nshmp.Text.Delimiter.COMMA; import static java.lang.Math.abs; import static java.nio.charset.StandardCharsets.UTF_8; import static org.junit.jupiter.api.Assertions.assertArrayEquals; @@ -31,13 +31,13 @@ import com.google.gson.Gson; import com.google.gson.GsonBuilder; import gov.usgs.earthquake.nshmp.Maths; +import gov.usgs.earthquake.nshmp.Text; import gov.usgs.earthquake.nshmp.calc.CalcConfig; import gov.usgs.earthquake.nshmp.calc.Hazard; import gov.usgs.earthquake.nshmp.calc.HazardCalcs; import gov.usgs.earthquake.nshmp.calc.Site; import gov.usgs.earthquake.nshmp.calc.Sites; import gov.usgs.earthquake.nshmp.gmm.Imt; -import gov.usgs.earthquake.nshmp.internal.Parsing; import gov.usgs.earthquake.nshmp.model.HazardModel; class PeerTest { @@ -164,7 +164,7 @@ class PeerTest { for (String line : Iterables.skip(lines, 1)) { String[] splitLine = line.split(",", 4); String siteName = splitLine[0]; - List<Double> values = Parsing.splitToDoubleList(splitLine[3], COMMA); + List<Double> values = Text.splitToDoubleList(splitLine[3], COMMA); siteValuesMap.put(siteName, Doubles.toArray(values)); } return siteValuesMap;