From b7f6261c4b40751e98a272e579ce62790a79a0f1 Mon Sep 17 00:00:00 2001 From: Brandon Clayton <bclayton@usgs.gov> Date: Tue, 17 Jan 2023 09:16:45 -0700 Subject: [PATCH] Add ws legacy files --- .dockerignore | 17 + .gitignore | 16 + DISCLAIMER.md | 3 + Dockerfile | 53 + LICENSE.md | 1 + README.md | 8 + build.gradle | 162 ++ code.json | 44 + docker-entrypoint.sh | 161 ++ gradle/wrapper/gradle-wrapper.jar | Bin 0 -> 56172 bytes gradle/wrapper/gradle-wrapper.properties | 5 + gradlew | 172 ++ gradlew.bat | 84 + settings.gradle | 3 + .../nshmp/aws/HazardResultSliceLambda.java | 311 ++++ .../aws/HazardResultsMetadataLambda.java | 314 ++++ .../nshmp/aws/HazardResultsSlicerLambda.java | 257 +++ src/gov/usgs/earthquake/nshmp/aws/Util.java | 41 + .../nshmp/www/DeaggEpsilonService.java | 356 ++++ .../earthquake/nshmp/www/DeaggService.java | 235 +++ .../earthquake/nshmp/www/DeaggService2.java | 384 ++++ .../earthquake/nshmp/www/GmmServices.java | 701 +++++++ .../earthquake/nshmp/www/HazardService.java | 527 ++++++ .../earthquake/nshmp/www/HazardService2.java | 383 ++++ src/gov/usgs/earthquake/nshmp/www/Model.java | 85 + .../earthquake/nshmp/www/NshmpServlet.java | 108 ++ .../earthquake/nshmp/www/RateService.java | 424 +++++ .../earthquake/nshmp/www/ServletUtil.java | 329 ++++ .../earthquake/nshmp/www/SourceServices.java | 258 +++ src/gov/usgs/earthquake/nshmp/www/Util.java | 150 ++ .../nshmp/www/UtilitiesService.java | 131 ++ .../earthquake/nshmp/www/XY_DataGroup.java | 52 + .../nshmp/www/meta/Constrained.java | 10 + .../nshmp/www/meta/Constraints.java | 7 + .../nshmp/www/meta/DoubleParameter.java | 27 + .../earthquake/nshmp/www/meta/Edition.java | 129 ++ .../nshmp/www/meta/EditionConstraints.java | 28 + .../nshmp/www/meta/EnumParameter.java | 18 + .../earthquake/nshmp/www/meta/Metadata.java | 299 +++ .../earthquake/nshmp/www/meta/ParamType.java | 13 + .../earthquake/nshmp/www/meta/Region.java | 130 ++ .../nshmp/www/meta/RegionConstraints.java | 21 + .../earthquake/nshmp/www/meta/Status.java | 20 + .../usgs/earthquake/nshmp/www/meta/Util.java | 159 ++ .../earthquake/nshmp/www/meta/Versions.java | 55 + .../nshmp/www/meta/package-info.java | 9 + webapp/META-INF/MANIFEST.MF | 1 + webapp/WEB-INF/web-aws.xml | 65 + webapp/WEB-INF/web.xml | 49 + webapp/apps/config.js | 7 + webapp/apps/css/D3MapView.css | 97 + webapp/apps/css/D3View.css | 310 ++++ webapp/apps/css/Gmm.css | 176 ++ webapp/apps/css/MetadataPrint.css | 22 + webapp/apps/css/PrintFigure.css | 38 + webapp/apps/css/dashboard.css | 31 + webapp/apps/css/header.css | 20 + webapp/apps/css/location.css | 143 ++ webapp/apps/css/model-compare.css | 28 + webapp/apps/css/model-explorer.css | 27 + webapp/apps/css/services.css | 91 + webapp/apps/css/styles.css | 77 + webapp/apps/css/template.css | 610 ++++++ webapp/apps/css/test-sites.css | 13 + webapp/apps/css/util.css | 29 + webapp/apps/dynamic-compare.html | 173 ++ webapp/apps/exceedance-explorer.html | 166 ++ webapp/apps/geo-deagg.html | 151 ++ webapp/apps/gmm-distance.html | 253 +++ webapp/apps/hw-fw.html | 280 +++ webapp/apps/img/github.svg | 2 + webapp/apps/img/servicesIcon.png | Bin 0 -> 152531 bytes webapp/apps/img/usgs_logo 2.png | Bin 0 -> 5379 bytes webapp/apps/img/usgs_logo.png | Bin 0 -> 7503 bytes webapp/apps/js/Dashboard.js | 70 + webapp/apps/js/DashboardDev.js | 91 + webapp/apps/js/DynamicCompare.js | 1481 +++++++++++++++ webapp/apps/js/ExceedanceExplorer.js | 416 +++++ webapp/apps/js/GeoDeagg.js | 599 ++++++ webapp/apps/js/GmmDistance.js | 241 +++ webapp/apps/js/HwFw.js | 568 ++++++ webapp/apps/js/ModelCompare.js | 286 +++ webapp/apps/js/ModelExplorer.js | 394 ++++ webapp/apps/js/Services.js | 500 +++++ webapp/apps/js/Spectra.js | 1156 ++++++++++++ webapp/apps/js/Util.js | 34 + webapp/apps/js/calc/ExceedanceModel.js | 122 ++ webapp/apps/js/calc/Maths.js | 63 + webapp/apps/js/calc/UncertaintyModel.js | 33 + webapp/apps/js/d3/D3LinePlot.js | 1630 +++++++++++++++++ webapp/apps/js/d3/D3SaveFigure.js | 811 ++++++++ webapp/apps/js/d3/D3SaveLineData.js | 79 + webapp/apps/js/d3/D3Tooltip.js | 139 ++ webapp/apps/js/d3/D3Utils.js | 71 + webapp/apps/js/d3/axes/D3LineAxes.js | 418 +++++ webapp/apps/js/d3/data/D3LineData.js | 544 ++++++ webapp/apps/js/d3/data/D3LineSeriesData.js | 171 ++ webapp/apps/js/d3/data/D3XYPair.js | 43 + webapp/apps/js/d3/legend/D3LineLegend.js | 636 +++++++ .../js/d3/options/D3BaseSubViewOptions.js | 459 +++++ .../apps/js/d3/options/D3BaseViewOptions.js | 173 ++ .../apps/js/d3/options/D3LineLegendOptions.js | 509 +++++ webapp/apps/js/d3/options/D3LineOptions.js | 541 ++++++ .../js/d3/options/D3LineSubViewOptions.js | 811 ++++++++ .../apps/js/d3/options/D3LineViewOptions.js | 237 +++ .../apps/js/d3/options/D3SaveFigureOptions.js | 458 +++++ webapp/apps/js/d3/options/D3TextOptions.js | 282 +++ webapp/apps/js/d3/options/D3TooltipOptions.js | 329 ++++ webapp/apps/js/d3/view/D3BaseSubView.js | 186 ++ webapp/apps/js/d3/view/D3BaseView.js | 946 ++++++++++ webapp/apps/js/d3/view/D3LineSubView.js | 205 +++ webapp/apps/js/d3/view/D3LineView.js | 488 +++++ webapp/apps/js/error/NshmpError.js | 253 +++ webapp/apps/js/error/Preconditions.js | 417 +++++ webapp/apps/js/lib/Constraints.js | 78 + webapp/apps/js/lib/ControlPanel.js | 1082 +++++++++++ webapp/apps/js/lib/D3GeoDeagg.js | 939 ++++++++++ webapp/apps/js/lib/D3SaveData.js | 96 + webapp/apps/js/lib/D3SaveFigure.js | 909 +++++++++ webapp/apps/js/lib/D3Tooltip.js | 220 +++ webapp/apps/js/lib/D3View.js | 1384 ++++++++++++++ webapp/apps/js/lib/Footer.js | 314 ++++ webapp/apps/js/lib/Gmm.js | 589 ++++++ webapp/apps/js/lib/GmmBeta.js | 455 +++++ webapp/apps/js/lib/Hazard.js | 565 ++++++ webapp/apps/js/lib/HazardNew.js | 237 +++ webapp/apps/js/lib/Header.js | 175 ++ webapp/apps/js/lib/LeafletTestSitePicker.js | 467 +++++ webapp/apps/js/lib/Settings.js | 179 ++ webapp/apps/js/lib/Spinner.js | 96 + webapp/apps/js/lib/TestSiteView.js | 284 +++ webapp/apps/js/lib/Tools.js | 417 +++++ .../apps/js/response/HazardServiceResponse.js | 395 ++++ webapp/apps/js/response/WebServiceResponse.js | 151 ++ webapp/apps/model-compare.html | 146 ++ webapp/apps/model-explorer.html | 145 ++ webapp/apps/services.html | 23 + webapp/apps/spectra-plot.html | 44 + webapp/apps/util.html | 85 + webapp/data/americas.json | 1 + webapp/data/data.tsv | 5 + webapp/data/us.json | 1 + webapp/dev/index.html | 44 + webapp/etc/examples/Dashboard.js | 80 + webapp/etc/examples/d3/D3BasicLinePlot.js | 112 ++ webapp/etc/examples/d3/D3CustomLinePlot.js | 244 +++ .../etc/examples/d3/d3-basic-line-plot.html | 40 + .../etc/examples/d3/d3-custom-line-plot.html | 40 + webapp/etc/examples/index.html | 43 + webapp/favicon.ico | Bin 0 -> 10990 bytes webapp/index.html | 44 + 151 files changed, 36578 insertions(+) create mode 100644 .dockerignore create mode 100644 .gitignore create mode 100644 DISCLAIMER.md create mode 100644 Dockerfile create mode 100644 LICENSE.md create mode 100644 build.gradle create mode 100644 code.json create mode 100755 docker-entrypoint.sh create mode 100644 gradle/wrapper/gradle-wrapper.jar create mode 100644 gradle/wrapper/gradle-wrapper.properties create mode 100755 gradlew create mode 100644 gradlew.bat create mode 100644 settings.gradle create mode 100644 src/gov/usgs/earthquake/nshmp/aws/HazardResultSliceLambda.java create mode 100644 src/gov/usgs/earthquake/nshmp/aws/HazardResultsMetadataLambda.java create mode 100644 src/gov/usgs/earthquake/nshmp/aws/HazardResultsSlicerLambda.java create mode 100644 src/gov/usgs/earthquake/nshmp/aws/Util.java create mode 100644 src/gov/usgs/earthquake/nshmp/www/DeaggEpsilonService.java create mode 100644 src/gov/usgs/earthquake/nshmp/www/DeaggService.java create mode 100644 src/gov/usgs/earthquake/nshmp/www/DeaggService2.java create mode 100644 src/gov/usgs/earthquake/nshmp/www/GmmServices.java create mode 100644 src/gov/usgs/earthquake/nshmp/www/HazardService.java create mode 100644 src/gov/usgs/earthquake/nshmp/www/HazardService2.java create mode 100644 src/gov/usgs/earthquake/nshmp/www/Model.java create mode 100644 src/gov/usgs/earthquake/nshmp/www/NshmpServlet.java create mode 100644 src/gov/usgs/earthquake/nshmp/www/RateService.java create mode 100644 src/gov/usgs/earthquake/nshmp/www/ServletUtil.java create mode 100644 src/gov/usgs/earthquake/nshmp/www/SourceServices.java create mode 100644 src/gov/usgs/earthquake/nshmp/www/Util.java create mode 100644 src/gov/usgs/earthquake/nshmp/www/UtilitiesService.java create mode 100644 src/gov/usgs/earthquake/nshmp/www/XY_DataGroup.java create mode 100644 src/gov/usgs/earthquake/nshmp/www/meta/Constrained.java create mode 100644 src/gov/usgs/earthquake/nshmp/www/meta/Constraints.java create mode 100644 src/gov/usgs/earthquake/nshmp/www/meta/DoubleParameter.java create mode 100644 src/gov/usgs/earthquake/nshmp/www/meta/Edition.java create mode 100644 src/gov/usgs/earthquake/nshmp/www/meta/EditionConstraints.java create mode 100644 src/gov/usgs/earthquake/nshmp/www/meta/EnumParameter.java create mode 100644 src/gov/usgs/earthquake/nshmp/www/meta/Metadata.java create mode 100644 src/gov/usgs/earthquake/nshmp/www/meta/ParamType.java create mode 100644 src/gov/usgs/earthquake/nshmp/www/meta/Region.java create mode 100644 src/gov/usgs/earthquake/nshmp/www/meta/RegionConstraints.java create mode 100644 src/gov/usgs/earthquake/nshmp/www/meta/Status.java create mode 100644 src/gov/usgs/earthquake/nshmp/www/meta/Util.java create mode 100644 src/gov/usgs/earthquake/nshmp/www/meta/Versions.java create mode 100644 src/gov/usgs/earthquake/nshmp/www/meta/package-info.java create mode 100644 webapp/META-INF/MANIFEST.MF create mode 100644 webapp/WEB-INF/web-aws.xml create mode 100644 webapp/WEB-INF/web.xml create mode 100644 webapp/apps/config.js create mode 100644 webapp/apps/css/D3MapView.css create mode 100644 webapp/apps/css/D3View.css create mode 100644 webapp/apps/css/Gmm.css create mode 100644 webapp/apps/css/MetadataPrint.css create mode 100644 webapp/apps/css/PrintFigure.css create mode 100644 webapp/apps/css/dashboard.css create mode 100644 webapp/apps/css/header.css create mode 100644 webapp/apps/css/location.css create mode 100644 webapp/apps/css/model-compare.css create mode 100644 webapp/apps/css/model-explorer.css create mode 100644 webapp/apps/css/services.css create mode 100644 webapp/apps/css/styles.css create mode 100644 webapp/apps/css/template.css create mode 100644 webapp/apps/css/test-sites.css create mode 100644 webapp/apps/css/util.css create mode 100644 webapp/apps/dynamic-compare.html create mode 100644 webapp/apps/exceedance-explorer.html create mode 100644 webapp/apps/geo-deagg.html create mode 100644 webapp/apps/gmm-distance.html create mode 100644 webapp/apps/hw-fw.html create mode 100644 webapp/apps/img/github.svg create mode 100644 webapp/apps/img/servicesIcon.png create mode 100644 webapp/apps/img/usgs_logo 2.png create mode 100644 webapp/apps/img/usgs_logo.png create mode 100644 webapp/apps/js/Dashboard.js create mode 100644 webapp/apps/js/DashboardDev.js create mode 100644 webapp/apps/js/DynamicCompare.js create mode 100644 webapp/apps/js/ExceedanceExplorer.js create mode 100644 webapp/apps/js/GeoDeagg.js create mode 100644 webapp/apps/js/GmmDistance.js create mode 100644 webapp/apps/js/HwFw.js create mode 100644 webapp/apps/js/ModelCompare.js create mode 100644 webapp/apps/js/ModelExplorer.js create mode 100644 webapp/apps/js/Services.js create mode 100644 webapp/apps/js/Spectra.js create mode 100644 webapp/apps/js/Util.js create mode 100644 webapp/apps/js/calc/ExceedanceModel.js create mode 100644 webapp/apps/js/calc/Maths.js create mode 100644 webapp/apps/js/calc/UncertaintyModel.js create mode 100644 webapp/apps/js/d3/D3LinePlot.js create mode 100644 webapp/apps/js/d3/D3SaveFigure.js create mode 100644 webapp/apps/js/d3/D3SaveLineData.js create mode 100644 webapp/apps/js/d3/D3Tooltip.js create mode 100644 webapp/apps/js/d3/D3Utils.js create mode 100644 webapp/apps/js/d3/axes/D3LineAxes.js create mode 100644 webapp/apps/js/d3/data/D3LineData.js create mode 100644 webapp/apps/js/d3/data/D3LineSeriesData.js create mode 100644 webapp/apps/js/d3/data/D3XYPair.js create mode 100644 webapp/apps/js/d3/legend/D3LineLegend.js create mode 100644 webapp/apps/js/d3/options/D3BaseSubViewOptions.js create mode 100644 webapp/apps/js/d3/options/D3BaseViewOptions.js create mode 100644 webapp/apps/js/d3/options/D3LineLegendOptions.js create mode 100644 webapp/apps/js/d3/options/D3LineOptions.js create mode 100644 webapp/apps/js/d3/options/D3LineSubViewOptions.js create mode 100644 webapp/apps/js/d3/options/D3LineViewOptions.js create mode 100644 webapp/apps/js/d3/options/D3SaveFigureOptions.js create mode 100644 webapp/apps/js/d3/options/D3TextOptions.js create mode 100644 webapp/apps/js/d3/options/D3TooltipOptions.js create mode 100644 webapp/apps/js/d3/view/D3BaseSubView.js create mode 100644 webapp/apps/js/d3/view/D3BaseView.js create mode 100644 webapp/apps/js/d3/view/D3LineSubView.js create mode 100644 webapp/apps/js/d3/view/D3LineView.js create mode 100644 webapp/apps/js/error/NshmpError.js create mode 100644 webapp/apps/js/error/Preconditions.js create mode 100644 webapp/apps/js/lib/Constraints.js create mode 100644 webapp/apps/js/lib/ControlPanel.js create mode 100644 webapp/apps/js/lib/D3GeoDeagg.js create mode 100644 webapp/apps/js/lib/D3SaveData.js create mode 100644 webapp/apps/js/lib/D3SaveFigure.js create mode 100644 webapp/apps/js/lib/D3Tooltip.js create mode 100644 webapp/apps/js/lib/D3View.js create mode 100644 webapp/apps/js/lib/Footer.js create mode 100644 webapp/apps/js/lib/Gmm.js create mode 100644 webapp/apps/js/lib/GmmBeta.js create mode 100644 webapp/apps/js/lib/Hazard.js create mode 100644 webapp/apps/js/lib/HazardNew.js create mode 100644 webapp/apps/js/lib/Header.js create mode 100644 webapp/apps/js/lib/LeafletTestSitePicker.js create mode 100644 webapp/apps/js/lib/Settings.js create mode 100644 webapp/apps/js/lib/Spinner.js create mode 100644 webapp/apps/js/lib/TestSiteView.js create mode 100644 webapp/apps/js/lib/Tools.js create mode 100644 webapp/apps/js/response/HazardServiceResponse.js create mode 100644 webapp/apps/js/response/WebServiceResponse.js create mode 100644 webapp/apps/model-compare.html create mode 100644 webapp/apps/model-explorer.html create mode 100644 webapp/apps/services.html create mode 100644 webapp/apps/spectra-plot.html create mode 100644 webapp/apps/util.html create mode 100644 webapp/data/americas.json create mode 100644 webapp/data/data.tsv create mode 100644 webapp/data/us.json create mode 100644 webapp/dev/index.html create mode 100644 webapp/etc/examples/Dashboard.js create mode 100644 webapp/etc/examples/d3/D3BasicLinePlot.js create mode 100644 webapp/etc/examples/d3/D3CustomLinePlot.js create mode 100644 webapp/etc/examples/d3/d3-basic-line-plot.html create mode 100644 webapp/etc/examples/d3/d3-custom-line-plot.html create mode 100644 webapp/etc/examples/index.html create mode 100644 webapp/favicon.ico create mode 100644 webapp/index.html diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 000000000..7cbd63b65 --- /dev/null +++ b/.dockerignore @@ -0,0 +1,17 @@ +.classpath +.project +.settings +.gradle +bin +build +classes +tmp +webapp/models +webapp/jsdocs +Scratch*.java +scratch*.html +Scratch*.js +webapp/config.json +.vscode +config.properties +.git diff --git a/.gitignore b/.gitignore new file mode 100644 index 000000000..2ec1629b5 --- /dev/null +++ b/.gitignore @@ -0,0 +1,16 @@ +.classpath +.project +.settings +.gradle +bin +build +classes +tmp +webapp/models +webapp/jsdocs +Scratch*.java +scratch*.html +Scratch*.js +webapp/config.json +.vscode +config.properties diff --git a/DISCLAIMER.md b/DISCLAIMER.md new file mode 100644 index 000000000..b041ea7a0 --- /dev/null +++ b/DISCLAIMER.md @@ -0,0 +1,3 @@ +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +Information provided by this software may be preliminary or provisional and is subject to revision. It is being provided to meet the need for timely best science. The information is provided on the condition that neither the U.S. Geological Survey nor the U.S. Government shall be held liable for any damages resulting from the authorized or unauthorized use of the information. diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 000000000..b30357899 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,53 @@ +#### +# Dockerfile for nshmp-haz-ws +# +# Usage: +# docker run -p <PORT>:8080 -d usgs/nshmp-haz-ws +# +# Note: Models load as requested. While all supported models are +# available, requesting them all will eventually result in an +# OutOfMemoryError. Increase -Xmx to -Xmx16g or -Xmx24g, if available. +#### + +ARG FROM_IMAGE=usgs/amazoncorretto:8 + +#### +# Build nshmp-haz-ws +#### +FROM ${FROM_IMAGE} + +LABEL maintainer="Peter Powers <pmpowers@usgs.gov>" + +# Don't throttle IP address in Docker container +ENV CATALINA_OPTS="${CATALINA_OPTS} -DthrottleIp=false" +# Java opts +ENV JAVA_OPTS -Xms8g -Xmx8g + +# Repository version +ENV NSHMP_HAZ_VERSION=master +ENV NSHM_COUS_2018_VERSION=master +ENV NSHM_COUS_2014_VERSION=v4.1.4 +ENV NSHM_COUS_2014B_VERSION=master +ENV NSHM_COUS_2008_VERSION=master +ENV NSHM_AK_2007_VERSION=master +ENV NSHM_HI_2020_VERSION=master + +ENV CATALINA_HOME /usr/local/tomcat +ENV LANG en_US.UTF-8 +ENV PATH ${CATALINA_HOME}/bin:${PATH} +ENV TOMCAT_SOURCE http://archive.apache.org/dist/tomcat +ENV TOMCAT_WEBAPPS ${CATALINA_HOME}/webapps +ENV TOMCAT_URL=${TOMCAT_SOURCE}/tomcat-8/v8.5.40/bin/apache-tomcat-8.5.40.tar.gz + +# Install Tomcat +WORKDIR ${CATALINA_HOME} +RUN curl -L ${TOMCAT_URL} | tar -xz --strip-components=1 + +ENV WORKDIR=/app +ENV WAR_PATH=${WORKDIR}/build/libs/nshmp-haz-ws.war + +WORKDIR ${WORKDIR} +COPY . ${WORKDIR}/. + +# Build and run nshmp-haz-ws +ENTRYPOINT [ "bash", "docker-entrypoint.sh" ] diff --git a/LICENSE.md b/LICENSE.md new file mode 100644 index 000000000..6025f2440 --- /dev/null +++ b/LICENSE.md @@ -0,0 +1 @@ +Unless otherwise noted, this software is in the public domain because it contains materials that originally came from the U.S. Geological Survey (USGS), an agency of the U.S. Department of Interior. For more information, see the official USGS [copyright policy](http://www.usgs.gov/visual-id/credit_usgs.html#copyright). diff --git a/README.md b/README.md index 8b1378917..aa89e7c11 100644 --- a/README.md +++ b/README.md @@ -1 +1,9 @@ +# nshmp-haz-ws +U.S. Geological Survey ([USGS](https://www.usgs.gov)) National Seismic Hazard Mapping Project +([NSHMP](https://earthquake.usgs.gov/hazards/)) web service code. + +Please see the [wiki](https://github.com/usgs/nshmp-haz-ws/wiki) for more information. + +> This repository has been moved to GitLab: +> [https://code.usgs.gov/ghsc/nshmp/nshmp-haz](https://code.usgs.gov/ghsc/nshmp/nshmp-haz). diff --git a/build.gradle b/build.gradle new file mode 100644 index 000000000..daeb2e234 --- /dev/null +++ b/build.gradle @@ -0,0 +1,162 @@ +/* + * Eclipse notes: In order to build and run services via Eclipse, + * one needs to include the following directories in webapp (they + * are ignored by git): + * + * webapp/ + * 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 also must set Properties > Deployment Assembly + * to include the nshmp-haz project and the 'webapp' directory. Other + * default Eclipse directories such as WebContent may be deleted. + */ + +apply plugin: 'war' +apply plugin: 'eclipse-wtp' + +sourceCompatibility = 1.8 +compileJava.options.encoding = 'UTF-8' + +repositories { + mavenCentral() +} + +dependencies { + providedCompile 'org.apache.tomcat:tomcat-catalina:8.0.45' + providedCompile 'javax.websocket:javax.websocket-api:1.1' + providedCompile 'com.amazonaws:aws-lambda-java-core:1.1.0' + providedCompile 'com.amazonaws:aws-java-sdk-lambda:1.11.461' + providedCompile 'com.amazonaws:aws-java-sdk-s3:1.11.579' + providedCompile 'com.amazonaws:aws-java-sdk-ec2:1.11.619' + compile project(':nshmp-haz') +} + +sourceSets { + main { + java { + srcDirs = ['src'] + } + + resources { + srcDirs = ['src'] + exclude '**/*.java' + } + } +} + +ext { + getGitTag = { gitDir -> + def cmd = 'git --git-dir=' + gitDir + '/.git describe --tags' + return cmd.execute().text.replace('\n', '') ?: 'unknown' + } + propsPath = '/classes/java/main/service.properties' + + /* Multi-model repository paths for version tracking */ + repo_cous_2008 = '../nshm-cous-2008' + repo_cous_2014 = '../nshm-cous-2014' + repo_cous_2014b = '../nshm-cous-2014b' + repo_cous_2018 = '../nshm-cous-2018' + repo_ak_2007 = '../nshm-ak-2007' + + /* Explicit model paths */ + model_wus_2008 = "${repo_cous_2008}/Western US" + model_ceus_2008 = "${repo_cous_2008}/Central & Eastern US" + model_wus_2014 = "${repo_cous_2014}/Western US" + model_ceus_2014 = "${repo_cous_2014}/Central & Eastern US" + model_wus_2014b = "${repo_cous_2014b}/Western US" + model_wus_2018 = "${repo_cous_2018}/Western US" + model_ceus_2018 = "${repo_cous_2018}/Central & Eastern US" + model_ak_2007 = "${repo_ak_2007}" + + /* Production models */ + prod_models = [ + [ model_ak_2007, 'models/ak/2007' ], + [ model_ceus_2008, 'models/ceus/2008' ], + [ model_wus_2008, 'models/wus/2008' ], + [ model_ceus_2014, 'models/ceus/2014' ], + [ model_wus_2014, 'models/wus/2014' ], + [ model_wus_2014b, 'models/wus/2014b' ], + [ model_ceus_2018, 'models/ceus/2018' ], + [ model_wus_2018, 'models/wus/2018' ], + ] +} + +/** + * Create war file with production models + */ +war { + enabled = true + webAppDirName = 'webapp' + + /* + * Exclude existing models directory with symlinks + * to support Eclipse deployments. + */ + exclude 'models' + + prod_models.each{model -> + from(model[0]) { into model[1] } + } + + doFirst { + /* Record service and model versions */ + writeProperties() + } +} + +/** + * Create am exploded war file with production models + */ +task assembleUsgs(type: Sync) { + into "${libsDir}/exploded-war" + with war + + doFirst { + /* Record service and model versions */ + writeProperties() + } +} + +/** + * Create a zip file of all dependencies + */ +task dependencies(type: Zip) { + baseName = "nshmp-haz-ws-dependencies" + from { + configurations.compile.collect { + it + } + } + + into("java/lib") + + destinationDir libsDir +} + +/** + * Create properties file + */ +def writeProperties() { + def props = new Properties() + def propsFile = new File(project.buildDir.toString() + propsPath) + propsFile.createNewFile() + props.setProperty('app.version', getGitTag('.')) + props.setProperty('E2007.version', getGitTag(repo_ak_2007)) + props.setProperty('E2008.version', getGitTag(repo_cous_2008)) + props.setProperty('E2014.version', getGitTag(repo_cous_2014)) + props.setProperty('E2014B.version', getGitTag(repo_cous_2014b)) + props.setProperty('E2018.version', getGitTag(repo_cous_2018)) + props.store(propsFile.newWriter(), null) +} diff --git a/code.json b/code.json new file mode 100644 index 000000000..733da7acd --- /dev/null +++ b/code.json @@ -0,0 +1,44 @@ +{ + "version": "2.0.0", + "measurementType": {}, + "agency": "U.S. Department of Interior", + "releases": [ + { + "name": "nshmp-haz", + "description": "National Seismic Hazard Mapping Project (NSHMP) Web Service Code", + "version": "1.0.0", + "status": "Production", + "organization": "U.S. Geological Survey", + "vcs": "git", + "repositoryURL": "https://github.com/usgs/nshmp-haz-ws", + "homepageURL": "https://github.com/usgs/nshmp-haz-ws/wiki", + "downloadURL": "https://github.com/usgs/nshmp-haz-ws/releases/tag/v1.0.0", + "disclaimerURL": "https://github.com/usgs/nshmp-haz-ws/blob/main/DISCLAIMER.md", + "permissions": { + "licenses": [ + { + "name": "Public Domain", + "URL": "https://github.com/usgs/nshmp-haz-ws/blob/main/LICENSE.md" + } + ], + "usageType": "openSource", + "exemptionText": null + }, + "laborHours": 200, + "languages": [ "Java" ], + "tags": [ + "psha", + "seismic-hazard", + "earthquake-hazard", + "web-services" + ], + "contact": { + "name": "Peter Powers", + "email": "pmpowers@usgs.gov" + }, + "date": { + "metadataLastUpdated": "2017-09-18" + } + } + ] +} diff --git a/docker-entrypoint.sh b/docker-entrypoint.sh new file mode 100755 index 000000000..c98e1da87 --- /dev/null +++ b/docker-entrypoint.sh @@ -0,0 +1,161 @@ +#!/bin/bash +# +# Docker entrypoint for nshmp-haz-ws: +# - Downloads nshmp-haz and all models +# - Builds nshmp-haz-ws +# - Deploys nshmp-haz-ws +#### + +set -o errexit; +set -o errtrace; + +readonly LOG_FILE="/var/log/docker-entrypoint-haz-ws.log"; + +# Docker usage +readonly USAGE=" + docker run -p <PORT>:8080 -d usgs/nshmp-haz-ws +"; + +#### +# Build and deploy nshmp-haz-ws. +# Globals: +# (string) LOG_FILE - The log file +# (string) TOMCAT_WEBAPPS - Path to Tomcat webapps directory +# (string) WAR_PATH - Path to nshmp-haz-ws.war +# Arguments: +# None +# Returns: +# None +#### +main() { + # Set trap for uncaught errors + trap 'error_exit "${BASH_COMMAND}" "$(< ${LOG_FILE})" "${USAGE}"' ERR; + + # Download repositories + download_repos; + + # Build nshmp-haz-ws + ./gradlew assemble 2> ${LOG_FILE}; + + # Move war file + mv "${WAR_PATH}" "${TOMCAT_WEBAPPS}" 2> ${LOG_FILE}; + + # Run Tomcat + catalina.sh run 1> ${LOG_FILE}; +} + +#### +# Download a repository from Github. +# Globals: +# (string) CENTOS_LOG_FILE - The log file +# Arguments: +# (string) user - The Github user +# (string) repo - The project to download +# (string) version - The version to download +# (string) directory - The direcotry name for repo download +# Returns: +# None +#### +download_repo() { + local usage="download_repo <user> <repo> <version>"; + + local user=${1}; + local repo=${2}; + local version=${3}; + local directory=${4}; + local url="https://github.com/${user}/${repo}/archive/${version}.tar.gz"; + + if [ "${version}" == "null" ]; then + printf "\n Skipping download of [%s/%s]\n" "${user}" "${repo}"; + return; + fi + + printf "\n Downloading [%s] \n\n" "${url}"; + + if [ -z "${directory}" ]; then + directory=${repo}; + fi + + curl -L "${url}" | tar -xz 2> "${LOG_FILE}" || \ + error_exit "Could not download [${url}]" "$(< "${LOG_FILE}")" "${usage}"; + + mv "${repo}-${version#v*}" "${directory}"; +} + +#### +# Download nshmp-haz and all models. +# Globals: +# (string) HOME - app home +# (string) LOG_FILE - The log file +# (string) NSHM_AK_2007_VERSION - nshm-ak-2007 repository version +# (string) NSHM_COUS_2008_VERSION - nshm-cous-2008 repository version +# (string) NSHM_COUS_2014_VERSION - nshm-cous-2014 repository version +# (string) NSHM_COUS_2014B_VERSION - nshm-cous-2014b repository version +# (string) NSHM_COUS_2018_VERSION - nshm-cous-2018 repository version +# (string) NSHMP_HAZ_VERSION - nshmp-haz repository version +# (string) WORKDIR - The Docker working directory +# Arguments: +# None +# Returns: +# None +#### +download_repos() { + pushd .. > /dev/null 2>&1; + + # Download nshmp-haz + download_repo "usgs" "nshmp-haz" "${NSHMP_HAZ_VERSION}"; + + # Download nshm-ak-2007 + download_repo "usgs" "nshm-ak-2007" "${NSHM_AK_2007_VERSION}"; + + # Download nshm-cous-2008 + download_repo "usgs" "nshm-cous-2008" "${NSHM_COUS_2008_VERSION}"; + + # Download nshm-cous-2014 + download_repo "usgs" "nshm-cous-2014" "${NSHM_COUS_2014_VERSION}"; + + # Download nshm-cous-2014 + download_repo "usgs" "nshm-cous-2014" "${NSHM_COUS_2014B_VERSION}" "nshm-cous-2014b"; + + # Download nshm-cous-2018 + download_repo "usgs" "nshm-cous-2018" "${NSHM_COUS_2018_VERSION}"; + + popd > /dev/null 2>&1; +} + +#### +# Exit script with error. +# Globals: +# None +# Arguments: +# (string) err - The error message +# (string) logs - The log for the error +# (string) usage - The Docker usage +# Returns: +# None +#### +error_exit() { + local err="${1}"; + local logs="${2}"; + local usage="${3}"; + + local message=" + Error: + ${err} + ---------- + Logs: + ${logs} + ---------- + Usage: + ${usage} + "; + + echo "${message}"; + + exit 0; +} + +#### +# Run main +#### +main "$@"; diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar new file mode 100644 index 0000000000000000000000000000000000000000..28861d273a5d270fd8f65dd74570c17c9c507736 GIT binary patch literal 56172 zcmagFV{~WVwk?_pE4FRhwr$(CRk3Z`c2coz+fFL^#m=jD_df5v|GoR1_hGCxKaAPt z?5)i;2YO!$(jcHHKtMl#0s#RD{xu*V;Q#dm0)qVemK9YIq?MEtqXz*}_=lrH_H#1- zUkBB{_ILXK>nJNICn+YXtU@O%b}u_MDI-lwHxDaKOEoh!+oZ&>#JqQWH$^)pIW0R) zElKkO>LS!6^{7~jvK^hY^r+ZqY@j9c3={bA&gsYhw&342{-2$J{vF#png1V~`v3Ys z|J%ph$<V~c^nY|k{->+Elc9rysnh>4g@{9znhgvHh#m?Ei1t5E5wf>;ad!DTU)Ipl zPT9rK$;H%(&e+D#**Qi{+kH_C;R|h2%}C_u2qcGqkpzJo9a~9qYH;Z<LXtgDEa7s< z5{jn{#4#nRNgc1=x%Y10gBE&UTYVfYx$|@Dcj`C#H_)7v>OJi2lcQ=i<|gKQUuNz* zeRzLwpgkbJpG3jTf>&Z%BiYff1YVA8;m#hM;b101PJBP{=|CI8ql`RDKr{(EmI6pI z(@dkm8Zhf7+L4B=+o^=N!x>UdkGSH||FmmB8Bw|!kp6^SHPN~GMb}zF;MN~+$OIZ| z5o#vS_+kVQ1*bGU;T$|^HoJY5vdqvvT{g`jDQM16eiU6^81j~-Sf|#?Ak1Z}F>17^ z@XR5%*Sff%YD*lIU8LK5U@Ef`8&RXp(oTZ;YFuN28BSeTUBb3fQjalWGS<#i%yuEo z%*bAG;X6Mn(h`lVZ;4?Po`dByPNhhz9T|klseNj;QhefEtbe8DE~z?p+EBUA4n}+q z?!P_?3317h!l6@Ki48ZD*0m8Q5rY22X;Yu#5!TNM7>4GWU6)iBPwkEw+SYp<gJ$pK zWnvP2$_lUAdgOFnW-P)|l+B?lD6CW3OKNlSSzeJTqB(S}R1svpvurUsQfogpxQ=6; zUz3Senae(?NM#j)OVxVdVwdPQ!(_7%W|C7_iq>p!^4Z|TuvFg&b|^G}2S>#jW(>8J zCrA^lSf!{Jkgx$m-HLZq?x)>SyA9QN+LOh!r}V(Sq3}SzL1eRP4%S``)&t4mIPQwl zLFtNv|M`moj?nr*y+5pdaPCvX$L$qsInqP*7Ll)1%3G$`rD+Q68;Y+#Kg}tI=r{H6 zR+@!(m45RVoqqI}M4(R37;n!Qaxpq&>eT2u6rULTa(O&)y>g6JwS&uH6OIffYA-&k zbT^f<*apufy?sS=?WKE6USAu+O3Yl2Iz`Op<H%bC+#PK^If@sw-H4?gbBwRNwnW4C zq_;G?9b;A9M!(IjdV$Y*+^ALk!u9cyi<uRgMcP!$#w*LpxK;dpqBiEXNj7>`J@r}P zd&tvT=l5(Y#~?E4tt=Y7V)AUH!;)I`nK}&}(!MMwRB4X8ok3Vb-3p1GscV(2f(3MM zsdl-Xr<RIF_7$!LEufWr&tZ;@lPRUijF-g`MFD2J^wo+m_|<nOdZD1YOo|<<!oriG zPXi1aELK|t&hiS`5WT4djV{xjv0w*1ROYUP%SX=LZJHux(&bWUK|JpbfJekHG?}RA z%wMg9HL!D!W41Z;3t`^uzWu{_@G?_8T486jlJa8m-(y&7^v&zPx|_jq_T8?ER2^X% zqA_?Yv>AoeT+*)zxid^c5*k=-(tF|c)!uNGR@n7IdLso+@Q$dsR^~Vfw}lyqR2vwH zLXxT2WM7EC6wo#8XWm*1xs``gBLqnLB#<WF#Lj&$hr=Jp2i%7{^ydc&8k4fGo(hsy z_#%6Peq9Ywe6n$b`(+ORecpy2Km{BZBd*W3W3Du!cPd76hC^Y92gZXsq*zEGFa}8& z^5+&%K%BGB{@{@=519?}h^>ZOZg+5<P5J|t_z=WfXwVov$n@J(hT}VlDR#^w;Xzq$ z1%_Uc<ZWWf;ybGmYVLl@_rXkDcypz`J92DmEr@cLutI#}kr;f$s~9A(gz8O*yK>DF zJs|x1lpE>&e4hWgfg1bbx&3!o0ISHigBA7JdC3x}q#`h{T>bOn7efEeX)!W^CwnZi z0sn7_tN}<VTj(5~2+KQ!(mx!c+>*s@a+{c8G$#Uo0&fThn9MLX0rZ}R>8@C(5B~p* zIcj)i!$p5D-sQhW{GTsi5qoz#8+$_&62^aByS~w~Py-AIA-fi=TGVdzfzYeq-GTgj zLOLFSYoTjMiHR!S?C5xX!V#1QE1px{Jn64`H>1dXSdbvb;gEp!9UZdgkknwn3Y(aA z0=={&dhqy+$;R72c~Ny8n>hxe*$QQC_E^hN46-UI?)N9H8Yn_y5aWVv^R1qj(8fYL zniycQBw157{VSmO{@2+a_clQ=S^+wf5dRB<4US#8?fD+aKQXR4ne@Q_jlcqbV;sx> z4@Lzidk;@RR~HLYI~Pl1Ll^sh$C?ynU3(-!6kd?zVN**-)%q1FTWj6Q#-%z71~O1% zBO#e2E9Av8N*RM`w=kHXWPOu^q@Fb~WdC3M6CM!dNK#tcVIA&&IG<-aoX!2e-kw1E ze0f?E#QH;n0z*<z>^3xpwV*C3X|SGCV_>&h5yQ+47YA@dkD3Ue9-Kql)wfI~mQ0ix zXqJK`y8hr^K|hAxgrPWIHuewd)&e)-Lm>agb%ESeyK_*uK5q?oncLH%0zXwnfmDU| zY@-fWu9aTC(~e{p-hW2DaS6WDAM-<z+R@?H%^#>=L-NX6cvoU2uNM%5vDRz&%Jtv# zBWdQ(QfY8V`vFt6lVNVJDs$K{$RxavLlo3a>|IHy2VVL)1*yWMgk!=W&pMMZ%&@!i zTlpeAb=NJV(P35)l5hJ^e~)C9z!X{=PWCx~bH5-&9H!*EQzmo^Usbv9E(4d@BrJk3 zPU~wXziRl0@Wzy=q|wEX!BF+Qd<#^O8YzHF`2IM|0e`7knK6mbq*hi{rBb#CN!Nj1 z3?ctvcy}h|%>t&aQOFk-#7PvfS*b*vS%4d#rk7y)CXdh+G$*5pr7T<!zGH%#xyY|5 zY7^u!LVmegr6q&coA6#|<k58Jq*~YLC3PK+z)#Q0t+EKGS4B=>=5{u^=VTk3>X7M` zL~O(nt?0Jk%faSj!f$Z8B-e52qHyVY#}t~zirs%6uuI4jn-(}Apg3G0Aj1Fofc@(e z%F%>0Kw0(t^0RDV)`|(%aHPf1fLRkN>&LKh#2}#yAPGhj1RZ%Ih$#+PuI1s5iqGL7 zOJ)<t_9BrJQs$Q1;<C*~DKiX_^&zoz%xwL$-7M=>Z0q&=e7iXY_t@JW{#puq88V;! z=4JQ&=H^r0=eU!;3)CP<2gcxM9r#=fy?W#GW#wz6m7g$cZ-tuw<dF4`4>rHiz8i3a zz8kRH_m?1`F9iSM%sQ$}ezoa5PzQ*wrM^`dAKqVFADTddAD%$|0lg}dy9(3#884SW zU*Nkc)4P=?H^496AHqQ2;r>d~mnkNXvt&J}eZ717upe0w{_qC0Uq!$d^0WpA{2(v% zAMU6<G2f7T{s4b6v85^ZB}x1840f2Vis08;tJ3wKD#VqP%Ca3+J}44YG5`n%5g26| zWhz)m(0UMkwvtHw1{BGbi{&}7MuJ};O4bttzd%c7l0`#l)Rd)VG_59>KyKJcP~wjp z2a>gyDyU&KO~<U12TH3MkJOY`z~|^MWi;(4Y%jJpQ%D|5Z5T@Wlf{%XWh~iBhLhD? z`*1^TSWJ=#-JuBTxegsiufx~Ji<3}5y|;&6$G0i%mm<991vjPJ12cDe2H)SG#E9Ts zaL#TxN#f-MUB!uVAFkTnr(#R8olM0ARlUD^e7)cC!d-ILwBeSVC-9}Cqd9m?Mb!sz zEpZ*M3(^<4_BRd>V>dTS(AywkV!f{z!-!mR8fMpP7`gctumD>YKEabe=<uDlOjr=n zmts335LYG1ItwLdBdP|oq;Y%584=@1uL^cFk9cN#d9mWWxWTR;@55Qu6FZo(V<CNT zBM7B>@~N@hy_Ag0aG%S4xk_CnVKy3!Td`FSuZm}}V-}XEPmwc-$WBtOAQYc#Djg>c zi1=`DB|B!WDCW%Q>(oV-5ohsuHf`g~TNuL{ZNRE7nNLS>>sos2m?udyEw<5PI5UF` z;bAG~F_edkVR8t`&qWV4^;n0!F@d~i;kgd260)qFdAJXA4@a&sLZmwyG|Su^wPmT! z+dIXxZPFJ2Wy*ttR7MkWt;)F`R@JkLjq1woT9cPf2gExRz8O&su_988hI9BNsOQdR zZtat!y2);uh}vXgTbL?^O26(zCXi{ytDHHGW6F52wi`y<b<mEVhF_OwUZx5sl)Eg) z%!L0inM4t4!**z!fAga7Si*CE+NsoArOvZYvC+^Xvlhn)&AnW^F>!HhHegG=+19d6 z1O@ber1z+=Tt~x`hZC1w7dM&S@4V#8g=}6(2WwOe)#5sKO_8;20>qG6F7AN2Rxx7} zw5`oz9#V@UoSVhW&d>%&_7~0DB|G$|w_Vq^tvega3$=6vQsT;S_E&&~dfgbgrJ>y{ z(ytbvUEsfK&}d8o;Y*ELPajTW9IY+$P^@cX&{yNlWAC>jf~7+OMMuxaP-!aZJ%t3O zah(r@p^B@Rf@nnOvNb1WUy;XQ2GqzBLy|hT1;Kp?5+yo<Vx`zeJn6=?mpIYLZp*Fk zniyikXxb&TZdH;7IH7NbjGOn$Ph;AmsrM8Tu^YD&aXr-LnBY}vvb?wB<E~ha^rYR! z?ZkRXuNMr*@z$;PdD|`akv%pNeH-&UQlzuoau#P444oLMp$Ur}BpW#LGv&;Zhr9Sp z_Az}H2BJJ7LKP0!ttXkUY0eYvq`i*nNxPkbH7z9bL|{0rBztk1j@pr~96hml5mW{C zOGkLk$KroT%Z*72>hiV0pMuCCOlT7D7?KZyVQVMrY?0B1Zkdl$cI?JO(0D4?4E!Q3 zGo4E$<g|<^inc=y%^)507*9l8j-Ei2F06DHV>MsD-AWHR1q9{`y;50@rz<2&kGelU zx;$OMKa*ps?SqKNJ%zH$1V=d%WpkXi8*<B8Ce0AgN8G}o1b2&YgpC{8LEWr=!NCKd zoQlenBTGrQu$_M*S!pv(pg@(~!4T6%a}Lw(i{Z}{E-b7g?&e00xJbP*A5dPB(D*>j zYBAL|`$*_WCk_NxsCsLUv8^oBI!3HpNlMMkcQgMIPR>i&OqCgXwK+nu(@)z~O!|>s z6cH_>sTNXiJXTB!KS|8u{5|hG4O8DX$sKv-q<tx6kU&svHrtksAJvi_I})b#w3d4< z8_Nx9#3i6F3O(xiD-E$G$GwzWaw(g$I*wh<Wr~vHUWINexnKH)3^O;}tLXlPFB4`I zS&t(F3twI#w1DB13=(5MX!N)17@ySH1c|3K+qfzgV1Y9CY8p7_ZkwCdQ-(BKtTxqI z6Q`-Mh=z}L3})2KELx<I6eR1%Q)H=aj0yD$x%Mjg;NxY|=0h(mn65P4Sf>ONJQ<?8 zRJGd-O<gwp9NS4EQxrGojgmS)tnynTEoijI4^P8Vchr!Xc|~@{go<G`lmS5#ra$Ya z(18Yy<5ATdOyrtOiQ1@1!E-XxU6Q^z(^ZXhFlG-Kk6u4XaP8xmZOV*frclAyjS+MB zinJP9bmh!s)U!ID%`ODK*VYTlvzC_6AWPHJ=AOosXOjJ(FHcwU3f>k%(zU7zeglNW zY4Tjn6m`*y)qH1!DbZ?}Lw|RREGz$Bsx2rL{nFLSw=zUcuZZW0j8eXsK~JAuPO%pK z9Cu@_riF^IQOt5mVRb${;38s{hFhLDIh}%4(TIDZ${v?iQa8%{V8w7$uSk?%|9I~) zI+JCMPCCX7$>J8XWiPbB#&?OdD%;M~8s;jo{P>Y8kWA;!3wS*!Ni;#kSNy#)O|=Y% zr^2Kz)2<lz!(y&AC&#KZdsbrxegsKURdAj>p<hwFAin=AKxAR^!3#2OJ-0^3w9%d9 zm?BtBvSqeKe8feT{VUbT^pDIbe4Fg~UYYE*hEr-)<8b3d=}DVPi}{VA>VVg)wZeIY zqG*Q8;8mulHrYXx0Xa(=jkeZe&xG>&;mS9^&@l!@-cc@Cr_>cEr@8z-r86GZWX~?v zHAYOHbau(*4W;2|5~+;#g=Hbk3g3B!{%;z}k^-+>wkdpK&!gF{olEYM`;^F@4D?8U zj{Vs69U4?AjmlssO{(gCgx`b?d!tU-{hCk4Kobljj$H=X0t&o1Yw(qAL0?|$^!f-N z;1b*c_cr957vf+(A8KqYQp)!zN1VP>gPHZwwismV`~!Nzp$PV)+z)m4RIJ4Fyu+0; z&nQh!(+Bf3QSQ#7pTG{PgD4YNSak(m1+Q2>u!Os;Dl9CzL3z+4FuSS@Yqg|pt~~a< zRu0%``)<JsaVcy0rt8_9jP#S0T{KnAnWn!_tu$5SC65njcI7oI;H)VFL<R4;sO$?* zz=1)SLMkuolWC5%Qn9*wBiJm&aJ5+Nshj90-vdiixa4}E(Z`95mz}yD1(mUuEthcY zEo?Fe0x@x!Pwfsm+#7o}^RQ=Z#xI+J_yeUgSAjn${Gz#cr+ea<eXn}SN2`5EgM>b% z>NDlbS|dj;%VmuXv%bLtLD&`81xBJu>)XkX<c%e>>IxW-vIdkgeKfNW@4$o!iDQll z^|7cosL)mp@6EC*#M*2iRqSdix3q98e`Z)#<E$xZK9P*n8mnSe@dpm)iE^})#c10b zTnjjCqpb9H6?-`zlX_b}CQ^OYXeqV)Y>QF#+k<3b^MO0=e`8_8SxuT*p_+NICo1QQ zi2_MWRpE~V=g$;2dp($7!OF|<%i9rtXAPsW8-P(Qo?q}mhMl%-<_l`Eg_f$rw&HEx zJ3e)p>keJDY+MD<eonVPlwqp%ZR^+*54&ahLx7l&gIw!zd7<YtVsFmP^x~ck={*?X z6RpJ=bM(<%nT+6?gyR}fxGqxiwofVi7OE_B1f5-A?G?J9PkdvA*{k9Rs5>O-2~d6^ z`%{Jj^1^ny(<iyFEJ)?ht5M(bzAG<jbqlo<^*mpYb*uQn5wt=uD*Xako9Pjs;g`{& zCutXVK)wzgze@Z<d7&a2dkSE_QdLe;2mJ6+D5+989f;_*;b=&MEMuw|IDA2v5DjrL z6Awv$Vc-**EbsdwqAK%rXbGSO_gm=`hyBP7Acx_x+%pExWtsy6B+RlnJM&S^tLzHC zvyeW{l9KSqDrXg<mU}YZ0xZe2ud`FO@R8~>O8H1<X(h0fKZS_13c@TzQ?#t@_F@A< zS=gYmWIsBR__JP}(fwOx3@?{`X5Jn{nZYpX!TIHrX6j$Sl=y<=%`|<upli6XvC16W z<?pL*dC|Md61o;FI)SAjZrQ*clpWq-uQ1XFX7;)*udE1QZcf-^+{{j3&`cc|<v5X8 zmN{V0jVOglrX02er?UFbay((yGe+ZcUPQUl#f;9r6Gw)jvFavw1W<jODe8u_kN^dF z0g@3(6?xQYma(xcPcH0W&!r9Y?Y$qOI1)uoWt1KYG4QV(cocQQ>cLI6J!XW0?pVCG zsD%3EfmPce$1(kbmJf;fr>Hm`6E%n}k7w02gn7wC_V?QY-vYPkfpv%U$`VPCtE0V$ zMsHw#%xYHowgNS>;IB-fp46z;#9B{`4MZ{(%rd3WGG$RRq^1q;7D1-PFD!h6$XXR& z^i8LSQ%pL;&JX*TTAa-834Y%+$XlaHt%uH6ltVq)ZBM4QnrJvj-msPv<u+U4jh3Xj zv2tXC$xs)UixvU&{KJsEleDvkAy=rbA;iZsG5u|T$O|~Q`fJPI5z9jzsO#Z`Kz}N2 zr4L1%$&6~5dgSz`N2ic{<^AI6e6kz;!y_IH1GiEFHP+M9{EA}D&}fohLVIRug~m_s z8<fC2k!7`Z%GzIu^d=@=S(y|IJgGeJNw1)C7Z!HviedTsTBp%{6nv=yTi;%S%B*Ft z4a|PgGwmIg5i#{~M8LcVUttpZoAlX4!z{gJtzk?jTw-L856a;22y*5X2jd$X-lJFt z#WBP4^;7Lz)@%ku3utbCaKxsynA40*QERmk5bi;1wIMtqoT>OCnBn<GbVKDff%Q)0 z<|X%*a_$b5(-lrIOj!f9kS+N~h#|h<{j=uv43H@-_*?5v{58h^x#s=bQ!#fkb#|7q zbapYd`>*c3YfL{>pa6>K4fUcGs>tM%=$yc2s%ZRAQKffD{L*k@X5%mID8Br-NR|yZ z^sr9O?A3PwX#GH6&}o5u`cNgE6Y1fcly=6nEE?o!Fo0(4NH;RDh9mFEdN)u1=b(Zr z*MV*(v*GX03h^4G=@HP12Az7nRx-l^7a}Cu!)(zSQ_V)SZ<K+vtjVk{4jty$YH0$O zxmX+_bh-CA>$QOQAOFNl=~X<~1r7uh0RsfY{GaiPdKlZdI$OG#idov23K|>#g)D1m zXK4Okh*Q)yow3z1zi~AeHtx9GwuWjlH@PIW$0KT*!IVsp5855$jkzt4(tkrrt}aA$ z1FY1m)f}g46eJ+qfJ;Kyl3V8%_!x35&C3(_0&YQ>c?NIMZ`aWE(gS`xyStH&wgp#+ z^Lfv>_q;#9_iXom+_?J#-TvH>+at`j><{9oN~O2pNE1LgW#!2cz%gIySLr-ALs@Dn zr%<9rUt%gs)r3`JrmMWx0miLIR#9EpV;Ph+s507(bOP27F0-S8d?{x;Ok7~!jh?L0 z=u1O-Vd_cjQwOwQEa|@|4Ayvn>#yFz!p>T~lnRWVMHC#KhB+6B&z{P|!=L7&oZ)m^ z=rJ+3o==(F^_X)qe*)VI*D3>KNAp;&D^V-}HHj`&UmBtUN1$vex|=hcJr8sltwbXb zG^2O$kV8rxI$lZyTt{e>YkXFmPF-4=sXM`(w$i4vwCPX9=b9Hf<YJ$iQCdtzwd_e5 zfT==S=KEYtI%#_62dDm8wQ6anwZ>zE0s`t3#zjW+VsY_9GXVq)nGi<}J2AjxSXrh0 zdPd+SN@XrNE<dx$Mr-3*o*v?3#CcTX9<}M6)A}76#=%eB#_fUZzNh&4f}MftbB={t zP;iXlY^h|Ob6n)64QSL1&4Ntb?@{~!GgEq%#*PNxt-G^~DxHg4nX<FgffwP{_cYra zEoX68t?f<I#Y-X#DEOmmE#1Trry?5fn?H|zLn7aVDZyR#XeT36P6Q+1Klh$!2h;~q zVP`F0S_J}KlTCMt>ch*rSP#?vmWvV^0wS*7tZ?2m9$|PTolDr67xD;nMrk(H@~xyw zG-swsoej0%*6l?36kCeznagzBY(dcpnSSo13LR27%!2b=QGh4ASLqe#J?pxQS>`3K z&WBZTJsI}K>RqAFsf(2za=+B}bz5@-B$gYa78U`#KKi5Zw>*F)bMzCJ4+X@xTVh=P z5oj*I!c=qsu%M&%Xhmhwh8yP%FhuB9r7jE3Dmzpzi?3y}Y>If%8c?QV|04_-{~_=v zlS>y0)>}oa@)-1%JNX!-NS7xr|KMbGN36Po>?o+5^~>K806JhL!XX&r518=q9oFV{ zK5~erCd-NJq<L4s<8dt0zz&-ay91duf-;xhD0Xmito}h|sQ$SJvL~$l`3q}pqefei zKBC*rXeQNFON#}{nxpy)>z|t?<CsPp4pB8vUdGFg`vR#0Hztw$@q7uxRBom77Gg2t z$#%_y<5U%|B1=4VLT*tqqLweOZV_BpE3#KR38b|qL;hMN5&OduP9aL0-(`UTR;oUD z+BFlAnk#Z)5M|W0x{t?_i+QMPzY~m?(g;qS;5xDAw@s5Rd)g4lTu*U3E{i&iRaZ*J zKZdAUlTFz)twc0;b0@ZGxfS7Ly=@oL^vN>GZ7tP~sDxibBI%`Ns*Sm7t$xClx*mr3 zf!;%G`z-Shp?e}HN)W;Z;N=oY<SWY}{o!6_*k_*T<TGi<+p~=1k-Y%PKjjjcZG}fT zU;p}%xdR`J4>we()7kMy4Eo6c`RPs?oI!|@CsICGA0Yq}@hZ9C=X2gr*_bGE!Y*+r zn*dL1_}NkqmQhr=yl&Wtturib4kR6GvtAhA&g7;I3uaBhH5Q)QtZZGrD(_}pfj1(q zvg`WHGzyWsx$sl2HW4=RI*0K3!o9XgZ8`*Nf~{oh2WC*@N=f$%6&#(>rHZ}zs_Rx( z45=~eR$2`CAu9>UNJ%g0A-jV=(?|$aX6;sAt9$BKxynN=OLq=iN(7dh%bz2^T`Kmc z-66UF8zRX-M2ced068v?O#vo=UaPBd?uxdiFIbUZ)ay3{AIkNVVdq+PE=6Rx1jMQD zg(RG6-KhpO0#qj?2w3o7^(3d-kjZ@15k-?1>dKX-+NtNtDJjm;+$W2<37UNoes4dJ zRkGF)0WIEe7)Pi-QJB9W==X>tjiHK&gOCM>Bz<fI-O&(Rba)O2-P0}IbSDT0roj;n zqVkBWmIn-0&58xNG?wFBAFTUlGezLGxkGsSx@=A!X$>Uhyr4Yzk~-s;oPR8WsOSf( zutzq2lQ?B9y)>Ni9R{VR#rLowY~G>$C{k;_s4yKzY_JIIC~LGBYxIxr{scbh!55@X zvCVjR7#AG!3*UPn5ak#E==W=E)$<&2Kkl3l$hLNU=ffYT`yr6Ga{^4SF=cq3f*lXn zS7#rwK)es+4KF*Rx<2mk*dBSO<Y-%#O_cIWAsUNyikW!7Sa$REIQcq--B?MVT=^78 z*aSvcc_w}oMLga|#D50EoTqV6ufL2+=wC+VzoG5L|CdHU)!EcZ!rs<Y#@^oA)#2Z? zz4E5qrT_vjG@)B`#}bPG6pEC#!~zn!g)}`%MBtAI2Swn7JCqC)RG5)ioUlkgReGd+ zqQ?gK&(g|^YGjH49s_CSrlLdVnQC|c?@s`79}F7G!$CxJ859`<C1EW=aBy_727gpO z#4M={W%y48DHGih=!7!TDT#Db3>`K#H1|dBkmacZrwxiLvltmeTkAoCxdn)mhKkKn z<&~zt;pzAphM3(k<a%nQHDP@meI<rRcs21-#di_lT&jCG<PU9cb8<0v+jXq*1uX2% zlVJRlwuAI7pWK$yr^&5(v>VrX_GBPTo8>zDT+?XVBJ{(zY9d~uQ%{rL+id*gjeNFR zrM;{Ud~%!Wd1Z?@*KK=HE2P>zE$a=Y8zAB<GP$fylZ9MLeVH{p2-}1-CgCwoFpGX2 zsz`dd(nW->5voC*k-VooANQlM?y|%xSmGL4WPlpAj&U?!FAepU9kjPYnQF&KZkX2s z287*zcr?>At$h@sqfi|H#}Zgwb}>M80thg?i{%!9`--x;#=R}vU8=lfYm=+w<2O2^ zarWPIj#%e6Ob_4X<A_i*r`&*&ASaY@+2Sk}YIl`_QFbr1xr$f=+3jxj=HC&*jytn^ zSirRsm$6|`WqeR1<5{+S7(wW6g+oq^*p+gO=i)LI>mc?7e`5VLL=hTfh5}Df=?WCe zAj27m$YbO4!ASs8+S2OWe7fo{*eyUIuY#-Je9KvUl1kAdh-Ny-I3@`(Y)B!p8KxL% z>~cI>7fec0L4JY-JGA+gFF%kDo*~wYW0a~BWqt;n@PUa^lXR6WwEUYQyYQXcgb}Ng zO^bgRV6Zj%{lBSS$o5CkUjOP&x-fu%sQz<C_pd{&{x9*$|E)jyH+`c4;e))2`OQ!B zessV7%Z9-S3fRMt+7Qe}=wb~VrV(~69V0Pbn9nAlnt35&zPgshGK+4ZRQ9)dW^H_P z$~Fh&xlGod?NaZX?#<}0-5e5YoIQPijhjEdH=CEfqsX&74-~&zjG`iI+NFoJy5#8U z(k8c|{wOL^^_O@A1+seq$mn-y-0c+sYF*P&ZQcMlup1RdZKLUt&ZGTK)vc&!igtO? z7!bd@NL!D*qgTHU9|1ai6-WLVDi4AFAUz+6K)z}tNWMaX5+hDB6`GOaz_>~c%8sqL zFccY2Kz$?^PvL=Lc9MPE__49mYdd=0?LiV%*Gux2zgGVt6<^S7r3Y}HGQiVEa2Opx z3Z}1ii;9|ctBR^WxZ3>^TKrmyzN>U=`}&6K`BKdDQET#0jJ}%`-E%VxkMg0g;gqK1 zcQkx`_i9Y<S9nnNk`b8a4S<&XkVfqts5`#HM(rKGQTf30SGW~N1$jvlxPI!s&MBxB zet+wH!rPc%U34wCe-?1pulq&sqQYMQQRHPqvSvR_+>pQ)FagJ$TK|yFS}vXxDv%%E z)nuLD&Aqgoajcvpw%%0NX-xpFn+-urM74<&AzEDnO!^2L1e^=!oW5WdM#Nae&gr%m z4u2L_6socSb2%@_i<j`uGG`nkh6G$CZ01A4fwUS{Bz9|}gOAUC(0~2p*4uPBPt1sQ zjCrVR;P=^Ylw^DE<rDl-sT&Kn#B!SBrISC=AD-TCn^4}rSP5t!7p68MArTRX$SPgN zB(z&^$CJnK(M+f^=fHw=OE)(5veCfm+#De+x~s?J78FY~5GEA-J`XmHzMhGkFq}&u zB!(ZxX(Nixi;~cuae%{}KP7=$TM&))m83?3l^ZO{fvfXXaSP)V<DM)$84Fi&OVCCU zdLleGD<jk`i*{IGis)T{_4nsXb@5)Us83)l)56E2dkE-jeEYCCx-l_O6^||gEC4@* z4rt8CS|?E(%lRT^vQH!@N184YnrZa=dUtw~QJ72O)qlH8Ey$b3B#^GypYI#<zP0B} zyG6Y)YnFt-V*!GvJIRPU@&)8Me#)gN^tUH4BmwH@a|vP#;vlCct+sHn<+b3_i#PU7 zNVvLIe_|&%Qb(CJ<*mr~>#upN1)zSU$ch=*ehxcVjESqygr5mT6g_RKaf-6`mRD*Q z3&5`KX~7b=YYxh`D-J4djitIaSS{YNf8^v+KhO=1?&5?sb4pH~D4NBF`tRjIeU<F* zzQ8-iLMP+Ni^2SM*#1^5!}^LUW8sN0N8zbT$TK*RRo{l=Wt`ZW8=_Gxgp8wDj_<>S zEd%JlqWw`3$sj}7N7Xnx=&@VxDpFJ{nKUf(WI|(oG-QK1Jt_`GKV<V#Og`<T6>iXO z6Wc_FG>(qIO7p1Hp#r_oiLWy{l-Af9dtn&0H4Y)8%JA$s7j(v*NIl=7TvwwsY9%`f z@5sDmEG*2djKJC&(Q}3!#MP%%NRTEviFi${P31KuLk}QAvlyU9qcTb$LyIDf)ToRw zCCU#!&eR~JD_EpcXn%Ni>A8{}sUAyD;7zuwHo>$uN?BTU4mPtgYAHuv+b9?{Dn-R$ zJBwu`6C%J_MvidwVsjXZhFG`&_vi+V9hz<sNN3+S<TV7nP$Rw2x?5<zxm6^#u`JYM zk%)-Nv`9So5bUhaI+Qa#eYJ?OFthNU{uP5HQ3T2)8$?B7JeaI>xbn<8PZXHhuA)O$ zpTM(FLypkoEl<E`7fdWpG~z&j#0~$A)`~1!J=}7&Pq!VKaLJHVJo&6JLT>3vyRhaO zsZkdJYeYP$s8bs*o4FRfi84=hd1%J9-!(0w)Mo0$fV&mV^~%d6KOQjO?zxb`Ua6^c zGVa@8%&4ZIf1;$Nxyz6g)jcJX<<)Wd;`js2Hv{_+7`KLgy30sKzIjwU(O7Kice<5k zkJAYU5~k#c<zZ!EtgcOTmy+~#g<0uJdn_)eiCgbc37e5dN86vkscm(haEfzI$-PR< zEk;hAHI+>)s3#{0X|3x<!?z1)N6V|os5mN>RMW0r2PX%t?YF`NW3eXr9#b%N<A&Ak zu#Jw8M##=TfzjK4lBRO@y)xZ-*MLz5TLVk+T_)kx5oc14y-wUp)|}`ZaX+DS=gtNi zZzFg5RU#$?D>FGg0GLf2L04PLht=HVC&%mEUFNV=>S=>zXzU|Jzq8E`An|M}^As_* z!TWw^BrJTaFV4Yvo^r4)a7DHK<Q6D9_d%~2Q_&7=h!U?}>=(j`)b%oi8HK;2p2^sJ z`Jpl7`j-5GmVFc59i1(-j>*j(z+J<wGB+V?BbjR1_5duy8Am_ns@7Vxxs%{{0Uw9; zG#c&5Qz3qy@7eJ(<GQ)x@atzafH5av^XlL;h>pcBA?sAg8a*b5aittNuUquqCkT7n z)66H1d5^Z-oi}ZPs?_`1(oZ-q&%NiaWWSv9-S04Dk$!hH1YKP*$PB~7(Ugu+9b*1n zTPLLp<K~LB&C4wPqp`?(;rnKvZO?A@9TYC(q~VBzSI<A+5Mi<bP5Xz(-YCJ7@SfEl zvx4mRg6!^z&wy-S(q{<gC)HwvyQa`f*I>|B6rWT!IRPGnBAf#)Gmx|cuiDHYAl$H5 z8gY!lA)*EjVMo+pUbYC$f>O!k2M54|T!D)PuxSlmFFBZL@2>LO&n{uop1Uu?IQeV& z0wOS5EFH>zRirL|s3u9yvX&)%D$CP1-WbXktw}P)?aCKap~+GO;bc$BDfxnx*(9(U zz1}uYB)<;LHLV^qq$n-b-VKhBVd1YkN}Bx(ZLSDY<exHzukpLw0jS9BCQ#Jlma0%y zW!F29_^YZe_f}e3;_j^ZXfYarWYwcsR|3#abC&9dOmAkmFhcT%#CH8;Tajv80kj-J z)DCdhtv4gM-v~G1xLskdjGR*bsm8#FJ>$Q4#%3oJlNDxsIYKEKp8AF`j2>PeKg<)Q zF*$LD9ES=N)VReL6g?<ydHL59kK97Na{44k4wGrc^TTdFN~Oa%W%Qa}s*A|W6xf%} zs`3N|NzQgQSjs6OSgzx#sK8wcv2<O=**42x;i$I+NDX>%TVj-spB=UKLS6J!<8_nn z-CGGde>*o;4Lm`Q9hA~UJ+bK3)Hpy{zgR!DyaZC}a0N_4tv?>sS4}q_ws~i6qv(=9 z?r6reP*zJD`a)qVt+ik3sf3o+Tb5e_XU!^#Rn^gk&^{XkfWFn<@&wihlg4}|wL1aN za;B-3`U0!xw3tp8*wdAz!L5T8Ib4(5#LxX$GQd|h=TADbQoH$~JqYA@dg~6IJE{vC z^z761D?2rx6V{v1KZW94{kE`7p>}Tt$aoswaulH<96(DtK>!PIEuQPB0ywH{Ot^7k z*%|BE!?P+*<I48@L2-Mk9yhp3#IW}AM6u%Aq?)8Ydd}W7&3kNwc*r4sHnk{vHU;In zB6lFAE(*KhzAiJq(&EDQp)S)*xq@q%YBZf%OqqHl<Ki1burZTrW-N1O%5rB+DgnWw zEQpYF;l?G#*5K9)Ohf94o=amK1Y@K=OXgmvwFo?=T%z)LUDT-ELxs|Nt%f1}u_Z|1 zvE1|kf3#VJ2>^}ik9djK{TVG)RL2vt?Orq@>1+2?T(2(Xfb_`}C*|a{T_`0<W5OtV zx~SCo6W3LW08J@6gnQBGuL4Fz*6`OBHKi5sJJjNRD2|(|r5kWvLxbKs|39n1w*!om z%D;*8=3gWF&k3~Te~AbGl{kz3?fDKcz=VEJou8l}c0uVq8uee+Qo(=<&U<f9tWjK- zFTUE~`2gWdU8$H6km%o?&fdHF18L;n6kHM#yhPL{FYyq{lxAh?b1D~_gz2LAQ7+o# zNFvU}1{=6Wi7J|D<?uex+&m5s749H|&)=D|*1<y>+bX4E<x2EZ4MquJtXTXUjO#w4 zGTl_BJ4j@Bi_4V91@0HbJwnoQplzdFZZTeL>IV6S{U=iHO>!Q82p}MKg#R9?owJLf zjm>|FBy-eX-LchCzj9d@DDK)Fx5z|g7qBkK8kMv)GlMyxC9jh+C*-U~86`nnXk?2c zMwyLRCX`YelT%v|S`QlQ3@KS?8xC0JfJ1;w1fWgB^k30AAhh<pnG<OyBPDM+d+c|- zpSK@7pLu!TZ^&~&>k<8Rg`8v(B_(MjOGz3?9gWt410&f-5kjg8F@#~jH~~lMl#z!{ zJcR0UQchBd-hZin7|$-&(6;?+#Vu;}9YXaT%;C<X1ou$~{2gNTKlZ~Q9(5)jdD%U^ zsQ7&l+5kcxit(_bmv9|E+(B_b0|p;m$}s>^lCR>RfPxQo*aZb%9B_{D8-UpX(4@R} zX5_l{MAcUSh@$EvS@73t>!v2n*9@BNvn?`#)=J?o#$8e_N{+v}1*nZDu}1CuI)~EH z&FMH18E3}zo@%iQvl*0*iGjJBV;WC&yecxQJ-SGg&*#2w?@*apZc0ty+P?@1{HqxW zYUs^PIX#TA61#sJnbsDQRtClmV3KZgu25uJ<HQU1@N8OZ(KAqGrVVnNU!;}3yJoOm z+$43-H)0%l6|`Whdh=SBqiz2Ep8sp%#su@R#Bz}>R9YE1)LS4g-t$aivKePdS9yjy zD)K=I2zVpkRyn8yJqldCR(~j?7WP5AfPt)%cYZs4H=SLz+>}2#MbeJ36SNi*1Jjq9 z^$hc2z;T>ztfh<0*kN}k3A0FHT+2qvog9`OVc85@td(OgyPj5j_HNIxu&f-P6&!26 z$WxBc7KfdND7vS4l~OKAUF(J`mb~7`Peu;4((&AeqtUo0sgt76c4?70N!Y8Of8b3O zV2Y}*2vALhk*#}GQ~|Jh>BA=H)%zlkMn|)ljF)FLxz-&io#%$YxSAn+WF%f<wWeK| zpOq|-sSS(eBxW%NhFhitKT5F~8P(-9WD_MYnRs}TR~<x$)*WJFx$U!Ju^C5&)+Bae zZL&NVc821vRT#4ONNPS-viLyPA-A_82DGKhP+PZS;q|w%`ar}X0d>z5hc-F&V8>Z{ z;Os6t$R%QSsEv4{Heu22K?XS33%c{dq8~p!-}+kBlx7WZmkg1s@|5gDycC4u?^~ks zuiPT@6z%`53q$h`HO&MD>2Gls^Y_z~X6hIOvtck&_azC3h(Rvf%P9V=dg%QnCH;bS znLM%dhHhB?R*eMy$UI0ApK{|9ZX2<ynvFM%&^e$=Wv31Z-N5sR!t`8j_+a0Pzn{oI zGzayIA%mDSs?ew@ChVrV`z}P2^kpSW?CZ^E!f2@d47*i%?;n>u-L^|&h)bDj3%va@ zAZ@HSPBPib!Ey+b<8do#%{|^-&!vAUrQ93(PFPeYbg0poZdSkKiX`Q>8B_oZ;YEAN z)sr|F7i!Mh+T_-lIp#;g@9MOshik%I=}2)u%b?&^9bvw^($DstWkf3;(Kh5hi@Zg? z`y;cT7_~G;)OYNZP4uvzWZEo6ysnD7A5LSAOPygmuh_+}u*n-QZ<MlIZ;T&*)Xzu@ zmnhPvhQ5%7Fpi`SUeVa6t_3-h*(a0Tv^yn1R3UEqMZ9H2S+HoocNGv8-zyVeM<R8L zT>S`xPXafP98;OzdFY+C<LJ6Ajm&IW&`mhA^9!l;?B=k^HgTnD<yV1i7sZ-$CIz2+ zYf01G6%<|4?{S?C;QUavU8k|_xM#AdfapI)u~qFyt1x)~{)D<0oi8AqSa5cIY<!Fi zqR7&jV-AZ!c7n|DA-6xEzC;t**gTencG%Lw^xS<TuG8^9s=18oN__<(8v5Q|8(eI= zv*y>zchX7HVFyX*@&u<H&&3Q*bTkE*V#3Q6<iL!ZB1q(ce`IuO&Cg>QxbO3ViMRTC z#=085j<@IEkv}SYP{1&x)a~*>oEIK<wxBc~7afAj5w~VooUpiTk1UQZIel$Buqux> zUDW8VjgGaf-V2P6>K|EdYCo}YXgoA5pTMLj$jPQ|(%|c|!b*y|&{SMpEE`H;s>MxP zFb70JS&L`G@S5s~molk=XH^xyv^)K%5)P*hXuce+GMhdK<?~NVbJOR*>-nV)C1YIn z;gzyCNVI`&so+GMGDQ49T3=d7ftMk=`jYX@qndz2cUa2QB;@;Xda^MgCY{gb2=4wI zf-OQ$$yBcZb)$hUBb;(ReUGw&dzpZyXlNfph*!ITcyNLx#yf`!KT9Oqa5;Lo--J-8 zA05v46|C$dv!-$WEg*}KwHZFmg6J7+F@+T2X#`+NctL3Jh?VdO)$qy1c*U0Q3I5T5 z<vpymMV8pE`)~-y82Fi-jdqBE1aG9l6X|>47#&{5NR>PI0{{&7w#GeyUs^_a31_5V zQ0%(&JLK$x+dYgSnt^mH#COP3<DfopKG*})JyCuyoFf7;8mUfGvG2zQZlUaVAI35D zQY*~i5%l3BYBq#-SotM`x}DGiX|d|oa;tV1jC?uA9aCzRM^GgD7xL$*O4qqQO#ilt zs)vUC?%+ZC&qxr;+OYvhW?SqTIwj5h$YoZ>V$3{#=t2BAqSKpW!-JNO$OLQRkKS+K ze}?aS(?=V+zkk%3Py+!G{5Ofpzry#w`+J%Y1}ew6-`~!My0H*K1bvM1CMHO1NGPy` z5-gx3Fd(Wvl6r|j*nmH{Bvw@|8r8Zhs`FeI1A?k5NDRO$0oa>XX)RjjHJvTBk)^%g z&wuFBju7JGZ{By<zr20*bKl+qctu!ZdT*7e<C4viS&B+9iKo|WSo|zwQ)eCZCQgl6 z@NNkF_6g}dJp-#{lg$#-?m6@`f$7nm64Gjqj4a8lbq-1hc|@P&mpwAW%Dg?nXO}!u z)5LCV=<z3=O-fZVJFxIiOB#Hl)8bA&^{5V!kM2b*CyFz6<0d@=Mo{te$^nv%#tCU; zjwMrhSNqDvbx2O=lUgllp#UU!`joFF8@&-$J-r#~E{vKbkCZes5cCzfq*AraWnjY? zftT1Y`LqmX7QHG!1|A!a4mt9p9#;Z(F=O}Em@F2%HBS6K7rpA87d`Dk{s<vGE!tTW z$JKtgPqPHG{$W#>%AjJ5v7Q!T_i>4;PjuMff_=PMPa3;ZRoEtvPb-4A99!PxE^2De z>Hd8&zdprl&j`B5creENM?Sv&0d&c0!AMqjbF8|wbAruB!U($chcUgViG8|15riL= z&ezl=|EcuRJrd@p5Q<a8NE#Djk{%{p*8E*QVpIkr*=}HDh$(etYMrivW|NQR@dxT8 zb(%_@u2!oLqO#mFmvN9H63c)rDXSU_f?ek1QZElutfttSmL=$6X&Ak+69loWtXZ<9 zi7Ls4$^r^2R-oDVNcsGNk(hMl5|Nx277_MffB1YK3)$kx1XzS?|ALXmv?&3j3OdTz zArr1fW)DP|SbEK4zXwfyzYfOFOw-3lGn&@5pV*PDi82UOa9%)rTccnDKdkW<|6YuJ ztL)BB7LE7nOq`R%uw;vhssZ(yxbVvv^T*(5q96%g#O({=;78ny=gpZQb6e%n>7wlY z1m({w;<AHG=%7Yui`mgQ)s5a1&5Uy>aad{uNV!?|)Vv6kh#BEj7mKSIcktLK99BSY z7Ws5^yVQk(r9aqS>Mc{MHPj+#JI=MOGGi>6&6kISWr6|+-U6FNW9Ua+RBtRxF~gGY zUiiv>X(CTS1J9!<Zu4A1y<P}J`3R@hBR7f04zu(CTlE|H^85spBJ$AHktcu1>>OIK zX=iZ!+Lf|sR1BDf>L(T3+%z`x<-w}okU|?oGYp3YmNlD7Oo}Od*g}b&aFE^t)>-^% zm_i8duG`h1D8p+#?c<@Xi`{Im0j|szzk$L4dn3H;<0^%sYmE7LiH=P>F@r#lu*uq^ zbf|CT0#V2TOjcbx-aIh?OFeCo-$1LIKS_j$v5~ANbVeP-_ryxG4TP57@E82>N>vjf z0@y6bHL?bLstQ;#L+H~(RBLLn{fqZCZ!LMN=a`uK{tI~4M{rsyd)DKnap7Qwr!OQQ ziLiqKt%)^sBiltyJE96&0&dh$(PL@jyPuhLl%{<Ktkp1bvn>49D|41CSDPF$7B0NG z)}pq{Og`p_keWf4SR9DHY(Axp2B3Uh9kILr2@yty*h~wxrk-Egq+=;M6u2RMji;-Y zy*VY2HI<2cYSYYwjfOb}oZDxlI#gmyYQ0*hn*j+HGqr?`Bj~65uSKP>xg4_9lKF7Z zgI9pST<8$3OwhYsJZe*zG>zoz`BpMzIdY0&e)Nbo!S@5L9=91yWH3-!@24UjWJojv zj?!p^1j~MCrQTX$WgtQ#?;Xz&Zg>q;aKaLU+tKk~(keltg|NO6dn%u@pFLC1ZLNIx zfNK30h>zz*R=?F!@Ho6)5~EcgB8yktI4XP|?k|=RGnXcp>-MR7R9k6E2}pc#X@o^8 z6VX7N=A=l%17%49>4g(gIjHhqDA0oozf^+{37JvPa3g8VgDBU<xVbSo!XAffDVAQW zk~t|XS$f0{_4`{diD_*u31cjs^hOlYG9)(f2>HVrIm8uA&RLVAN98k^LMo_?!DUJ( ziQ%*~Ym|#KsHU6kRFuI~PfW5zQW$+pt%^zVErHM4i6N5pgh>r$`B|!kL-R?hF@dXI zBn)c)@bM_a<#}O*#j$*twaDF!FiF=>@fx|7amynuT@jzC!L62;+jIZQU1Qg5J%6CN zUOg9nlPKeDRxk5k*yQ4siaUSs{Vh;-f98|3Q6XG5?L&)zuh>r&R=apE^j09ppD&B0 zUw04tVVz@tl*Q7c$!9nJs<u;kZ`*|$@v$cplSkEOxB3d#gG^KG{PVO{Q$;O06Cx#G zX7h#J;qC#}vImNw8Gkuw#vQQDwL7@!+LLx8Ts*E5XyMa4!`-fX3b#{nPv<9)DVKwf z0$01>$=)3yGw<u6$zua1WJXoUoa32jG?u5Q^g=EhE_=r=GL_Dc)9_JlMoWzFguTyZ z6GcQWPSP&>q)vj=yc_v~jkx-0M(yNTKh4kDQfJFlnPB%JeX(Mwb;{eN4*C>7(|epF zQ-+@$4*CZ}LFA*rUOZq1{+^giSA6cK=p%jRodDHN4NNm%Z`jzscs?&8R15^lio;9D zL#Q2%Ez?nc%;KIM8(YRd$1?OY711i8_|GmzeI~j5&#E^*tUK-L(2$V_`3a3~`MWj| zVh)RzSHg3)ep78N$AJYh@|FHpeJcZh0`Ps25OIo9!Pu7=3JGZu=CyF4G>$*^(PBb= zgZ83_j0tJF=CWubALpzU_$BHU{z5iF9GGaIN*oi3yg7*;zJ;JPs*%7L{uz~rZ!~8g z?HY&3T>RtmmLJVCv*8DM$Da~A+lEavSgac)ZWkXo-4*vYFV9@xf?~76<`1D7jcs%Y zavu5Vv(OSN5Y&NQ>AH={?#t|9L=-AGP3AL8uW>#}0<TFk{wKZpYV)skM$y8|tLE)5 z=3})_nKSfvyPzx53m@GO`W0`or)jTbCO!#;9(~l~w!D`NIp2hvBR<ZQsnLYILPCLn zz&i<^wR|1)M@k(c&HOj*-_$Dp#w1!7X%2wOk-GB_F8*MiE*1+cMn<CEt@Ppje8qO< zd_~MPF=CsDs^JFlXzG19YO4KXEfO})XeGH*_7~Zh-G$e@kJg#*1NZvY`Pw%ZzqH-O zSKlunQHv2S@iO@r;Uz9Ef0D{?_HXivc)rK<8GPY@4}HdH#ctQuV(|ht9^Rc&`iS3r zh}XWAMfE02YFz#!s}~=f_Tqay9)03~tZYPou@d&{G5aM+5Cth2<-eo$)lzI(F93VK z`Hw)N-*M&-@t#_Y6D%ak5o<xekEiFy#NG$O2XrfsyC$U0$#G8>!J*W)g1nvh8R&bT zH%D&uvKI89Lyt^-@Ne;@{>WIz9nqd@^F|*%5NYcgD_yyw_v>9rcPH4qt)QyQSKzWa zXGjaSCA4d#n066SS_@)@G9L7prX&Y(Fb3n*vAXF&1bz199}wuk!<CtN;EeGPRmC8; zt`a=8nECmUmPfU*V5F)jrq|2PY{%b%n(Os+ZNgk&QY~+q@>4gKzeAF<*D)1cw>w^1 zHfE;CLenK==$MF~q&#ouc|B5caj0jsdRI#%!qFmB{cO=_H~EdNs->Ww$Je*=kYXct z=gf>q6j#*Hw|-DQCyKwLoavNhPS`r?B`8^#RMp{2+=km$O@{_KLaVG(U~XkA%=_cU zg+R2Vmxcz6bsPPlAG4G&_AjG7(V4Q2r2y4}8cmO?+;luIZllOse)Q})eU2VZE0O9+ z&~NeUPb}wyH<khnsQj@Q2a?#2|BJD&0FI+gvJ_fuF*7qWGc&VfF|%4s7BiD&F*B<z zw3wMJW@ctO&Ccx3%)fIN7ts~n5!KNZU0>FhnJ+Wn!)pA2laaPXE*!#>?xH5mq94De zNV6<K`i*#;6q@!#F)FurMnq`xU~uaP<4K@{<Ach!m6SREHm3UV+d(sF{SSc}DU@Ig zO2dsv#M$&DM%mv%>-~Gk#51O00YwqUsaD%Y-8nxSsd>Lk2dB7KqqCO@mKD;Esh{hA zcF{hDS{LC;K4(XBu_Y6mpCk?hH7gW(8AUCXPdrxcj>=+MPeNrCWW+3POU+e6XAnck zq}z7ZE?JWccpuax6Ivssy+Q1Mt@@SY;Jfx^>R`N>ENg*aQWdI!P1Bc&M8(-oteySH z(z?ip#5o~uBF`n_sO@ni|3W!duY`Fbp{?oIiB^NZdgu_!<ifla`Yi4eAV9=59mjZ> zdm5;4{b&CcS4`10{&&zbCfYesRjwse3tXi8RKOW*Z@;BvJnk7+=ItyJ&lk4n5@t5g zf{0s_O0-3$Bg$J<5_Xgft(f3)I(C#+y!1EhH#}C6afR!|P(K4BUi>Dk@vh^*7b}o2 zK{8na7QB1Ot%bOH#{)k8Ic-Uya~O}S0-DN3PEdQm*{LwgMgES%F{n7m06hquC@V7g zFMFzJSy8sO)I0~%<hO82yZPL%)cRkj3^Ih%M1OzHq|c>2q;cdx@v+aVsI$R~$+uy0 zo~?0Qj!0VAhOaK=5cFZ#Z`W#JvUpUurav!4ZVJI?t6ydw<+dc^Kcoii@ibJIDEA9! z^2TKBjR6c6?vxWI_l6*o3VykDD9<UF$aL>5E`PmFvyRoy){C3$IFQI-32*f|*PFb( zI4dlWZSY+>W1H{<zq_#lFKL`*uJFl_i!p2(?$vF(e`$2fAToBMs-r6uwp4^#vY1kC z#3**6P_vxUDld>$LlkD8s+)swf;c48ksP(;cZ0Y>&u^d-u}kNT%a;j``KF|>0YYpx zJIt2kC(oHEnXV9VC(;Td5@@qIH|`1-?1E;Ot7}DjIGl&I7K*CS1wC`-3f0GhsCCgd z6yrx=SFj-@?+&WK+|pV*UNyajvsN(e7ISVEb54qL!;a7+RPgcyB0pz2h&k68rm$Q_ zYGk4ao~~s909D&6XIK|U#XiPcmrk;Fxz22(?);;y){wM`6yjZ{6YS{hYuwWOP;Y`M zKan3i&OK{uPr9s8yYz)u5DLScA*GkI&9{JuJk#1two-z(juDO$bDF^mr01xwvKoSt z713CtFJ4|7%CcReZSeM+6XKbC?IVOKm6#gZMZtAo{#P1m07le?TuVlAZ((uu$d6)b z1y~#Ftn_pP)f1ZPGQdk_k9OIKK?X4f_iRg&xt-#Vajv32Z~=~}cR?y)MA?r>vaumG zna~c}LYg#R4?v&la$krYcX}qcZ*_Szo%9p7TLTF+lw~Ehg|)43!>=3L)bw^3L7B2T zC6DSL{6B;lV|D*XH*8@I$`qzIgcKLhRxzxzjvl4&jfB{&Nxg6DEi|h9np{(G`4w-l z>vEC5Q*Sv>fw{V!l5bxXqYUyZptmBg$%YECv;^b~FIq7`nzBHgK<|KJ?@F{Z{(gEV z*PSbKAI7YQH1CX(*%`)(+F%p~=N=^Eke#+j(|ccd40@7ucshi_Y`u-$E0Q>WItP4n zmZp?HXv4y)6TiIykBAia=H*-Tpab#2y#kJgZaQmCkb>6Oe3q+ml{aU~Jdg9f=s5SD z5{qj`ZgCLJsbwqD^k?P93XcA?P`oKiO`CRu(tU~=Uya<!g1JvuzdYhKbuQKGewryK zwPEfaET^P6OF=(`ihRpV`%VMLI!Hg(NYJ~v9)}`5POAzqL(|1WUjoe1i0aR?DPcdm z#n&F=#t%B%q{lP|AkUY+v*;u+wa>GmozWwGR3R<dNg4ez8O&nCuggQ5HWKLj6?ra! zw;U@4V9XzT<N5$0YTEIE3Va3+0HSVrW6mu_T)q<dx%Fn>)AR$oq%^ywa|$+u^DRgc z-m>38Y{%I$vcsgk0<5q*g#3deWslIFQQxp}TClu7MEv_#(XDUuS+0Dkn=T4Eshbcb z0=%SucrYBkc#rha4(%L)87Qi3Ja&o}q_KO67x-J=(oBQm1hp^>PapjZ-?zD49>(dY z-UC0yy)`HK$+;uTXC*d)&1-em;cCu{tscS+I8)03u(o8b;H{{vXBG_kV!1s+_q|Y6 zdgP!CDB+3(B4mA;(j8F^F-0V9|B4A)zl$LF9YDE=8I_}7+HT9z8rmQ0Sr8Rp63d{( zq0Q!n6I~yanYa_rjlaUd-3ML=u;!F@3-E+Z^v4O$`5wg&r++Fr<nxvL2s`5o0(1mx zfIXPoThuxzD)^>rq6;1uYr=Zb0~&aPs#m)F1uZ``_}lOmI>OW;IKdlafa&lC8A{8u zG!dpnYh#k!@JtL4l2ba=G8G=Vi>NEy`o#8^c4tT^jEnd+GKBXTS|BIihO|+$N+EDi z2dc?+N}Ed8N8v~0^C~_X>aTjBivLPCT@KLQW??UojUkDE{o3>19xADXbWcK9Kbdac z+i3Uaw8NLPpWfv6n03!62!(0LS%%*o4MHvr3U-bFVn@F~j_kU;psZf?g}k6zeGzK~ zgycSu;su1>ZW2(gS%ysbvLrqvngLsLTF>e4aPo*^_AkK#kP<^Q<h!y*4PqE=$pV@K z41na0NjS;Zz;`GHF~~1ghSNeZzvm9SKxw8AD;P3lbTWKLVT;+HGve3Ue&?Q6GFYyp zEC<GcTbIG%xZLr;pbb|li)vpJqz~aN7%Pmq_7G9Z)ExI1C+jseWG+kv<QuCy+AS{M z3BWs(_9V?`-JSZpGcPL=uT3gly&<Y!q{WYaPi6+QltDq|f(t>YNB~Dk@)6KL=lGg_ z%;Z)s=ahC$zw0FS^72)Q!5x)<s+YlyGQ7HrI-yZRDV*>8h{0|RwqHs-aAO@TVv)@9 zRGLb3$5vgX@R};XyT!1_Np@<KA+2w7EEnJ%gn$zUIM3OteYyOKxLJS_CX4!gq$;v@ ztmI5+SHWoy*aDXMosS9??*K>|oYWhHYHR>|B*k?rG}bJ|1+)k@O|#ENBSR!w5|4&* z21a2aA}S*b=x?|1u@&$%uoOI*0}Qf?73xxq`1q2TxL8kvpuuCeliv6OCp21!;kp;z z-N`X$7$ZIq{~c?*?Buz3_-u`3`((8u{LfgUoP)*x%!Gs_**MI6LmT`+OjEZviQW=g zq;R3Z)aPuEVrC|jmAXu<{Z{WjIg(V}&{&BUW7w~lCt<M-Cm29D;*qU_9m_5|JA2?f z@!`&JbRj6{4{Di<EeZe2xH)izSxqUXAheauy|9IL90;b~9UG`0dN{m@!~o9wP0Pbv zEA%CkScSXv{&2SbC6IWHneNJ(j|*)=K#=Xow8g|`_0(xF-ur`Tn*f_M)OgU%Ro&fV z1ML_p3^A-nnHN|MS2B#mVV6a$d4zZKpvT@)RH47XOxHpEeMBf)Lhk|E<tuzy^=Okj z<I@x+gOpRnWG!pO0?)RBW@dCqQG_{Qe>>!WUet_a`7oH65N&V@dd~J2xOxF;8gKni zI}(pFbebw5hvMlK<8b%0x`GIPQH+%ITWj3`vIG&*2#7@3b8;s_L^M9RZDeO@v`eiF z${9X#g>MVksS}Sih;bnjFx7g=D0_MdCh1ofet0d$LYVjI`OZl)@VdUDq)t{$frzE? zr;vke<9Vw;FoL|6eD=}Y886=T<ouS8T{Nj?H;};DGuF+P6rNS{8x@QaY%|=vp{K$7 zYy`Wk`!Aq<^Yo!*O)-%vywQ;>6J-dn9S%H`bTBS8R8j^a(06^teGOUlUqYuS`#MSV z1jWT*!z_ZMl$7%Co}(STXflhF)KSK~mF4zzyV!H4ZeV`E5Hk~tZTu0)F-eZ7lP1<> zjUG!*$itJdh;AIzy1}NH$Io+c>yeU{usTD7yGe#sE-%!0plXs{OisL`c5aGAU<{+H zo~3z>%e)%e+dPgeQQB{zadM|BL{?g(uzxjNOXXbo>Hn9RreG^Uka|!M5Djn;5U&4h zt4c<$mclMBW_HH5X3k`C4kkvnVxMDN&Q`_%S1X5q^uwm8=*r>>qrFd<ugy2lH{WTT zk?Rm{Ypjv@szNA35HE^EAqt>T3?otMyZ4$FJl3GWix9qozEd6jU``%@?GDT0{&m3; z*5Uu?3-t|^aF8i5goKYS<FJZ*{poSDK<*N^JSnG?!zh0l<$y#(+b0*LW!~Y8R{ru@ zbzb{PPOI9y6`Qlkl8oi|RQ_5j&pXBS?3#jP_B=`%_JLi6+8%H4+Q{XV@N~EBUHay^ z8G>|rWw{ywVA5LU0|}lic)pS$(IhWr_(gmHi(GDLU0`LQ{Li?0DoS84TZ$JWGTk_- zVW^JoQ(W){28Y?Z!*F$pnznCi8_DFAhWx<rLuJLuH8Tv97zaFq5b_Qv7#$>5uO$d! zfj}zEPsWEK`^prt!tqC&D)JNVJSFA|Iz*FRln-oz4_3(F0dUDYW{6~&f&8;eimS*; zm<hk0@1qZ{w`EX~_FG~${Al@i1gkdHyy=a2<`T@RQOcc{I50AL)U3EB%>9J6rj2;G z*nk4|przj$W1Ls~C~LWncWJ8);&w1WgWm;+jn1`eU(kG>;1|2w`8R5HFIOUXFP_M6 zq5gf(Qpp8EVt%$a7=3csQ2c+`!QZPSDH>LyxC`j~;E599peER-0mLcH^1%?LZn(eL zBXog_GDyv~)NUv&xpi2&(aF<8q32d7g)fN=R?Cg@53ZDUBrSO{oe!J<UU&2OfN+W@ zri-<Q{omdb`s-Ow2mb3Gs9oBtF^*ae-Pq~KdV?|0MeXpoELt?M_4>*EvoxpBBwA@% ziBbw!W<MMxa<@}$9Y$)4wo#GsdMQaCliPWN6vM85Knp}oieJJDFwS7A`6%zNAZPY9 zu<(^^%hLSD8_*;P->NY3kx%Yq=;iF2;uL?@z}iTCdSd#GI^a(FNbs9+lQH-zh{+&1 ziLvxCFOra&i$`B;_9n@ExNdyD-UNdVQfIjy-kYQ*O-4exJ0i-(BxzQaHtI&zg*MHc zRh9Mz&gJMw6m0(N!rf0Vni}1fIX(of7G+2~RLF|m!_<btgVE5y`%#yMI5~N`IfbSJ zTmGM%7_y<hWwpdO+1L&7UW;S6Y{*_yjdBNpf%}h}iGf{W#IjAKv&Cj($($Mp-ol3C zxPgqjX|5~DO`X_|f8^`Wa~Kv|lA%&s%})CBjY=3!CZ4f6mUD~ca|^ECrUKo_EVy2F z>QEd^PnaEwe=UsZE&UO9cfGVzhFV8)j96MWpoPWBu!1fnYA;WV#?}YJo|vhm1TKew zt<`p<&@eV%7txw4ciX;JEqP=5aSXNV0B_Q6XL!g5rjpKW0%k59S3;F(j<`)`#<0mH zg>y>OSpJLvk8F!rybV<an_Ks!037=Gnz>Vh)%+SI91GF;ggHvXAw)gx1vP6!hvL7K zJQC7vRu-vN*@`*vdudt{5Vh>P(7s4Xvqt+ddl;QQWYxh_HgTm1kinvCiSrs(oao!( zFxI1}wHFeJwC#-j{F(ILYogYP3M$QtIDt8GpF#Yy^20ZUorIDtdRrKQ@Usy?@DJ1X z97_){MQg235S^{qv*SVM&!uX6r4fR*!EF%Tz^J)^%_5E;1&`n$BUW;9sNsk;TIbBA zO@d!g8hWPh1AvjkK>11+fi-<ke^O2$ciSDuFHVq20_Qzk(CU#Di0<0j$6?99Zofa1 zYO)(VUy;ug*$G|HNyGJr=mXa4xXs0El$kS{pV6BVcF%{y96fW9XO!Jsr@tQc2G(56 zwo@XGx;-{Bl;+&>@u!C#dUI@$opLYkqS5=C-{6Usc@*w&1~9VI<}r-y8=6Bs3Hi-| zNo94qc4SHwuErL|aNjyZa9<@aYn#`amdm}}_)Cc22XA{nA08o}R>9!c#!jbSr#w3d zHgCE0Q$_w@W_7ut8`FCa6>>U1R2T2IZof~gc1<O^$1INV#lH9KF#V~uxVZR;LAv*Y zY3#^TcD(*zBdbv@TtnQzY02bXy%FWr(eeshm;<{KwzA1{X0G66!;Ch>$CSvcjKhd5 z>By?~Xf-lNiD~urwJ=&^SWV<O<XIk3<R7+(8DFw7t_O90-cX^bIlrITxpFnpJ9_m) z$5KqY^EnFOud$VcsyVG$tx7bp^$>2i#Z0HMI6)$j<?8n@YItR|k>Dig;--2e(v%N( zdCTKJfgrpW9x*zvqj&ZRuXu3L;DSO`r>bc!$K;aW0{4a9H1G*d+^60uz}lhvGT;l2 zsH*BpYD|>igD(%DJu8HK{{|`50Qpv3w37{VkS5C`C!=6GT6twmP@DLLIt-gp0d0yR zst#d+(mPBeasbY&l(whd9GQwQmRe!CCsUD2zdVu0+m#ncs_vSJcz#T<R)V`$av~#o zCz^+kr>o!!)h4R$YQM00Bphy%Sq;ApP3i?Eok-9_5vsqy;8|!>y*7Z>+pDwHc__Z0 zA5mhja)Q_E42B^nbbyrs6MBstN+iW==aH-up7F}{)J^4#zR4F))VmMcTFxb)`p`!z zc$%;w5Z}crx2m0{+tZ-D!?Ag-q-QlEpC9TS@6^IR%sC|KA9Ap}D|Oq4znVn+?O_aQ z+RM$+nOjJrL;V&2ujY8+W)4-icSvns{!wl7gr@pVuv{@{AHBn+bL0Y*w5GT_+lS#t znEOF|yUijX@v1Rk@%4t!JL4J*L*GHd`c$%Zx86V68G58VGEUW`W#E}dQRWChQBXpQ z<x%=T4w1AnyuS{+-gdT*v4sZU_fFRSNg)$Dodam;Yj0q0RM2ypR<qXY-x0fZ`Uv58 zcNu03iF<)pj2tHzIQl&nUueE5Lf9P!40{GsRCn~pf8*l+;tM|`7(?JTRYLVvYEe-# zkPAiY5s4-2D(p@xy|ahHFwT&MLG>Y_)?YrgbrGd_;F*!oB~MXs1^dNNjOz*~1DG@& z+;$w_hAh7hs>;z$zjQN7!_(vJY(v}RO}*~^0CF`5^9&))H>_4w8-C0G%e!8}2StKj zd3R>L|6yU3WSn_VrTEppUT!J${V%Td?1g}G^K(kB_LKRS=|8(xRnO0{c)QOb`A>pe zS1U6YDI@z&cHMt++^VW-qP=rSa}nc-3C(G#MQZfW*I`zWOX;FpQ$fg3g?B89a#2Y3 zavu#x2szyQ)hK37EQb9CoXVB3-jjbdD;97o798ej+7O5!hMDI1QTe&qZ5Vi;IaGBd zc7D9=D1s<%>42=ID_uH+Af!WoLs5m@27N4a<^h3Zb-s$s9H)_@N>{zK2BA;CG%<*U zQ^`y+W(Gk&Ab)K#Z;$27xT0W?x=Q6UokpY&ASWx*N)<_)iW-+9uIf^9l+NX^OHarB z*~-Mq%P-2zLBK1yw@ZE&i7{+xPLt?p+bbsysiUB4J~1t4VKBN2_&$K#%a*AOs#xk^ z(B-|XQw#*mFx`3hnMwaTXe^3m$kLXkXRTQZ)k{k@ptReC_(Dm~i!Qyi>?{#ixvaxc zv69f|H8HJeZW{$RIOSr&o@D-$*tO8L<PVJ_lN$U&Ey<3AMBF`kn&;q<SaBoW!xKW% z!c-Ib_iyMN=H@pF(_-o0zGazt+j`m3ms#NVvRK{d{aHAYYEXSbUvn+z&M;^#q{Pq1 ziS7k!qnU`~Zmr@@h4+&L;x+JPL>|{dX2^yEB<Mfyumq8=)5#k8K0QbNj8gxD*S)~O zU^#xQt>U%Yc&VIE&vas1OYdF5W_=*MZ0daZxBe<6)m&<$Lb>tb6+X+;Ef~+;AaEF3 z2g<w8ItC(DRPAc{Hd8i|<qFS1Wvc8AU1)g^*wLzb9K&~h#7}0@D?4-!XBxbtwgLrx z=+hNg9}Wkefk*?RRaNf_RZr4}$`!D%0o#@OHqF`ArL4L}v*p%bi7UPdb(v}jgXq*6 z>Xk<NUv6U~-LJ9(AJlyUDt7hz!-mQqp$QwjoXrAzU<H(~s0CE7xPL2Mk^fe?V*hP) zuI3YU0r5=uwgz)rS>^giOkDzUP6p>9Y41E;cIA(C8LF*6rY)(&5qE7&rUk5xjU*65 zI-zTwUUjc61=^6sWY1JFk&`(BAJ&es?6+OHia<X5<BW?R*7}8(I653{BfgZFe-<>w z$<+41#?X1<6u#%%$e@UNW26n{4(G`3S#_W$8!ma(-u5%jw81QXc>x_~WmXgO^?cp% zih_N&<BV<Corh@#4r}X=L=bcOfrXq=#yy!AUeUVNU=J0RN74^bS8f(WRyxQ0udfMs zn@)JU4n$q8TkT;x#ML-RyP898!k)ny(30KL3;uB1?wB*OONTT;(8}fFAkR~ibui#q zWRXAn4KGn(UV#yz{koTO_n4b+$iKh;{A8G!=4yg5OUQn^>dphpctltY;5ki6%6+&; za2@2#W3bN;ImAD!f;=sZ0)j1v+2`%te*<mfn4(4^T<)^}e!|r|Oz_>vVM@1a{qw|2 zwMlKeM`b{@k>S+flHwsA^t0ZqpAM&ES5OG<1IHKp9#H`=Wb;iUJis7PtO?e5du+Q8 z9)9x6)*xtO;vfeL7MVZ4X;oSd=nTrfM`nZ33<^0j9G3Af_#GPT4v8AUP3hM_i%Z(r z7P5&MT|}M;*qc|X)^OgDCH7O&`moz&kJOL2Y;$-Visl=vs>0Oe9lW<kY|2YKRph=0 zKK2o`=;TDz#;jf2xwp8z&L|GZ$&DRi14u^4@=bBb5+bzB-{2qAkQS}y`O@8sf>@oR ziaYk(hWTL)=XCdk|DK4P%i=;Me1a!WpF|t~m$~A93}cEq*qd8f0Gy5fnT5tA*(st5 zBMpA6SR4!IfPjiuMK*>xszByQdz40&8J7xe<2r{l;8ANjyU+J27DdEFFusELQSF?r zft|I=`>?X|vVJUWOf+?VyuL!_21;7#_4vTTiAwcKZ4o>~t*SM*Opb%wrzUDCY!e5$ zS$hAr;pF+f=7uF<J-AOf>qxh;xU}vw5`R`z^CP=I9?@H;c$V#0%_YNmgLhWY80$oS zK5lGe#<|0#C;rtqCp5_e?VcigDfX;}NlbQ6KXlRSCI0wF#+jA_FD1gLuLFlp_u3hF zLz7J_hhUWHm|#7BsB_gBM@+E|0g!H|!6rLfr@9XF`3`t9ZSSU+)PQ7PZ1sfe%Q%@j za=pTuy_!sW_u%*^kd4M?`EaTEogJM|{YL9(!(jf<QS&;QiI@29p3bKSS4$@8xOeG5 z%DW@17Ue4@x>M;d-t+HwJ^O7rYV;o8J0*Il1}tkBe`#`B&%b4P0lYuv|NJZuMK;9> zo&1gTk>Y_<KV&)NqPi*3TN{e!koh)QBcrKL=TzZji~mR!_(2>1LE=Lqj_l{X+0b(k zJPBtA{mO)OK*_66!au@#J^PHv#7}rcQhs2f-xtJ%+&Ap-{gq|Osc$%zL_#@(MO#jV zEd*x7dW&d8F2SNXuwok}h_9yq?n26!pD-0E5YFjUk1xhXq+MhUdA({9kkBe54YfpK zW&Z_rpqGL9yQI#gM(9a%9!SIp5vxo*NsMNIm{~lF)h#H|Ywu;01GVrr%TPPYE)a)| zA&4%qm<5E4R>(Y=NR(wL5oI?P$5iTzr(6alxR5iLsRm49yl^(Hu#9zlFnqmCMiVHJ zC#Z@>AemWwIf|HO(C54SOgjOH3KEga_x*Fjf46O|sS|O=&nSTBvk{T%KSu)pux)V< zGZVl+nTIu>{Ac&EKWOSmCBs3!f})7nh=7>zLQpAH&m<s80Pzemg-~0IBCE*22A$2Y zrP}Oid0Xp#R_R)~yFs<#a!6@4q-NCu9(*?*t|)IC&OFJ8M<tl<gEme)*DqWIYhG77 zbNn#6FkWT*Y{c6cf+fk+w(dyQiHO5zQ6SnA6xYeN$BEm;`*g|M5ggK#qF7NdHXFm; zrL=-)oBEy<2nhDJ^nI?@`1l6q8i@HOZcu%$2@E<ogCDM5{Rjk*dZ0Z05{C)5=fgxF zp(hFVw)CJ0cb<0md_?*vMIX^<FRjc!qJw!a(JHulD{k7KZJ~2a?iiVR#gQBMCYhh2 z!(~x`J2!HEIz4>9yK*O`JTTJ8eUJ@dw?@Hm9^6a5K(+FQerbDokqG<ZC6V-r@mGKE zHUizsE*Nnt;;-1HSXM30+oN!U(^@P$fGtjBMWkNJr^zjY&owbD9}QAckUPiaJ0z~A zA}#)Uj=NTG_hM;Xs@Zj&aK3A4UF)A)fif*x7LarunyrbTstN~|x>SxSPrs7wIw}3u zin0JoFZ;Z(l$o(U;k{idebVA&C(;#4u$FF_!;~ziVJB!r<=ML6x0uaKpPiqVo{?Q3 zd$-dn>>OKe<<e<BRf;<c29y}>b_iVrsK{d;;e3bWxr4U?mP(G6`SzDF&ts_#Xe~I# zWoy)jp^5HvxD2`RIuDl=hJmM7GPxR!sLc#|rL?=$n8&5gj&*?j(X>3eXhjHvfOf6w zPWqgqnzdfP66(sF8@j6cWt^}7UClFj3$3C(Zy#NBtp=THcpws<%hVDKLy~i`$GLn- zfNg5LoBB|kR3CPQ9o9_1vuD19Xq(owE{_HqPMwgY-j%X~_D3P5tcXtRwT^nRUc(U7 zT8qzgV;szV1<7xUZCG&=5%vz8L@!sBR4B0R=?_XPv3X}`Z5J}H-DjN}(c}H)QFC7_ z{8sx!KbhZ}Mr~-lY6!Hpp#AAYHYdKO@hBMx)VWXQV32h9H{G4WDUanMp!G{%k5x@? zz?^eX;b~F;(<dn_n<X`;XqT=zJyg6t=W8@768M^tOExjK2lT`-k0K%Pckd+gzM<Bp zVnUZ(qLIxqp5iT4xaKt)R7f_%2GnNc_4*mIw6Lb6QAktQow5;?mw&1QEFn8P>|B7j zvTKS1M86gC-y*ZDHa3l<23#H~?yeHY!TU4I<jd^KV5GQs#AP-UoKz_4{p^f<$qHsU zN`4~Wk*QBg(ELQv_`u?1xK4_8*jk~x<eiJPuM043hh|K50%1&cLRw=P&|XV3QX6o; z;!jMvQnLwDQTl9M5sY_2K^%D`mZjN|qS-HuCS)Fr`?0u5UQB`R^;4yv8e6cAnIQ2> z)jWxC>Y5rh*jn}xTh-q{qV~Igcd#K#-g=3DA}a5lF^36vWSiP<aGWB7@5uF$#Qj{2 zX{qf=4fZ4FV^#FV)$s#XkwFTzO_8>Sht2@CoZ%>DiGvP=ms$t+?vX#;0V2yMe4$L5 zd}W~!NhcxxDn4L%#fj{nc7^z=+Vxw2-+0ewH`rW3BDQSS?GnzDy(-4Wnj(MCN4_<c zA{mpmBeJO2rPz69C&w%2UT=T2TUu1msMD5w-4C&6d!!Y;cHm);)&_4*prU~9kJgiJ zEl_Qn{dP)$y4Zi^l0%1g@Qqg~$Z(caI|??%VLZ|D$$L=m4xJIjf(YGpPE*adJFW#K z(B;m{;KfxOvPKtGjAk{SY`O}W-XSUpc0ESEt{XqHBVw%`wJibIt|ZG;c3U_S*Dd0w zawtn;u{=KY7C_~@#@>8N&C5CK`n?B>4RCEUJbg}y+nJ-6U}`q^fcu?0@ThWvgMIB0 zk{oxo&p{`LTVr|kIIIW2@d%LW#7w)TNlyh-{ocSt4>e|gbJr63NU)v`?`Zz%#+a** z&N1zmW6_y;kDvV}v+VA5|7+T>(_%y9g<;Z<zGoF^g}*;xH$FFNWgu1WJtdpz+jt^| zP!%xX3Eg{BiW3`Bae$OA>FDv5-37^luGtUAZU7)PL$#82i2~P(0nV@qAr_SyK2CDW zr7>3E#zhC2-5t1ftaXgC%T3ol)?>WKQcjNzU;}6F2`|95BhZE!j85*SWt$aqD4|zt z4r72gG^OAO;{h`e>xyDDmZoz;-qLy{Io>H8*UpTfWH7Qi1ykOiVu~{R!_uBvqFtFT zx<i+7+K_Uu-2ecN2(*n0v}N(3{ph!TsqGigGhqfUTiBd60RAjJyI3AKtk!nBiDs@% zafB`I-4yym#BDQ+;X}E8V~+Vbl)-+h=**fr(|IKET!G!q5qad6zbfst@!h)?Rdq2W ztnTOe1$i((S5(_2TEC!G3zN``8CP&`s;tG~0Moas#e~47ZTPZ!y;gYj58`-hWKxpY z@Mg@w@U$7~&U-dzON5@v{FanV%nRZ;ifZR|r3pQuH@f9R(@)Mmx@C*)qWI9NA=eqf z-M3A}2#my>Msk+a0!^e}I|5XNm^P?^mwY;6(Zup?AX(<&x&Zc;1)d=EKu3>RIu64S zG&qNh-qhZkW|Ku7`>bBz$k;JC`m>TEY%+^YQ$b*o_8q|w6#q*umK-7y-Fj<+m9SxO z_xl0VhDG7dtOKIEt5pfms(kBGQE+CC_y~mRSBi2%g(V$WX?$t;q_Hm<Kbeh>Q0i`V z_e{BKxVYxLsUbh%CInURu!v9E`yD3yDkpUT3BhMCM{6gzaa*Gyg+cw4CZC)^IO0J# zup;$|mW}gO#Ot?_QPk{F;fMOz_MI9!Y_#1+O53A0cgW@Km}GqKi8d)WrPzd=1}%|5 zY^Ms}(eVYQ^O7;tN_EiU6m}ytr_6Ji!h0BJtuBC2^5JdA9#-w(@S+kO14OAMt=*6} z3-hiF{1#|M63a}`*BMZea$o|ApHwkr_yXzG@m^zjJrkibQ%<4&R5|5{F-`V(8(7SD z+EOd{F|ul+^mJ_iMpGRZ`CYV<%q~U`Se}&W9!U=(>NQJ`-giwEmX6575<Wj$0Kn>R zFW0Sk+Cz+&x(NGqc@F19=~6!eBV<G<n~0O-lW{VD_V0OH_J+1V1y<azj&FuGXXt*O zeLGv`nrY^en4mSlN5h-H1YJ8JQ-%be!4}@<@i%#kzbhBf|3i+5{=Z~|96w83437T; zzkJSuP0GkKPy^)|W#opX=jA7ccWG$GX@|$;n;`!}A@li*|0AG;`1$#70!n|s1fz+q z)&EjEnqL*H=KD-<R6lFOf8k90+t-DS|E6<f;wtK3XZMK#$ywQ(eWp2XpQzA(qd_TZ z|4^ZTi#k~*0XF%G3MR*?&?puoq0Csa5mzD4^bHzmFW7xcgBLCE@7c&_WSj0I+>B#c z$B$P^ZM-!)Sm*Y>XmQzJUla8AfB&K+u_Oe>%j1S1R%;?Oc+=&L?4ga%jqiyM8R{{A zr>AWaZthY7znrj9hpmBIZ9$0WZKvDl(IzWZzNOplJraU@N|{R`*ajYI+>5C&jNCrk zB&)GNKfeM_-Ao?$Y7pn06>vKAFkwe*r);#?Ja*UgkyGP?nr~g9UWWYBJ_b3o*LEj5 z=SC&XTj2;l1fntp`?S#4T(>?EPP8xtF08SVK0ntc@pd`2o1bnd=Ai{^G0@1yplhsq zqXH|^z;)yp{!enx9bOT=3=Vemf+1ZSqy7f&;i5_Nyeod(XkIQYuU1A(sdMDHXcGWS zLm5s~GaLrcZTT!}wB)dw8~3B)8Av$CY_!QC`rLZLqTKg80_CgRYOic)4+2FnF?UUb zkvEL;77ME~U<=+GNLeDE7di#)=Zrrezjk`ZisWO(%+3m5gYnhQK3mMp&Ajw*Vk1;0 zq#!lJk6zS21VRe>jhDo<jJq+Tu_DRL)Z_`SmT*_hDeO-(n5tIZJRa0Hbm@9`Mq=vv zXr5mZo}g=-^*Br8hT%T#LbqIoc*3KYaHM_r65;+i8d}6~BeLJJ?s3ai{4#mV&^%6V zL#ZzW_>m(Owm}J0>>Xnpw-+-rP4GS}aX!+wbK+}|uhAxxZ`t@w7=!4|etrC<^cxj) z=VbkfOJaR$dhz~m%l&Ut{3j~;e>ci1jWtbNb)=6q)1(kHI5HHZJoNav;6gDwS(`kn zqPc-kM0rRnTDJ!69+AbEHeC2;!N+s%-w#c{#jf!9eeVTl3jVbGjHj?Iq#oSe^&88I z+ZbE@@pI$jX^#`+VoMiBw3*ykxrfO9#z?vc--m3AVaDf$*>Ei>zPmmcz4HDWLeA}` zs_BzsCtQy7rBMeQE<aY+fltG57!(PC^OGdWZ=||@q|8}_wI3FjgWD(YJ^4Qtu%203 z^Qw{`TrvQL!{`wx6Y1qDdYW`u7bjxfOVf6tHrTT?g|MlKjMmgmr6m~7Kojcn2J;`} z<Ib+*25b)x=%kbL3>gEU$m}+$#A;KqKfY?p#@ge+gV%YOYjP{8i1$+!*2fm%LK@@W z*RKD;6KAyc44vk%09qdbV%Ey7Y)?Y!#p4U=lD_@St)fnqZ}uPxBzGTYx^nj0<~S)< z*r_HawO6hR3D`=7im71PAY<2slUSOLDl;o$!xgM68B39q0h3ityl?CU6lwiQr6HGX zu)|bo)@Sp5CKGR!R?k4m=b~_zsN^>Jbu|zbD@?;)KgKvA?HW{tc~I-><5>-?pYSyD zqP{7-)cd16$DinU7yg(y60Ah0u2vPQ+h;Q3slkX9xwHS;rWxxT_HEn3b<2J*KyP?{ zwYr$6!HF?~_`|Sip?Z6NA~=mSwcdP5rHPkkQZK*ZIeWj=v^~}+^gYSTtUZDmdj|_u zSk8fzQY0lIjKU-^$F_jTI4tLo#Let9kIL9E6g0`1p&+=%RBMy-qZl5_?8^{W*8&R- z*KRMTtESFt3i2SDemg6G*7*gUMBeP6ioPb2Vj8kSX?+2{#3>GYz~GN(>D>T<mnz0Z zAxu{OYcuL6VZJYX)HWaXiIfwgbW`KTkHrs&X;m_Km#oA4A$B^w)99pSm<}d;iz2RI zlW{=Kw4Fg%M-^HqLr210H^%pU1=&2EX`0xVXMx;@IY-Va#%~34;%h@TDf4!rQoJ>@ zujEuok9X;st-ba$c4<#V6ux)>p0#`O*uLfI5T|EdW{7v>Zjbrd$1i6pY^ru7On0b@ zagCQo!2`Ln(cjS8?e)K84nhhcdDu7}Ts`x3TWov6B>{@ax9?|tn2{gRf6ITUp}(IN z3nj%@k<?~Zk~C))R%wYZ48T<wL9@joO({QTOD5AfLzuAJu|u5G$OuaK_|APdCL8y- zA!4w9<JLx;X;0a7t2nF{;SoXX9VsFmg5NNSz&OCl?sgZ&!sz*|^^8N1d?!SbnE`%4 z6AN-=e+zbdm&%R2tVM!>j;rvf^1FRK*j243YA$6|k`kT{S0O8=hE1dX3K#5<6wgnh zw;JRr!WIMJn-t6tN!u*u4NAOPfY!eA{A>Qw0q$aELvFvC0ksBE6W4Py89QIk<%aY% zBtHDapOk#t_Z}+ry|4h6fh|;ftR=5wsZ)q)->SdYB_!I(Wk!wU>2tzTEIT{Vt?cV@ zh=QU13Do0M7UnzTzXK}1RTG|)pWQ36pC0u;c+-E`u!Nm00Ct~(PM-w5W{&>^3{w)u zWx$!yLKL4_3z~pBcC^Pm=Z)%6s~WH*usxeSspqp+=@RBB!(*j2d*z!wP?vdqWc2Ed z(B@7_-p&{9ibF4hC%6HuY_e3}MuY7z0hkD22bpl$_t3{-@BF@n24doecdGs3i~Kk! zXbgMl$ZEa}i*^`s={Qr$g((?~;5Z0n+Y~ubA+9~BfvAS%Q*h|`l4Ecr=lUaD#m2To zm^5R?6f+eE0sMt}kqqB)8_4qVir$@trwq2wezK%fJ(=$7_Vx#uM^MbCX&@y(v#5f$ z?GHGdFq)KnI(Fn(81%piK?CvH7xoVZRO+~;Z4~<5JI3@BaAs6jSHPcHPlXGGHdaW_ zx(8aG)XL?#6ke_Ql7UK@6PwiS+-Sf!Q{_k|pul4H?i|QFsJiRdbMHF)I|P4h1cS-_ zD{Bc2M`geKivA14zpqNe#`ZJz=c-tIt_t=4b}aw0Du0P>VwB}&dxemEXa5Y$)s$0C zlCZ%_@NpCoi7P`>k$G$spVX7D4Y{d4ukbyBzbbEYgrLa5>T9{}kN<uM{gJa#;{fu& z<vs;)lyOXyqi^O2_G8SfhrCfO4*oE1Hs48xB0mN2?M1+}(}*4NF`&L%nHKMVoq)C{ z3o)ix$aK`C<fAf5m{y7ShSle^xg_gx9`Xa@_mpj-v!Ha*$G~l-qQsKzz#Lh!^}$57 zd;Icas~ZlX3YwjvdN{dEULN>G))a2vTlrP3n~ZYmNwDDX+_7QuuEYtsqi>rrGQ%%k zhu<izAN0_iD-nn~yR6k?=BjpJdEe))fY9=dwm^%9)czGm+qo`3WhWaNd|mD?BjlO{ zQasFy;lDuS;1vjO@SpH_=Kt>1`CAP6FZWmRUraqqL)v{-1MPj6E7c^53=4&FOq42C z-f@LZPP!MVxDh*`P#Q)_$#x!@3YcIPI^$V<lPd2{kBd=M()G!7R8qn4jM;iJd3Z$q zmR&h?t>)Ys?z%DCw()k}vEe&$@d=p21sq(-L*qIb41^&0aBT!4cvL}RI!SAldyIu8 zi15H8)I>>242WRyFpM^n^g`z~?KV+WR@OQT?~3{uqQkL<2R<4{NGkJH!(5zfJBbc_ z3OP!}yLie@n!%wg4=_|L%$ZKl#Ox-UBgk0(m|@kPr^(0&K1(qSlaUo2H&0YeEwf+^ z>b+G`V^!6gtN(L5&X=X(tq_A{o!3QbQ}GbG-NTys2bNm(*RWLhT#qdD(UO{zK~r-g z(RhO4z!>^XLu(UJUT22k#26WCaRx`D>Bv+PX-mI2`%i+|hUG&1zI|L78&6f)veeX6 zB&?Z+R(3jKoSR_6CN|Y9&c^O_Y?${1Jss2{k})wSCj-`!eokSoG?f_a`MLh(CHUP; zS0AsqpUvY_Uz(gLs2{5!v*tJMU3*fRTs)-@E8!<*cp;AWrgL2?is{$^W_sf*)j%Hm zVGmUi<9?!ip}c5wc?Mc*K;<t9Ro?OkoJ$zS(vaTRbTarp>*Tq%#K5zPD^zRU1RF(L z@j*01#p2bG*SJq)(2aXTh8{|;N{KC9+kJe2RD4a!W}k>M(@y!ull~{c0xTqZZ!Cog z!sO)q05U#IG7{HO)F@HauAZ>7BK`45B$`oc7y_yLnr=|B7Gs!8){9kU#IdL74W6<q z3Y8*3T0!=b#-Ud}-!gbpqV}|-q_O%j>fR#i3!xUUzQkFawFrNq{~O>><}$q!`e~2u zoG*8ebW?2?6)cBQL-a57_MkIZV1#7NVoTAce*2)X>ZQO0)#E4mk7bR0XmlK!PqgA< zE6Z)VL9Smu!fx(2sBC4XSVeR)BopPyl#5n4Sc8G|z^o#~J?|7k`<>vx$;+0@H<9kN zN15&glH1f0^zy*R-B&YualeG+Q4`OGZHh)S)`rYnUq6ZxRowTZhLTum=;QP530QuQ zYLy?Y*;DpR<$^YyG+{Mj(yIV;*l(un<3jj#%MBt!zJRcTX|%+$6k0o{dwBYv$SCIa z1t=VS67QqTLO7XN>o5i}vAgg=YQad5xCVGpEjBp7YbZa`k0@v&l19k;Fj~R~UlD`z z)-ZpyK)Z%DAIaeB)eEP0^3ylB^D_~`g|?PwaQVxdH<ef9!bg+G2?3E=@W&m&{3lSb zJ$&xE;N0LVeOb!{N2n94V6xZ9OR-nV$CuRHM<(xw;hcwveaXa0JLv0{hzhFZsSt%q z%8PL<vXY%f3D0fq?C_gDq8^Xf*Pla9v>z77l!Em=a9AL=HmLXUPX^1d8%0^ZjrX(X z0T(d%KTYxCyKw=~k5R%hWt~H!y<CT6nY%L0oN#{x7oif}eEavm{#1nNT#C>K<L>L| z<=PI&+}FKK+JR9f1D!SP4L<!;@ffr#otR<fL|C%Y;YO&N-2qzleRWAzL^fx1@B5MZ zh!dO!{>1m)ZI=INYjqnU(Xo<YsN9R_jib|xXwaz#v}GJ)$%3#T#mf@c<ZjtNC=ux* zOp6~EQ-iF2ANkL+xnyCI`8IL>-gc!)N_RHoQUeEGE{TCDb13#^e2LbZ!Xwe0S0WBI zfD8J_!FBkwRdLnoYn84Z%$=J<a}ZS!z&A=M-*;5hb&}#4qUy!|QsEwgE;6SGhP45@ zyAApmols!2{;?YvoK0ry_0peU`pGw7yKjQIUrrx&*pP4y(Xx#U-^3;=0t9ifhSW9N z1lPC0%-E)eSkQ?r_xLyWEr-?v3RRU8Y{l7S{JuA!vu{-x#IBmJ)bZxamv~miAi3sN z97m$P<_}O3cIwILgG?xSH}$3l0WnF0K5-GfayaHQihcZpp^kDVg#~O2n3_4jd8wO0 z=?)#N+675M1tUtqmN<*e24W2lQAWYfQjjf#wpgfOY{MR}46`TTHNr0STE$%a(X`6k z9tOaVcqA=qF`_MPR9nXd2>5GRY6PjtwD{9cAATNxDNFsupL|MveX=?KH^Eg%wD8|l zK*c{Sn{?pZ_FBVjf(-Jgpd$k*!_Sm-XCM-fxAZ(f5Xp<1UAKJp{RPI_|4Y9?0*?e9 z89Be9WhwJlig6Det2`;7u7)kA5MZ0u)GpiOTHs=)S2PO#OH(yC9ch0cHNUZ5iOyL) zBIlq#5=5kZHp8yC(B%|bIt)$bSOt%f{S)+mlax`JJlf**Wqic=w#nKx^|I)&>riSl zeE1h3(0V%G8|BYl=abJe+<!=V|HXIy<G%`<e?l)3R?aT2ssQ7Eoc??BYrn>c0;)37 zy8<<xEgV{Kq<dfF6kJC{fOw-?i&WmX6o?PIl_rMdPtkzLnoXBSuyji$>F5tRAGDlq ztb<bwD&Ule)T_&W<`3=-Jva0F*Vmgn+yLkq5*{S`0&<dkR3l{NRlqm22Z(4Gtb@cC z5{NzsIoNcRtu9hzl5MbJNHQ#)=mnW+RAVeDYcGNHg(5xSujX(vOnG+pGS&=brPuX^ zm}dME%Sn;tSgP<1qIdHj^Gr)EupNt+G(r4%d#0fZ6IwKO=B95g$<#<Cyq}3>PkABj ztDgCCOB+1@m1bz=B$d~+R2qw!)R%+y@)56mBJ?O0tC;z_X;rweZC6u7cALUt9+Xfw zd3oGK`$8bRxGE%{(P904Dm4mD@SQVN%V#zf2q`@dH5*!8`lQ8f(fs>BeQ{Sbsqnya zyZrKS)T&s3TOC=ae2n*KMVE(9s6KH`D;YSZX!K_R9vq8fq6p(y5|87g|DK~SjmeM% zK3n3PIoztM&|(ie1T&#c#v<5aEW%#Tu_uH9v_WCa$e>G=5+mO9uqKTtG@>=OU5Qi8 zPPa-K-FGk|^RsfiT8Eb6q7M!?*wq$?3V}n%S`l5^O%u0TW%j$0DLT7s7AIo3{<8tt z^~q9h5Qe100slDQS>4qbSxZLELWP4CGb;NEN!_aP`v4X&qsf#igy;_AqJb3N`ncVe z30`9&M$KG*0_Vk@RvRpP`j!V}xlIT40B^a@`Ic?D9S%XhQ)1dL%jhywZ;P@l4QlH{ zChLQ(^st1`pOPOreY776=Pcvf&P~id05NO-a8+#X=*~BA{N&~${|G$G?y#sSXmpV- zV+jw>mf%xFN?PK%IeavrrC?Z$FVx0#T*Nm{V=-c&gV5*&zU>1p!|pLQwWtfx^+H(d zCZTYC)NLBr0Ob^Oa@Jk9e}g)Ty@(0CNdM}h*~(3%D~72n!YJF_t0Cv!o|*^lzTF%F z>Kt@oKRqEK9JbkQ*Mm)FPrK;g0kP`jB<HZSg(l4%`zc0)Z=M?>TK5B1wdXrEr~sJ7 z{)EGRzy%l<vkM*@vq-a;YsQ${R8nOe*Fwa4Z4?#>tS0SRxG~r(Jw`uxB5$|=gnz&I z)uMeb$uxP}Bj&$n5%+tBW`%#tAU?a&|Dv|?pLeDIdQ$%$@w)u|39U-8Q=C=$oUHkU zdvf>%mnwV`E>H+AIWIq)8QBMVSPaz^*&tmH$Wy*nbriWRdD-?Tf|4SJ`d_0p_L`Dw z60ieoNBjq?F8&9Z-jjBJ7wzRsWh+geiyu&9lx~f*LXaM_W@0YMFE!34R&_c7FqD() zYQYzfFI4<skJH2-t>gkeC3_=Ov^pO)^u@QDz^!zSG6`T`2&kJ&RX3{#9uykc{rYX^ zIr#__P3=z9-BS4B4V)7-nc1krg<tY!^d0rS9{8nCJg-hn=g_|u6DnT5M8J*0D%FD< zRb5hf19<ma(RpY&;EaJ6O)AoSrRT(Fu-<RISjq>oHTB1D8pu;DFb_{1L_&-7vxj~! zUX7MX5}2=@4_PJG@Il76ZTYZI_a8vFseV+I->-pBZJWm+WW<r~2Yg|wB-lyc8Sc+T zPwwD;qG5@~w}1%(o}YW6pXAZ&nl$%XXkh#h|DyfWnjp@|h7JcGRd<VUrPF*NmNtva zZ7nm<T(+%&5!F1-<dRQdnl3PW&pMXV&U^S}@PejSY%qcVU=YG37>c;&^(M$B*NFbX zz82f;8sypZ{B82V;|FisA7sMsEU>rza-zVG+*9gAuiPO4QdvT)I4M=jvBOi4NP8b) z;~X`}x7%~cKn(#&#FgLyU_9xH<1D^sCK#BsF*bh*GnxpdWwL?Hwn<b<V0$nl34Bq% z(vUO7x9CluArCoPDjw&JgjKfNNvR=tZ-FLITF}}lS36J_VkH?&DHW;T;iJS}+?Co% zYCE(l&&29UthP{A=8`J&I;K_96QeznqU*lb30JDEP9VUI)GZ0JUbU#Co|JDZg>0c$ zL<JSyBQQMFLM1LLM&AP3CuTetsQm-JFidOZB0sUcfg_9wz5In`vc$WJ0_qc!E&Rk} z`Tn1&^FQ2^h?%*A^QYR-fAmjhDk~Cb{IqalrU_V>vs0;ac@zPHOk8B$Sczccnodkr zNsSb5iDv!EwMEf%oSq>9A{!)GR$+y5N$)3e8~Oe(U(ar<cLO32SbmJ%#<@M5uX#^q z^Lsb>zrUQofnZ~?geLF`=a6F~?~>`I5^qOFoB81N!D^6KUUgHVR6GAVVKH5ecXR>C zk<m&^7^KGzT8vkUM;@?15lB-qJ#;6)idm%V^;oa3I4ZBwu=diaz&>KHFwh*AS!cSF zpSM4Bi)~MXpLJwl)yuhd_h0K}*Ia&eo^{9WW3R|(&D;)+G4H5c`8DqxL$}plRMym1 zZg=T4O6A-PpP>Hs+w5ckzHJNb=bnb#m%U=E<9i)>J2qEm-AhR96P$22oVk1bw)oi= z%uwM`I-c?~Gy?8WGnwXIrro;^J+>pI%Br$g(K~N;ebsU6*2Be6?Qwuk@mrpI9|b(< ze6{m2&-V0^cC}!_E}$I-2jeUJYzM_U9N(OTdS1#76}zWECX+~&-G&NbOPFj11+pxW ze1OqQ74(=tqf0e(2xY@7>!2WZs21Z1)^7fMBRdMB=Dt+eB)lL5WC?TmH;4<F$3S8L zo%7Q+BYJWz`Brvd%_Y0&T$~o<#-?s)r9(0AKG2GvqfP4e)<_k0!Z@>lhL!BAVy&^} zPr#aMwZQakD$xW`L_*hCdVYxUn3|b~dpbSS2>Pr7sN`2_6AK|P49PR;k+YR}k@^R5 zX-et=h9Hg1|7yHkj4_}+nKn*cR}lKJHe&3mhJTI2zlDGrZ!*HDqhx08q$p8ceik=o zv4>8-`i6h?z=~0Gmf6~>9JXBqk4ee1;`nQCi(7iOib0hf=NajcGX!b}QEt?IK;#Fg zoB!d!h%OcXSxTFxf@lqCUaP`PWrdh55N^U-lC?>*msJ1HwU2+NF!ueE(c=g9JEL>b zU_>Mpe*?)ak4YX9<Ha1D(B^#hyTQfu;g=ZjIhw0JMx^bC-LGJR(^_3GxBSBgi=MZ2 zo)AlrK0-j`3{wASW!q-{a-gZnBlLle@<$KIbLqT0aVm;5Fz9#8<0Mjcp5PN+V!#G- zEmdr!|F;R&oJZs>{h=ZVgdnGD&FpjIS~LOb_fXX$q4G!gJbd_$Rq^IN%|eNO&Fl+4 z0B8SJ_IEMI1_%JM30;^IFqlkNB38efLKm<#>D_g|d6M3T*1g|hbqoV-4Ch2fy^l4W z)C1pPG<z-);{RCSrqQ$N2rahlW77tCjb4_b_r56L|Hj8O+8nWyJ!)VKgL%l8zS4Hc zGUT0ByP2)@C#EhOu}q(wBkX9&CzOuano{h&(m*26U=rIJ83)8)!w}q`jnO3Om1J%m z1JXybie}`mMIT<@_AZrAuX_Et$w>VFY%romE@sm9E@t*FR<57AW~!fafA$uiaj>J& zXXB;AKU&m_ROKCJKY_awpJte^2v)ecN;)!mPx%TXpm}QONHEkYuu^4S8)W~7vbTWB zE6KV*A-Dy1cX#*T?oM!bcMb0D?(Po3-5~^b3l^N<`o8{q=5;sIGp}E*br+Yls9l%3 zr|O=nI%n_I+QFuZCZ$WYd-ygxN+gJZG~Yl9{Dx)~WkpCNi1Uf5E_Y_zj;DvGkQgAg zO9B{V*M`&?Dd@ZFdYk;heq&@6WLD%m%<rGSC-t%k<tuL(iZ^%o>7|~EtMTCD-UhDh z@rDouMK2yq;i)N}@9HtRk$MO3q1}nB-UJ<wf5MBf$t~ozat{&Hy6jPcRb6!svfy01 z10&@*b+a&oN5$vpJLCRNc0%Y1@%5)vwO8Iku{;~*gimV$+k#XUh|h#|NV^cXAR9UH z9^0OqnBVnU@B%Y12lobvF-TkYbPomjBAnaX%}4%lk(`R#d{=@;0Y@f9n~WvBPoX*G z%`<cT>>G2K3$I|4u}5Qh;{kCC-8Ut{qJB;%x<aeza~(3u%hl|P-j~uA&VykN*j%H6 zXel&HrJv2dtW3Pg8tZ@jMP}v_^LBTTZ5RW}IH6k@s_*>Rh_Sy@QGeVNQe6^QJzZ<p z+cc9x;uvhAo+cW@#aoK`IEm@uB7xx{+t^@cnoxxOMR|`m()9<%$V~czz%5wHXbTaL ziNcpIYL-3@4MZ-9X8Ectg1XLYw6dbQ+XTaKGQnbh(b^h7{JYUIyE-7-UNelzQ1z5H z7eke5i^?7RMsFLd8hP4aZibTdAV>ZM+x{iQDVZRnLYbdXrQjU&=u%hsN4|smH&B~F zl9&;!OVFi3WD3zQ4LVBdL(o~|cH9FsJF;ercBChpx%O(MV?;LbB0l@%fAs}pz_{r# z0Dj;jA`lSoKe1XV8(UYK-+jT~Ka@&N`cB5bdxh)jN3O^!C~uu?r-esfioO{{^p#dw z&nEf9gwJa#P?^hDhztY~V$S+G6;DZPBCxOBp~k5wC=8&^H7ncko(=o<JMz3j;YE9# zPmpLMi64t~cL0(W+dDTq@77bEAMen0fz5_T5wH;G^8{cBBil(y1R-;CD7oM>+?V=< z;zNM<*-26bU?p4017Y-n0GT^U$in3)LKr5+RfKc;*uERo+g%7~JAMRsuz67MLA4<8 zzov)@dBTTNFE0tQ^~Ms4+@R%tT|@?&x<7Gl_;jJrZ%IJW*B?qD=_Fr-f3f<=_0{~E zE7^vGq(d^XDS_g8*%~8#J_)c8Y5>zDE>1F&QMceJYZ{98uuS1($i=!0wJ~EaO|H^l zP1vJHr?{no%=86UkPB{=GDIH0A*v3$ClNrRtjC?7Avqy3pAOO?gKYe9=ZwVP&Q(aJ z<TX}5V&W`rEyL+i&zisb@igiBCzO!0A{u3NT65?3C)sk*{CR#?hziF?=l0^$3GzE0 zy|bjRdEB@k_!kaLbBs~0h;#4{m;IK%ks};3?FKGrOd!d?S$(g}n3s{rPZXO-O+Qhh z&Fc0x_%ef3s%4nkAAB&MXjVOP{>et6kIe`xOO=Q<7c;tN{$_dGBGtMabUw1{%F6kJ zV<=;Dkr?i^9D9mko~Eqw>d#o}57svg&7ACcoE0jbJ0w9ja4l^i#G}21LlmfOlr-|W zi;y&_i6!gNCS}p1X{r`nFX>GS^iuBM;G7?ssUPZ@dZ#g<Y9y9u^<#iQU!VC&tbNXk zYz)w?tatS(;ZJ#JcJ(>o(JxOKKv+?lb(oC@8!eq>W5#H*(LQEHe$=8gB(2_>*YSHm z20m@1amL={>u8c2DpDsbK&)a~sZ}oSYLp&w&>|{;Q1Ba?eM+1vQTc3`o&!4me7a9^ zO1%MAJvYDNEV(vkHOPQFsL)~-Zb5OxWtR8ZG5_O&%}V9qNW%+9&sitkE*uVu`m#C2 zN>6SBEpahyMKhCGnvjQ91hs2MG7@*x5gL^3m>Z1kxOzlrq)_OX8-xPXIkZ+L`W4=K zGi61`L>}=|i=>Dw*OOOjqv+(@PHE(wop9e16JJjV6JMV|IVvXpE;6PVCk8HWSz&?F zph@HESgnaU^MWsIj^gR)eI(;O4zW`0-I&-AML%EgF47QKqSqkFE=(pu>kodN`VXhf zm1mTKzZ|}$n>x!tvP>2afzf3yzlZ`7W%eYhczms4=JvW_Uorx1?64vz*FdPW52+m* zi{avqj78R|#D>d8<`>l66`7G_yDcj+(nsb>VB+T8ywaUkU|CZfesX4w7IJ2qbI%o! zuImh{cnvjPO;OhBgX<aHouqwGFCQ{Uw_c>t-Vk+lSd6qbe)RcBQi4xKEp*5#o?G<m z#}rP_!MV1^`kRw?vW{r85B+MP{PhF;+|^_73Au$cnd;G#vEqck2U!Sp38b6{hWJbQ zBs>a}dF!k{;4d2WzU^Lysf9|L)HF=YZE<DIT5X`5c4EOyqzMurN?|jEFeRya7S|SC z{tOW059_;5sKD4;J2X?7>YU0dTW@1_=5Z~y5wD3KH`D$yK0ekO^fexAO~L$t>TxAV zFds-}dk7IFa1aB!pBzD*KR6!|B_u<XASgo&0agu6R353C$l@@yqAp=*F!pe}0iCc8 z8`S{6e9(sm5kj6?N36gNL=Ab`0j18zNgs8qdg=Uw>tHteSL$0{z%NfkS7(}92TyLX zl?=WtJmKFv)tx?EJzjD8(KEVw>)$(ycMjVxV2pLy;0$(LySU%7RYhPAGj;<Vi_iEM z6xMy?6=5YN*a#;17<YHRBWRC-y94=k@aYVSiuX7TS-1{s{m1G}<HuaRQ3K5yfq^?0 z;+aFvP|bRD9Fajh<+?q4W$2UMOl0-eY<<3`LuK-PCWIa29WH1Qub)`SydquO$J#o1 z2yxlWT}>|OX_SYbpBRuc42l!-phN_8<F<wa$mtaWX?I_-4cj4{6SQu=RE|LzLHXi~ zHJLgq3?OrdY^WD-ENWZ9;z$bBa^NPCwgoWBLWs)Ea!eUEpf+;oZeeR7SX<$VOm6$W zUd+u!D;bg9Ps#q87g;7WsN#Q~K5vAT`LZHQ9>Nj!up>1#Y)etTxkGn}8$5WoMCp_3 z`V_N7?=vKE3Dbq%y+eMP5upZ=*OE|w0Uqv1=%R;cGawUqEYVlHIJr!m_=Fc#`^)~c z=T|Fc%Y9m1X#FY5g7_hK5E9h!tKbdg$l1;slS$Vke4fY<$w$T3y0SJZc@-9Ldn-*0 zUHf&-(@SF{g&}Y%^X+Pzy9mi4Tpxwe)>(QgOxHG%!HOvPb!xo?OTu6@^kM_5j#D#H zNc0&m`!8?q%h8shyQ=95Xaj=j=MZmg4Y=GOdGCoK;=e3U|F->d2RLZ_M=Mbob4N#j zYxw&|7jWGEr!Q{SzxQEWvDX)zndA}h(?E^kN7#fveL@}#!5~kc(DSdMt4w2Er`<ry zzl^fIo(1g01P!430P!bYt~E~pOARHHopQDuJWm;}0K(Q@z!@S?aiLJ0NED>wS*qqT zxD-<rOi4+Tc2lEv(E|3ZJd=rcQVlUYw#Vt)oMbe9wVE<v$OSR%l}~CfI$`dYM_VOg z#V=YnG#Y;{QYl&fk+0Jfh<PuQ{e?y;5mickgEu%xi~q|rmyp$bnG#>Xn4NV=oB5cU z*KBdZc6r0#sWTmIQAh~md6mdfG*64xB2pBPyDnQ_Ia<5v%uIshD9gjJOajXh*g1t{ z^<(t;Rs5t#f$}esHrfMrjC?INWgl`Krb1kM(7GAm8Q>M&JEdrK#{vD)xwr?u!$i+J z1~CvLoEeiV@wu{FEg#<KRaiZ@*~M?|Ps-1Op<WafpM&(TSm<CqCYR$r?;A?djvYc} zV9vvX1dNofb^zm=!qVtX^bnn4v<8lYCo|wd)a^g55(gjYXihM!dV*k*eQgQQ|B2EM zg_Tvi){p!B#m{!N7+PuSri0<@W_VUHB~P#wyQ$F;NOGWNkv=P2Jd5yL;Gt1yBYC07 zVH#B}h%oeEPUu-27NKLTdm>K@W6y?=DU#`t6$`^KXZ)5F^!OoHOdY~k6u~Azd;B_E z+HCNqxpr%us=*mMV07<~))FJ`qL-8)g)saG>%*VyJ@8lV3|r;+=&&)G?T!#iNU{nc zN7Wec{Lh1-$WT)qBJo3fY{nUv{mDLan%L6{)82c8=HuwT+2&NQEu)hxso|S~1_RT9 zr1u#?x{D{z<myA{c@D>$H>)gd)E@inCOLs9`G|0CGRv`oAcxM_Q85_&Bv<W)oTIPw zuPBuJhMYFQ|5t~zv#Rd07GV8j0PFup4(Gqj-c{eh_&*%ZDwY4+hV(g8U8thAA(}w< z>SZ*t>d}*oMc4fjN+`>crs2PN*33oyS;~fcCTEBKA_AWUkv0CeAcrAGsouCrlrUY7 zGtPsyX-ALgw$o|dO<y1i1J+=UA(q2dLbY8iUI6)6>}>3CVK^lm6*QFz%YeMHz0x3U zu-l|fQ>zMnT5@kJ-EzKy8KjOaR*>c_4bNU5<4;Rp1}Rv?yP_i_6OUYOyA4sonek%d zudbMQCIQ>MSIDT~#*@`bbx@c~RxRbhZbKC^;joD(ShlLI3`OS<s2X%aKS!f2R~E+T z(7QC3_vrR+;KrE-G!wX^1zLl8sF*v`4z&|#Uz;sr$=5(&`{kRj!@Ku`$Xc+1>ZzqG z>R2u_2`5B^(AJU)lb05Xt#OeCVo=*xBIsIoc8zam^P68%&)vv>MER*UujZRnW?T&@ zYJ<)yDvN!Pz%^y8DZn>%S{tej2g8j}SFEet{a8Bb=r>r|VFy=d13gUJQsI-XU#q5G zzHXSxg?Z2$rvQH=tLCs~n#ynd8I$a7&rPM0;fp?x+X{2T28)=?LG2>3z^+{9?#*KW zJ3vxr!wTCstwxevC57uIbI~Gr*J$75kS-=`%Vn%>{guAuzR<K`rpU#k+KE9HGcTrC zHfI(Ad@mGav=GN5JTUJY4xGH{betNIaH<tk$|lHoT@B<O*&V{`k8`loA|5Fr!vgVI z*kFp}e4JB4PecG|5V3G(3d0FAeLTHFoYpH$BD|Ov;z-??%Y|^X8&PW{b)so&IAV?| zx;I{V>Qf|x!cCm<CW3p&9N!%6(I~76o_EXupTXy@3h(O;RSS!FKN-TYVs57hv9BV! zcU)B}fkSv@;>bpG)La2DMvls&nXmi@NeH-Bc#9|x=wpWI2#oa&BurvxqldPC9SY3m zJ5RlUp-=@F3he)6?e+Umc)vxE^zT8iFr&bRQ8VTxU_S;O$@B>!9CFGmnMRLEXlIzo z#zbN<zM1$H))!Lu`GY7@zv)|)LO0g5Hg=|!qIC0y<Fxyf;}l>={`R<LS{I0BE6AV4 z1m!wH4By0ff-;t&CN~reGlD4@g=CN~%ovmU!>jO6c_b?)m(cWA^Nd$;A)cBuCUH{J z9A;Q$=?q(TY|k}s!xN1{%yJIa{uNd&r4yl|AKlEn!4p$?wp=cw<~Uf@+uU?Q<yNa} zv>L$&_JTC3I4#xl+J>7unv+bdeQdCvx`FQ2t$41EDV!ASZ3`<3xoQv8kRRlDvGS6` zX3a-Mf=A6lVD3L;HR(gw<c;~+8<{2RFE*8*r@LswTY;vHwP3p5esr5$<Yu+d7~%ra zydSAYah^$DdU6k5bN&X~snTGB?V~hTbyX9X-eqp?8{Ubh9RZiGS}r?Vb(vCZ_Np#+ z&nb{42p&MmGZ-9c3QN6c<t0CUi->h>gYe9WnL%l_%{jTT=fYqm8cc(UN56{K!aK_z z<7Rpi1}O}^OToAnQJ&soj2ZsM`{IjBbBNO~-m)-5AQl7GR6X@V0I5CP+p)q1u5xy) zmQAXsk6|5StC6Vm3BBa9r2c?<{bU_NR*jqd*LN^zTeT8VTEpxOgBPa&@Izb*LNd{4 z7oo;kv!d~!fon<we2Gx~)NEeC(JuYKAId;I6gGldLKX^#&we9FjdatQ1U}$LwA>;) z$R1OKw$m=93x&)igIz5QbXlJ`yFwRYI1qh@8J_$oZyQjZDfK=UKp&ymv@mH5;l>9Z zfUFIIKFH4Wp2d+EH&e7f>AO%H5$Y6{m`=^GOT8f%M%Qo{a6u*`c58{(OIp%Y!XNA8 z)B)MWnSX%43_T&D_nQ{7u9|HXI3}5=iTdDfEI}t<Nn#E?7fR#p0PPle@@`aQ!(8o` z0%F{X|C2ZC?@y=x%VtE9qV{jRS-cGz4M$`{B=h3kR&yu?p%pt3g^+CWnvj~}mwC1c zMoY3w6JsZZ_vp`@-c;AUfOvkSbE^*mL<1*0ncS|^yeXb%Yy8jMAa;>*d`wFh+XnqY zll^2uw++hQGZ~Gr+SOofsLx=6lK}Zv1}rDgFA1*1W6CS`F=A?3Ql2>^+P^-N!S0P) z5*ywG919;tZwLFJc2Sc$QSV3)g*tqXcE$)yzavJxCc<po*^aH~I%Te|m=0yZ46n<V z&w#NvoN_+Yk05+2oYpsV?YdS{;8!hV#ez1*=wev`c@2R;RMY>)s99dyR%^hBvX3oS zTyC^q(}<{|Bi08A5Abc4%qJH4ELLPV*h64%QfkW-$nlP{@2O4|%b7Dlxb=ahMm$QH zap=3CgTK!ejh}tGHXC^n(K1*{=Z6-u#v84gL3YvarorJxZu>byOF$A)*LVj%r3;Po zLoxp51+9jHE)wdZ4z{(CEm5g*%Q?J4U8>IF7wNbcGa^5!6WPv*`{mD61~j>X7Ppk- zPPqsCQeKLbykCg!i^I_RVRl&vMQg-=ofEZ#LqKW(b7BV|i{l@iP5%D&f8RX)7j>4> z>2J{kysoSD#u}2ey7?5K;f*lHl==65;d7}Nh|=<~ukBXs#`f*2Cv>9tgX9<VBp<@a z_`aMM+Mlia>tz7(yPN@{<e5!P&R!pXy^w$5q;bv1R>BH1hr>(^<V*-I(?>H#b;MFm z3~Z$x@WOHxKG8<KQ(iL_zsMbm{k*)5-*)6sV^S^Vw>yu=<a8f3a>=WRhC3aG$1IJe zxvR-L2p4QLShE7lOC4=mbGFcOvIV#4V68CP(%Rk&BDN%B%CzDl2<|O|7O6ktwe9XA zZ|{z=;siKJ6qu|8>-f1+yvJoSShLushDxgQi=Z*!`N+$HK&hd?RCdYk;Xp;Fgv&d~ zpk1_mk=VxDZ4f&?IvfJ_Xe6daMIH!4N2m1W7iIFETcTWpU}8|J;fO9tOkTw2WZd9~ zt7n=bHRu!^@zsqcXJ7W(lY{7`{!cJ{k>WG<V1V3}Yv{Vt0tgKQ9jcj9C!P<Tk-cwu zDXzFJ77$$7dJ@Bs!I<h2!_Kf3Ze#LaQJjNM_@A4qa96{pTB8ewrtpb7@BNp)po#K? z_PNS7PJY`oX&bz|zHl$l_f*MWrOO?hKS?X*^1wrk{4ST8hp(O&_MJmwJdAQSExt>~ z!_nKwIzB14VVFa(FO}=l_f$Th)s(UqCR&N}gjd4i+yv5CeF@lDUl!SZf@)wzWaHF1 zVZtD%710K13TwTY`(PtF=g??+j8|aiUy$bdF7Y`t_K>I4!O`?zr?gHKd;}eSBB)Cz z@myoHjP8PaQzeGAP}zJR9DxE(kVQ;o`j~f~<%CXrR1&MmsHp11w;-)k@KwUkN?HbA zV3|K7dXs5AR7e&)-=KpN0o9!oAx~xt4QZK$Ou<ZFH0!J>h|h$LE)Nx@h=qaVuHaia zx*aOksgYl5$$K@ON6&?f6oCDE0_^|)hkN|@hX+~8o4=jXzn)pQ2p;JXNsB=ELq7Q> z0t=2n`q2<-Fbx_73vbdDU=Du&%{8FD_>n>Hc?pIj6WR61j=9@*Dr|ok3EzG&{4&M4 z$;sWK+tv97sfSp>^%yssH!dWkBcu=#E_Ri=s5fRA4}&F%g@ze_+-w<w4J%34J)W!D z;T@r0YSO+iyD*cz$B*v$(LLBwM~I`nucc_+zI@vcm%+#-T4>erIM23yGThaP#tYGd zFF?Urd%T8&2$H6+YM!UtoXxxLT-~I<PNokJ%2gN5lkB<Ys-$LG+2k8moQR02W0R0Q zk+fK=46|r*JMRP4pqoN-ywN6DYe%vW<VPNU5**KmegfNayzrq4@}1V%)1*Fb&k$8& z8duLN6*wu_&<oxt+N8a?hhwndBVPgL>&4Sz>b_*0!N(lPCc#xk-znS9_7^zGqQ%bS z&Dv(`W$ogMwGLP&JpyAr%ox^62CLg2>WF?S&LHD(C*Sz$zNQ%DLkOy7vM_|h3O%}R zz*fAq38}>o_8VZd*=WKlb-qEZAP+laYztgFm@S{(h4+5o<;}V^_<~msO$Q;hK%hY; zp@~TXjlOj*zKxO3Oqr!6knThbz6CBykP<O83-t;c^xUI*i4*<^p8kkdUe9<}(Ufl@ zmlsnGn0{PtrOQjiOHbkDb!%Zr!UJWSDZ`LZ@k;0++&%>GgwZTA^gqS!a!GmtN%5c} zYDP!6KuVmV*@%&}*oCmj{zzsBZck*6Fkd5<A%#Q9VqsfW!=T~2xkhVZ-Y_>!x_};4 z&bx<N!FidQS$rLrR%8rnY$WHFSP;LO`m}rlGCA-?>J>_Q8+e_1KxGHtfGobDRl*_i z`GrC+wGk>_{7!)#Y(oEp`>!*88w5!$1i<3k0q15+|HKRak5yoj(x&ZqfSJouqQE$U zwUjw3tjX(HDc_keq>HmK60Ram;N80T1v^u=>^Cz%@;~fEkn!C^+>2pOTQ3_0fSP~L z#=pxv_d3X2-SqW&{a^>QD2m3-=CCwcV6h98tqC|MLU5q>J{qopO!L?c)N|>}6H`BZ z{LbBhamRZja1C;s*uMP<lw9y(C7WaNej)}k$S{vc?#2MIq2XRfQqK7Uq6i*-^VdAA zHe%9Xzk`n-Hctb0>tcnp2`4LLi&~(j)V+>8t;+5X4NpSiYjw`EBjozv0&&_p)gK(@ zY%-Cqe4H@j5iJTerUnpI1v!IE^i$*|Z!A0H4p7pRT<p1-mnQNM#`@)T;=P$`a*7U6 z#>!$_9L(}0fbvvzVQ)IBTCBZ%L`z@gSbEQb&@Hw)f8Fe`n;2+*%_E}u0j2ulJhx=a zN_&D@7ZV?Zrf-{e+uH66!u2!9Ga%Kj_W1|YYD7l6D$P3h9Ru3smbC8H7!hbgpRd}- z$2z@3#0w;wy1n`zQ3UNzAVch`uuIRA=H#3dwK~!u>eU~}m<1?-sT!mORx*vv4ox_J z;qEVDGgv}Rh+@U}k*wfW`eE4N-XU#0Ed_Srz*jG^B4=!7Of(m#DnK8Zjf5l&pwmQ2 zd}bb<LdiKn&7r}|i!bDr-D$DU*dt@|QtS^mcNB$l)v&1xOn8I&t64<?nI~@p%*qCE zxW)fZ%u2<^@_!s{b*jiH0r+pc8%D|W8$LlIT*4qK#FHXG<`RVx6&0zVXa+fKv5|Z} z{%Ml3VwKpv<$Fn>;-&0<0pWJFv)CJfPXCBb<H`1!P_UE)ufu8j`r32$*VhL3x2IEX zAon#@g#K3lkJ3)xsK_S@8I*PiYV@#}6||yuumZGhTT8?khY@@S*6Yuz!+-)FdzzhH zmv{E+bxcy#?X`$aTRY5-+Wd|8ld}}VRtHgM7b;rgxbAs`LfgTG9QH7MdmYhQqSn$6 z(whauF8c_R_3tSfrj;sp$;FL&8)a<c1my5dtz^IHT&6h(U(_oV?tB#)vnXmTYma#- zE{Z_s1hc7fTts;yz_`s$C;8rk{M2^Z(TH#3UTpYAY6?S|30i#Miuu(wCnZIzK=HeE z%$$rV8^y^|;<g7M1GI(YN4B)-%4x#RQb?mKR_Q96VdUa{Xl8v2jdtV`95_SABRrf{ zHt!z0#!5DdiDYUA!z?Uu)t4h<OW8r%!#nAj*SWe(s7q1Q&O>Aq9T9dUDvwy@yj-b4 z2JixPd3)ptg*AiJr-LKC5%xhgpc|G@<5k2opVrAB0}Pp#mB>63p`LG}5rgfk+2f0C zDtX?%1@_jToKGZSXF_TN_>u`pM1;(eP-w4sox{99<IV78toTnQ8(|X87nguZtgYY) zY1>0;*}5RyLq3uejuaEjM*0R$@CoSW%uIIW#&{1>a?O^5V)S74=!U_hbt9=szDlAX z=O1ch!c<SiqiZPMJD+BUhSu8h)oAj<xnWKjGJM+qP*{0Ne6<gG?}@8k?MENWq;i4d zenfHskDpmiHm5_foBu{cHpHE)QHWzNBDhN0<bt>&mYC@^QVNN7i)?>eQC%pUl*IKt zVjOr8oKpOes5r`a7{13PTKT4Tcv{)fLS@j7^c!dJ41n11d)Jgf(j_;s{)Fjxe!??@ z$WCey7TQ~C1BZ-?4pB@XMuvtKJhkt;-0Kliq1GZKARq;*{~)dX+eO&#o_CgpyI$ga z(_7ZWl}wkHl^;+64I<L0BoLvXI*2fs1W<sU`T*gBICyXx2Q%VMw3N0*-a-+WrKLIR zyv4C7P{9&a_4Gp5V%OrHig(vwOXIn-yLXi$!MW=>J9C-@IP#O&S*PPU=RvmP8E3cW zSxU=vhaFB2jXNzmx1A(wiHhUUfbk(KC>hTos|d;Pz(;$`9kzi4avetL)E(wH>bBri zvS2BlY;`6Yx!`fgd4PgzV%TTWP4WVn$YjP~lvE6ILvJS87rYv*?tG46;gZbb1SkuW zd<(L&v{63FLOO?Rxnc~ad0|G6`6-cLlne@i8o4P``dMYAd=5z!rDD)T>NeE!vcl|- zo7X&L@tEb9CL_|w^GxHhFwzrA%fS<jg)0MIZQq2WCRBuf!Im-Bsk`fUg=sb^(!#)M zm>IMowTheE8`WKnAvGx;3kjdrE3=MEYtT7cIK>g7ALut}?IfTES1R{Q%_moQDb`%u zT#Q=Wct#Og%CJ!Ori?N~7siR@PFTbv2`xPQa4=r<K>lnTfTg{iK(?0^RcsYMS!@+Y z?Om^8-uJ6@Eb)ugFNp?CE5-q|PkL35A*YA+@&srNhW>RGtGm78t&DhZ!Jkt^T$&*A z{oF__MqGM-82hDm65%xT*Xi-NMXl$EGko8cJ+MTL?B?lU##zR7L0bgPXXIYNfFH0H zT4~)aGSz^A7Bx=WAfzaTA2L{5(Wr`Q{z<x`*v@MsmbIKp$PH$AJt5L-MzgV07v*sZ z9r&3-udI_7sp)AF8%dHg*Tt2<!t|tFDmH}?aX<KI11VCc(_}q-9_}-?#qn`-b(wZc z^;tc*pYMEh5365!v>SsmYSZUaUKPs^_7<qw^caZtiP|e^iHt;1U-`btrmqd@S#khm z+`spIotOuknwBS6)7*nh?WRJquCj;%*A#S7aimaBs<l>Ou;Lz@(iKiC_>d=W<mzn~ zvmS4k@`>&H2i_ce9W6}l!hGU#Ut0K~537P~S%=yPun@Zupw;o;Z$8}Bi$_#lAIQSt zwl^=&IETx}c2j-FfvkcT4*2P6@Ez9{M)4|9PGQlWE$ODQB5tcMUIyfp_LN?rp{Z~* zFR)|3D~E+V0>fW(JsTkXz=hbm7SB?S%0pjt|E;;9u@7n*+63OhXyyw?2}%vFjlR_{ zJyixsqET_BkCXXblIZ<}=@J{_2DWOSBu1dn7}38Qh^_WNXXd0&u_PdV-`K3BDM^}i zQ(`7#a(LV-HpSv)V^-%{O#n_fWvLJBhCb6rS?EYO%G0<xZ5)qv(c#5XR)y1;i~Z>7 zpi6})iR6b?0e45LsxS&9u-vyc=da2v*8<PF&=6)1k*|>5%xx619A$Bq^OlqC1QjVh zh%`TqPe7Cmr4;3o35#wtMS}s2aH+_25lg66QJWWbId15uir38l5^Ax!ng%6%i)dOY z4!$29Cj9xtjA=Pjqe$0tZlijdgp-*`rdy>qRdKm#_Kc)M3mMYcPALXAT5SHDtAu`J zV1<acs+9=W9<@}1)u{N&WVr~KPia78)ciZ_w0!!QyPy+{=VEmaUhmv%^#s<w$O|kN z%<{99iBUU8y~8x-bKhv6DWvTy3@l;%6bs3eLkMsiF_O^*ErmovD-9C#@+22Vy@)4T z>aU9p`QhwnzlxUAT!f%h55{D!%va9~I|G+;^-G)Mr7rEP@AtsiwDZ&!?Wg6!BOU!u zpmY>U#nr}8<m#F0bRtZ`vwV(YB@XkjEbOaw>NA;`%%Fp$0R_U8HIJFR%#R!gR8ug) zeVn;G65**O!uM#glV#8oL*inMX{^bD=XD??GHMPqC&PR&uG=;+y7C2{m!t-&n`kMZ z2G(msu^*+X<JQr-{A-|~Ap-V*nNmd}MWt@V$=Ty9RUET&J-)3{TIQ9u*0BEom$FIB zaBEO^XgF#5f!3D9F#85c+a_aE(oRiH`;fUtSc>B`d(EVJ>P)`fTJJEM1k;lE*&$`k zW_10^UFs~3UcFxK7FkXbZCDZ+1*RlL<4UAW4bgiv{^^I0L9ve7xCN^20N;XeSlbxw z?071Oxmj}M&CmQ9@ws@2#P7S{#o`Qe`SoIEivd^0Qe8w4G@PY4m$4@;KPs+jNp%yR zXdk#rhl#J?b~;Ey5*uG3I0#B<ABw#qk~Gool?AR;hk$D$R@mUx85~2MRX3l6-R~6U zc}yfyytvIxKPdHcpC$`~GVz8`bi7rJ#1HR<!75o4%zP>V$kGvm6y$&F>)zR<d5Ct{ zO1MfS-Ap2feaH^d9hpt$nT_EE+ezlBAez#~q5fzJnhg?~%tPLydDVflPd50N_!U#U zu1|1KZxk`f(y&1&hfoalg%J(Lya`F5iWR{?W~DB|)wo>81nx(w4o4LSTNMKaHEdwM zOKwp^ZIG+ol1*B5qnkim+i*O(3fmkFOkjVUn|^Ll5kveCHi0b%=j_S1fgL}y4m($d z4ONaRhZQFn*DYBgo%$cG9abZEDxxQ-R#^E1ec~K*8cR4(!yvs3sMfYHf#$L-OIk~7 zL&%mUp@SGX7WC`ZS!^##APbycLOyz<)RJ*fq#5YC-EA*lR}l6#YAIRE*S;22&c&<k zfo5@;(bNU&GEvOro?p5LgmbLUdQn_vziiW`-Yn;E3;FFd@H`&C!pTB-fnZqh-Mt|Z zl-|?Wrsr*W@2dq~HO!hybUypVaCLoQ`UyDaShkplWrDNsXNDOgDZchYlcKgI*snx2 zO{5OPs&vweJDuNS<s42)%&wyOl~Y8aeDm6Ra&A|Bf+N_;bPr>5f&Npv^YiN`TJ>{K zB|iKNeVrAMRWq0YtP@`Qm%PBB6z)pjNJ`2{)&A%;)Wfyn?CBY|t4>w<_#(QsQa%K& zbwtR)M??}ie^6?0j>8)E&8^ebwc;s8_Jumy<j{HlcvKC`Wqqp5S?Q(J!NBf)?~EIv z(Tf3-vC+qX(MUp`)+cXQ2LEj|&lnH)cJuGh&Y9Hp873ANGv*ZP>8ECV#~bcps}wF} z9?>2kTtZ>k8pb(A9}6&adEz}#QjAo*-70WRd1p(yj^+djKW`_p8-;w{wdRsO`qClZ zN{A$jw<Ru<#!?`AbYP+K&B3R6X&h~dZes#Ad%>)*z*|WEG$AMZ<|na#c!PNWxib;b zlb`6-!mOo^jVd;@H*`G%uQXPyhhNN?xb8th@YSLN_W}+aS$A<$MakP54H^6l)JB#| ziRh1Q?}!`VJ=mCV_OI(D-GXLV_$|8UUKtk-hr%Jhob%3cvwZpjfE*stL!p+DTIiE` zR)uiuntu$=OuKgghhU_Ksa<zW7h0N3n1JzT*Ora<r6Nn1t9TBf-=bKF+#EN{ePc@Y zhzgYBUb8a2AJMOIP2D$*)5=L?Cz45jJclAI7>ouhaFO~6T!hpS03*s=pwu0}Pg>IO z><e_D4?zdA)yrA_bB{udH<|Fh18=jRzboU$t^21~v|0@JE1oD8U-9zBu&&hhPUyI? z*cRQfU&#?<M@ZbS$o-C-Q#(Z<$E_i)$PxQ(;MaN_Jf2YchRyMoJ77g>cbMga+G$#9 ze&_=1t`a5xj`T8F7>r{CQqa;F0iJ=I8ix~;H-@+S+=B&_pO2iA69pKq@D3RsdTdF& zF`0%V$T)t^p#48R89K@;{m+vT;r50Z;%gvVHoajBKp}qMvW}s9;TKr)B>Bj(58=d? zJZC@q+eGqyiQ~msEL0z6cN*=_ymj5p1mOrt^nnkXJ{=0gs@YtP3L|OF22Eh;b?P?# z(PtxFean>yR!E`T7`%D$E9Hr5(i1O@j%*fX(kZ*x*%PS{<@nA`$tfXca4vv?z!|X& zo~Q<5kSF?=E*VUiMaP&`_Z>#@-nUJ|BpO=-u_|1j^jK{}Gf85Bww8JbQWWKM-GwLz z5v`3V=y|!)%LniEQl2kf-Sp;kD!uC#9v%TDTrC7@ZIwR}_P<lTD!QO9q^1!~28uJ2 zI(YP3w|$@Y%!i36izuwfP(K&E)bpSuQo>)346bHorfO$w*fGZ?q{_|~0b6atm=;bA z7o9V}Ro!uDK1S>TKN&zh6h^k`6D{s18(KHv38!_#Q`>=93di52dJa#-*Ta5|G`Y?f z3GPj{U!p^vp$alfP&|o+sZ+v2jF(v=<wl5eX&i@Z)jShb#uJjx{&C*FL`$Eqq%+)f zMQ4+!yG!=KYm>yk<MIG=7R%g9O&)SoX3&lOFgG4ivy0daugBEoCxpWrj_5I!Ym(d` zsg1)G&@R)<eo32()Nk+|Pmr!@rC7~-jh&s5YB0ks(h1zz&ds~=1paU*6TRu`L)T;+ z#NHJ$Pc-?V)V>N6JSSJ^Im6x1xa|c=wn4IN68xpMS4`Ty6VoN@JTngOcp4anJNO=W zHuFV?Uw;Y1@F&;p6Z2i!yugB4_1=Y^IHkE$60|HMEg%114zhjY`kGzbwa$sVhHiww zvW^@D4E+?2_`wyG@RHJS_)lg-uPi)FNG6b`4dJoCL}vw|PYt0<5qKSkp|O%HHg+}* zg4x8WD!Lo;?j0+q<+mtq&}$*7b70vTtQ+A*E;_M7$R-DR{nmIUJx{2^3}WBpk9rV? zRLH)SYU(SCu+yFVd?~G@FE6?1_|$!Wm>?nCgLzWn9&U+AitY9j8xu@&bCTy$B9i1l zOJ=`MN?0C!`zz?M#K8~+%CA89nZBk%x3te+p{9{<%Gw(PNgi!X_$aP#7+rOGE3T!l zDznm%GZjpEQO|V3Z?N1Zdyc_3^r)Ryhbg#<OyF|{K$$wjT5uNIHg<<VZ65UD_t}#0 zdwGbQ;Sjxikbzx$5oxSSr3D@sY4WE%YE$`i6nCrVY$^z~$Z+nWu}FCioCoHp7^Nsh z1L%5<H55l2pv#CuF`^T-d~F2BJXj^yz>E7TsP2eUckYY>8Vp-Q`@S-?*|zCzIh-5% z=)Mk$*+aSJK~pC#Eyk4?;|Iod$0OVLR&VkIOKFGufD?<YOH?FIN^;kPV^yt_uu7Vl zqPU@;sN#xvH-*?2s$+rIH<qZYQq$w~V@+1G^S2QBRy~nwE&Nb~iSi$^sVorqMHPRm zC94D-T66r1okP@}X(VF}D(s`-Id9}5b+*Ld&!)&v2;>f7C_eeZl=cQ_hNf^cggv29 zyPPLv8+@Vt!ud8sdkW9-We<3c$HYU&zK;7O#J^y55Rq$;yyZs3JIER^Ri!S1Y5Ft1 zhqoB9ZzR9CiRtvm{E+FOK1U!-5Pu{{-n9;jXiZzHHsDV2<d5#w$H88E;p+&%XNu+* zMD!8<s&&SNcy1BRI#(32ag`7L5zC{srO19ER)Whnn*h4yYUrNvHQLdVQenC*S9Tv> zjK5b7^Qz6^gKvzlUi1B)`*S2#D}xkX-*nisjpi+qPu?#D<3+36=<IIkqlcIQanl-I z`>8m4BGO%64{hV^EQ}4Qpe!1%%^nCY#J8{`2qJIX2|pNczPVlB1>us~*i<j110+8= zL;s-z8#Vg8{@EB*j!6qiayDkK)FIca*Kvsng+uG|n%S_vb68IgFlx_l1K(EE&vlev z!8;zP)sk@gi?73B2gAZEhnO!39H=o|GT4<%Kl?;rwV^rRyhhr)7~_-4su2j{KOGt; zyyD7sN9J!*9oAu9tFUeiBW>(TmD%I+&DGU~t|-?|Jwv|9$~|$)uDMhqzJk1!+1rx7 zMvzy@+fe#MZJI?SGw|IOZMvkt`Z{$2FJPU`Vi<3=I6w!xK&;=j%az7C`o3hdi=o?o zKG<(fDJk`G=;-L$xhGO<YVYB_L~v0<X*Hd9yLR4|NHlxJv$7uRri!b~9x8yu>19Ln zfsRd2IHrAB%n7P`Ztldcf<R`melfRk{Dt<l0%BgKoQU6F&pQZw>{`lP(HPogO_SbL z1gVPe8)}MFju0z8d~V6mH#MchlD2zV-aGCE4c{J@XZq@c7212`mpjw^zTts#xzr<a zrMr2I#-pb(A-VO+^9r13+i!PC5hZq2T{UYzaEl$kO|pMd_}Rsk{k0~V{H(&8$2+Ne zbaj*iB&RE0HZPoz_kpC?2X@*V<?!AVK@Rt*Al+Ab4qWAA9=M{wj(7Pyi;297>SF6{ zZp!EtnHGB_bM`GRA?sncl6xG%rP!8Ff_K^C2HI}Q?BsArc7ySZu2p+l-@@mR!i5*2 z{rqxYnbR?qc78?d`ni_0Z!{tO2ff)M1E0Tqr_izb_^U-1Wx<Kz$w5O*!?~U>+~BE6 zcSvT|NsV(xYxK)aCjRg%_$_;Vci3_N^5%pO{nO_)&eo(C>%#7=mjm$@&5rxewr6ke zvep}D&R|{uTf~Nd%`US4+$R3Nvj(GoC8z(!8ThXwX0>Bo95qZI6Z(mIX-IiGKe8jT zy?Pp{ZzL-~lu6$P0)YVPO(gS&fmt*OblgU+XhN1UpQ|*_U1h2k%iY4#=RhSdZ)JRa z?ml<l<jDIzfaN%vxGWimfOk)H985g%9eC&5Ik>#JpPzOEafI@V%=m+$=0p;G39=xu zR~a-w(Ko%!bmOVnQBqLm=BA(9nr&4LK);N4>!{persBgE!9~ko3RAPV;M7vOe8BPo zt`WTuLDdcaelo7WvO`VPg(ZTGMs%O<=F97E8+ykcG}IEf*J62rtA#v%4*li4?A`}- zvEZ=BlJy=~2c3%_B?doi_?XJ4Qm=&7Hba%o*UJ9;RN69&>k!>BjE8P78?*QB<8!Y6 zPYLF%`BT9udAqOA#|oxtGYv<45PEhKV?|HjIeC*9A5EA{HjzE(Yzsvz+c<eMu$UW$ zFx_{y5aKKk97Q)qR;E<Do-ZW&i^ux+xcML?b2AxDX)!3_OBY%#4z9PgzA}h;*~}$v zMWzAh{JP97VNS{mOcokLP#OoMP-K@yWxG=u!P}=PA*0kOh1{ku$e<n3qt=vmGW>%X zEk&m@XB~^x+cV}r9`FcKC})-t=rvQD(Ok;nnSAE-ncXMNk<EqPSuM)2SFfIYon-S_ zA@p3Bcsf+wQc`OQZ)AUDEX-}o3?6(SLKXA6j@j7cjZ4N&Xy*GxV<|6Km#XqCJSdly ztjKVfQfVy^5h!X^u>>D=Y155kt_GcK4Qr}YkW6{CrHk#8tm2NY;T+f@F4LP$zXYvG z4I7O*Aw7nWrZ)Ku#hg--?4U!kLC=%(VSi~$Si#O|6|GB0ZTjbf!3^slHS5<SrS-}) zw0(^dTnXgK;D!j1@dY;tl^FH3NJe4}^aXBaRgVdE1|}V3_9tp;@q3@8d|%I(>1+6x zXR`e88SC!JpR>W<Ub7eG6btf4_eYgWYeZjgJ*Y?#r;pP7>%ai)t{48lI@2FT`snWu zH@cx-W9(Q>uh6ECOEJXx4zF3c%uyYfhoF?C{q~{nLHf+$#4ebTz6yMo;N>5WUi=mT zf{O3PZRW=R(Sjo~02*)Uo-1?wD8gS44!;M2lbof)FUL{c>>kXgOdqOS5urV2b7JXM zedfaQS#;2L86l%h&0eVg{K69~WG#&o;dq4HaIYn)LCvQqtdpsS8J)f%mX#-{g!LJi z-JRc>k=reg#1PA7TP8Z14$hRZOdqs3n181^oEwV|IKDFyb?PY|vsYH)I4xgoxMm82 z4!#{H$3PqRp;~>R-jH$^sXz`F0du_EO{$;D#?lR&63((!Tfzp+@g#2SNO_H>9RwA0 z*FiXAL)1}&JV`5=s$?3pEs4$QR9=;COzf)=NmIdzmhJ6aiauAjh)be%VwFY`kMPt5 z@ulR&7_KgSIh{ruXBNf_pY_v(XMoij{o`{-oQySW*Ofr?4H$A-U464n_+f^Z0Rkx7 zql_YWHky;uBj!Vp#%I1;v*|EW9J!)kW=v?=BSU=OvF3{u7f87L-MrkG3ZRW)R_yi9 z_&bjm#lPL~`(t&*<mOxf<w_GmCUMu0spgU_Af)ODTznKn=|`zJ`?QWgj8Z`<dY)Ts zikj|!`Gd-ZqBVLJ#RByij+=ZzUM%a;PyU<<OZD=Lzn`7u(tHGz1QpvyWl|gBIn8-2 zO%WvoC8^_cnyPMhDR54TH)S)gX8ZX8e$dH8&HE>BbRi#vf~6>l6ThfVH%$0#)PZ|u zU;OCrJ0u|W3K3$AfmB+b(DC|1?!}DaL;E>II}~<ew(NsnA+r;8LHs}93`2dA#3u{) zS4w2<%F0rZETL>6Zj|lM4QE8%r6T*{d8lkJI*6?Gf}Qn7nk{sf(6}ABonW+U{z&}I z11r7aH8S}~&mXpwdWn@27s((BrC%@-@{+c3Bay-X<8Y%;@FB<iE*B0$!1SbP>^aq0 zmbMUf!^M`H*~sYJC-Dm!M>}<Kn~!~-IzlYVwVs%s#qn^Sdljr&_0S(_tKNv)B13FE z!Z-;mOR*725Q@<47fj+++(QKb@(TTxBMBs)iQK!DqT6-iA~;X>(Tb_8oD}BpP;$I0 z(*}~?@$&Y>7$(K@wQ`1;rRPMc0vE*Am01Yg;NhtFievBFL(5t(@EgCb`DRLH?$h0s z02JS~at<{_tt1iT3~s^f`VBd#Py<V1ByuuvL8iMs&?XVKQMA0^mqPhZDD>qvAzZ*I z$)h?VK;koP{7>o48=4I=SY=6;bl`QxIGha4U)Hza=(#6<s3mG*w{6H(uZ$X@YoLgc zm(GfY4EW`r8|qxTdY(D;_Y~FdpmUq2*u(X<%a7ikMG@ufv2Wzj5z`~GQ&bAO&c56N ztyf{oz2dend}Cbyh8bV9NnGv;rtK>e-UltYh;1}Md0Q>;fV7^SWHXG@gM^MdWWfm~ zECx|%iAdo(Gf4I$W!!DSxL%G4CQ!uJ`m9)5f;~vvjl38($8qE<Bk!4s1~Ne;C0@^5 zl?+t2shSq0MD2cCMQ{8WbQU$8!Gg@i22(Pbum;)Q(#bwzC-&=tB=mqfwcRzX+cJs2 z>y!@X6$)jPc#fq4ITTVe=a2P<O}pu{bIOlqHC;QTHzH~tYH^Tu?W%LE=)Ay;#c$5z z<Ectk)AmUGa;`dCDvnVp;}O}Oo*Xs!NrPy6OB86IyAK??of-=p^J%IADQJUw!?$4G zI}q-hU`{K5b7m1hzz)qna3Fq<SNq+Kh*Hv)oBM>qyIyl9=4bpM52}wEXsl3PdJjw# zY9_AAs1eZHqVK8*-hNtqinLvFVYL$hpIQnkF=y(Vcq#i?PlMz#Z#He!a~cr03y`P< z#IC3IC9u>}l&6Xl`x`*xwq_Ua1&5E4T(cmxruEWFliGjoIxlUd-kf!4E7|D^hk!=< zJYi+0CeYkC+MK#^5m=TIcsxlVo)o0dShH;hMogPy8qhFGBSh~RT^pIkNhL7>E#>A2 zogZ|m0#+x|E;)!xs(+ahwZi49)8L#y)E2L;zfa{D$P?0=+CmsAk!QpmY{OA$;m~OS z{etSKrK8<w$!elPIw!e~+$?e$4}Paw1O&DoblH%I(wM}n>VD@x-;Y;T0Bw=TO=XV8 z>p|ugJqKH%ijGsDu$x?xTVls1#T9EbOxfmpDP_aJuKX#vQze#e6|ST&2Wr%13+E^S zNkRzT1Jx<3R@)AznU>P>P*@hAv4R4d<)qCfW5bX@b9w*$3Hq*%f*5F0&H8Mgc6Hpg zmNwgT!DXWxC!v0(HarB&grOprUz&XXL9_o_c>RY!u~b>ir`hRds`(3yUsz})c{6X= z=ah*_H!?be@T+n$!Do@wE+5X5&5O3j6lmCWgK`rqqrdlPf}{E*bXD|em(O=vYvuV; zNbzI9Nq-eTr{fa&7R7No>Yzz4Z}d@N1$cRfFL8&E$nq)FN93d-$2(5-LD!$kKzUY- zn|5TF^!n)@q!q{DG*EqZ&^Giu{}dstDf4U0kLexsfse67dH8*Hj}$n(pUC`mzulHH z{d7Gcjn37fx;Z3y7WgUOBd>IKRQp80%P7oMluq~~tn5eLtc1xR>FY*aY#=_4jel4O zgCDCJg-cQwgh95VF!UnH$N=yPk=v}r7zUGY<#fr(L9m+xyT2tL+}BRRonNu4ban;W zy>xR+V)a|Ib=O~Zg^`D~66QFFmffKgFTx_<-jRuFxeN(<0YZ9V03p3xe=|lLY%Pop zo&E_Oa#p;QT;C^@plL8rVK099{``|3&~yyvU1Ehu>U<K6J0b35M(Yq*5U4jvH8Q&2 zWN>#;${Cl0cWKU!GC4P|0gI4x`Wm3yy3e1`u-&cp>ypGMLr!sAAeWI5p}j@L)ht~D zrIo&B)~+EDcH@C-SKDYTvQKGBaZPj^N(%p4nmEkHK#0~<bZ;(6J$78Z3|d#c^5b3+ z&!MKh&!r6b5@ge8sT53X=-@1^esgbX<~6%07#tX7QFYTJI&rIUxQtsRe3*7P6>~_s zD1E<1nuxpr9*uMv9Tbg26`~tfy4T5nvk=NfK@`H{w-RXJD>)x^3x$qbU9}YMbY*g^ zLnU?BI*$vz*;EXtuCj4~rP_%bS+Hi#fXC=NVhPvR>-#avjw2w;6+*LalS7%o^o$=1 zQ~p}Ncq${!Ix%wUls6!ILI@g6sR7v$7p54k1h^mq*$Zl%Q7dNqTJxtpIIXwPtnQ)Y zhxBZb@vuXS59w(l)KH}luH<Dm@1(e)r#X0kd#BHUxsUTd-|2hZy+19X-cgfg#g9w& ze?keKUaGKeYF@CI7g=K!M3x^2LXSm5SEQFe@=3>=jUz!On-$!URP%?y?+HO7H%BNF z7|_UM{x$tJnc3Fi+tCHw18kK-03StUg_5TcIQhW}HCKedcZ`Q@8p>$pG4@mQ_^^2H ziYeZP^g3d=CznH_;<;l4mk^aYi|jyUX6=_Ag&dgGMlf7%GtH085c&i&oycoqgqYyk zXJ6;A#UfnV*p-OFkw36v8yi5|dXKh><<2ZT#W;z|gm^S_#`?QA*Ejp9ds0w3+DYrN z8`IT-N~zMo-7BlRjpm2nbSIh!gDK|%iF_y&%f%UxA67&0+Xa@it~T?juNuN<;S@Nv zaI0#XsfDYWb?i60oq#i)OUt)G;CLQpEnC&jr4#i-nTzjstcBpb*-{w)5H^*+Q;(HK zg`DL0ME@yU#S}`CYTvN#qcJMAW55_SV;A&1=oyJ!ao2U@7q;%aG<qjmut}PH?lLpL zPr7_f=yE>G6V11G?6UB0{b~UHBp|?2`2W<^|HbDI2>AHlT>g9S8T=t3ApsBqfa{Nf z0k}1AHn%dObuczGHn(&7vnqfTE!EV-^e^g38A;lD)){6NAV53{1SDukx52+3NL~u~ z0}2q}w?AP6Oz-~+fN}0!kr7cApp}pnrGH;dKJzZ|w{S2O!1WvSAB7Td`~Oyx5s;M- z6;V>AlM#K7@LP?4Hw*|_{8LE>-2Wz0@V{yR*oXd9y8cz;U$O@Ot0MBBssRBV{k7u1 zBp3dpWg-q4YBqplLJ$4B<l7$@IDn!1CqadO0|?sMIynM#66|F3&20dArhlj-yni-T zypq8J0Ny;{BK`vxaF+#0XZjmxVE}K0m94(fzc~)y11>rkb-@EV_7k8}0q_4$#SgGQ z^S=NA9}YKn0cR&O01LIb;UC;7?^`&A+P7)~F#E>f0s#^J2_Fb(2Vg<}qlMqSwfAuD ze$x4Q0GKhr^&3U@A7uex?EeD}@VurD#*U8C0Ihdpn}5qsyoaasDD3Y5bY&Rq@0k#P zzz<>mEj)mL+sfGyz$7DTZe=WBXb5OQM&Cx?^uINbbvp{`0qF2xK!^XP2lz*sCHUJ0 z#2oYi+Nml4o=S0BYh!6!TT5rVzwa8d?P0VBfX#IPIsy<u_NS5xu$8~*n%|+-0EP$9 zY5mRi{vP*n2V=h+fVK*_i2j5M1hh=|x407jwz|K^<6b4_@da2HJHmUse)_-1vvG9N zx3c<gHvG%@#)=Lwtr$@Q0TKM=`TPM1et+|S1NeV_!~6TEyXfY+wFSH&7QhI`{|nlg z)Zd~>+nWB@w;gEC4^$5r^r`?KjN>n0>9T(dCJ#_<5pZ-gwl)Ch<&sF-8tPjK0}R%| z+`#z{miPBY`(Et+kB0K)|G!)L`)+uz^7{woi`w5}zV|);qWSy&iQlVY{((-d{kQ1< zGSa`%$b1j|UX<_;Xb8Rk1^riv!uP1}Rd@bC^)mlQ8a(d-e**wm+5eT_bawtIs{p`1 z8SQV8pYJQbSKaxeGPK2iRQ|W{$$xhS-^0IGQuzZu$?Ctt|C5Ep`-a}@9sJRdy8VAN z^rz?lFX{*H;olSY{{esI@W0^S`O5EM-}BY~0W0hDzhM8o3Gp8DJ!kPBnAslx3-kB1 zjQ=(>zGnyi12x?9AE5qsuHgTtk~2n8Ac%tKBpzaqu&H<ejorY^&?{`Zk!T>ekst^n z8Y#wNCPo7yW{a0GwZ~Dbd9B@ljip}u8M@mVs<?cXn+jCxT0!M_?p3D*ut-vX&%OZ= zsg)#&jdCLW4h$qyzZ{wD0~518G%#cBU_`M8MX2q_fQq!Dki^ajkv3xkseBw7FB>R` zVy0iH{ltuN`^&dq0!RoW(t@0)W=IgDB85?0QT}FTiXY4+fLTWmu=pn+H8F<Fgv#9& z9%r1lz27&r<gA!BlK#iipdZg3ac&G}2-aSwVXrS)d=D<o`YzDCjU#+3&UMlnmu4?d zYsStf8j4b09YSThy~tlclS`bRW5D6H(|e2($2g4_VZKh!E8ROA=CS67>EfFvh3TTt b+=;!jU|P+J`>$CfFsoU|bwOU-ceCsYH7qU$ literal 0 HcmV?d00001 diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties new file mode 100644 index 000000000..e0b3fb8d7 --- /dev/null +++ b/gradle/wrapper/gradle-wrapper.properties @@ -0,0 +1,5 @@ +distributionBase=GRADLE_USER_HOME +distributionPath=wrapper/dists +distributionUrl=https\://services.gradle.org/distributions/gradle-4.10.2-bin.zip +zipStoreBase=GRADLE_USER_HOME +zipStorePath=wrapper/dists diff --git a/gradlew b/gradlew new file mode 100755 index 000000000..cccdd3d51 --- /dev/null +++ b/gradlew @@ -0,0 +1,172 @@ +#!/usr/bin/env sh + +############################################################################## +## +## Gradle start up script for UN*X +## +############################################################################## + +# Attempt to set APP_HOME +# Resolve links: $0 may be a link +PRG="$0" +# Need this for relative symlinks. +while [ -h "$PRG" ] ; do + ls=`ls -ld "$PRG"` + link=`expr "$ls" : '.*-> \(.*\)$'` + if expr "$link" : '/.*' > /dev/null; then + PRG="$link" + else + PRG=`dirname "$PRG"`"/$link" + fi +done +SAVED="`pwd`" +cd "`dirname \"$PRG\"`/" >/dev/null +APP_HOME="`pwd -P`" +cd "$SAVED" >/dev/null + +APP_NAME="Gradle" +APP_BASE_NAME=`basename "$0"` + +# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +DEFAULT_JVM_OPTS="" + +# Use the maximum available, or set MAX_FD != -1 to use that value. +MAX_FD="maximum" + +warn () { + echo "$*" +} + +die () { + echo + echo "$*" + echo + exit 1 +} + +# OS specific support (must be 'true' or 'false'). +cygwin=false +msys=false +darwin=false +nonstop=false +case "`uname`" in + CYGWIN* ) + cygwin=true + ;; + Darwin* ) + darwin=true + ;; + MINGW* ) + msys=true + ;; + NONSTOP* ) + nonstop=true + ;; +esac + +CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar + +# Determine the Java command to use to start the JVM. +if [ -n "$JAVA_HOME" ] ; then + if [ -x "$JAVA_HOME/jre/sh/java" ] ; then + # IBM's JDK on AIX uses strange locations for the executables + JAVACMD="$JAVA_HOME/jre/sh/java" + else + JAVACMD="$JAVA_HOME/bin/java" + fi + if [ ! -x "$JAVACMD" ] ; then + die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." + fi +else + JAVACMD="java" + which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." +fi + +# Increase the maximum file descriptors if we can. +if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then + MAX_FD_LIMIT=`ulimit -H -n` + if [ $? -eq 0 ] ; then + if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then + MAX_FD="$MAX_FD_LIMIT" + fi + ulimit -n $MAX_FD + if [ $? -ne 0 ] ; then + warn "Could not set maximum file descriptor limit: $MAX_FD" + fi + else + warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" + fi +fi + +# For Darwin, add options to specify how the application appears in the dock +if $darwin; then + GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" +fi + +# For Cygwin, switch paths to Windows format before running java +if $cygwin ; then + APP_HOME=`cygpath --path --mixed "$APP_HOME"` + CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` + JAVACMD=`cygpath --unix "$JAVACMD"` + + # We build the pattern for arguments to be converted via cygpath + ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` + SEP="" + for dir in $ROOTDIRSRAW ; do + ROOTDIRS="$ROOTDIRS$SEP$dir" + SEP="|" + done + OURCYGPATTERN="(^($ROOTDIRS))" + # Add a user-defined pattern to the cygpath arguments + if [ "$GRADLE_CYGPATTERN" != "" ] ; then + OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" + fi + # Now convert the arguments - kludge to limit ourselves to /bin/sh + i=0 + for arg in "$@" ; do + CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` + CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option + + if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition + eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` + else + eval `echo args$i`="\"$arg\"" + fi + i=$((i+1)) + done + case $i in + (0) set -- ;; + (1) set -- "$args0" ;; + (2) set -- "$args0" "$args1" ;; + (3) set -- "$args0" "$args1" "$args2" ;; + (4) set -- "$args0" "$args1" "$args2" "$args3" ;; + (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; + (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; + (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; + (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; + (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; + esac +fi + +# Escape application args +save () { + for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done + echo " " +} +APP_ARGS=$(save "$@") + +# Collect all arguments for the java command, following the shell quoting and substitution rules +eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS" + +# by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong +if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then + cd "$(dirname "$0")" +fi + +exec "$JAVACMD" "$@" diff --git a/gradlew.bat b/gradlew.bat new file mode 100644 index 000000000..e95643d6a --- /dev/null +++ b/gradlew.bat @@ -0,0 +1,84 @@ +@if "%DEBUG%" == "" @echo off +@rem ########################################################################## +@rem +@rem Gradle startup script for Windows +@rem +@rem ########################################################################## + +@rem Set local scope for the variables with windows NT shell +if "%OS%"=="Windows_NT" setlocal + +set DIRNAME=%~dp0 +if "%DIRNAME%" == "" set DIRNAME=. +set APP_BASE_NAME=%~n0 +set APP_HOME=%DIRNAME% + +@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +set DEFAULT_JVM_OPTS= + +@rem Find java.exe +if defined JAVA_HOME goto findJavaFromJavaHome + +set JAVA_EXE=java.exe +%JAVA_EXE% -version >NUL 2>&1 +if "%ERRORLEVEL%" == "0" goto init + +echo. +echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:findJavaFromJavaHome +set JAVA_HOME=%JAVA_HOME:"=% +set JAVA_EXE=%JAVA_HOME%/bin/java.exe + +if exist "%JAVA_EXE%" goto init + +echo. +echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:init +@rem Get command-line arguments, handling Windows variants + +if not "%OS%" == "Windows_NT" goto win9xME_args + +:win9xME_args +@rem Slurp the command line arguments. +set CMD_LINE_ARGS= +set _SKIP=2 + +:win9xME_args_slurp +if "x%~1" == "x" goto execute + +set CMD_LINE_ARGS=%* + +:execute +@rem Setup the command line + +set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar + +@rem Execute Gradle +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% + +:end +@rem End local scope for the variables with windows NT shell +if "%ERRORLEVEL%"=="0" goto mainEnd + +:fail +rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of +rem the _cmd.exe /c_ return code! +if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 +exit /b 1 + +:mainEnd +if "%OS%"=="Windows_NT" endlocal + +:omega diff --git a/settings.gradle b/settings.gradle new file mode 100644 index 000000000..6aa6a0456 --- /dev/null +++ b/settings.gradle @@ -0,0 +1,3 @@ +rootProject.name = 'nshmp-haz-ws' +include ':nshmp-haz' +project(':nshmp-haz').projectDir = new File(settingsDir, '../nshmp-haz') diff --git a/src/gov/usgs/earthquake/nshmp/aws/HazardResultSliceLambda.java b/src/gov/usgs/earthquake/nshmp/aws/HazardResultSliceLambda.java new file mode 100644 index 000000000..8609fe782 --- /dev/null +++ b/src/gov/usgs/earthquake/nshmp/aws/HazardResultSliceLambda.java @@ -0,0 +1,311 @@ +package gov.usgs.earthquake.nshmp.aws; + +import static com.google.common.base.Preconditions.checkState; +import static gov.usgs.earthquake.nshmp.aws.Util.CURVES_FILE; +import static gov.usgs.earthquake.nshmp.aws.Util.MAP_FILE; +import static gov.usgs.earthquake.nshmp.www.ServletUtil.GSON; + +import java.io.BufferedReader; +import java.io.ByteArrayInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.io.OutputStream; +import java.time.ZonedDateTime; +import java.util.ArrayList; +import java.util.List; +import java.util.Optional; +import java.util.function.Function; +import java.util.stream.Collectors; + +import com.amazonaws.services.lambda.runtime.Context; +import com.amazonaws.services.lambda.runtime.RequestStreamHandler; +import com.amazonaws.services.s3.AmazonS3; +import com.amazonaws.services.s3.AmazonS3ClientBuilder; +import com.amazonaws.services.s3.model.ObjectMetadata; +import com.amazonaws.services.s3.model.PutObjectRequest; +import com.amazonaws.services.s3.model.S3Object; +import com.amazonaws.services.s3.model.S3ObjectInputStream; +import com.google.common.base.Charsets; +import com.google.common.base.Throwables; +import com.google.common.collect.Iterables; +import com.google.common.collect.Lists; + +import gov.usgs.earthquake.nshmp.aws.Util.LambdaHelper; +import gov.usgs.earthquake.nshmp.calc.Site; +import gov.usgs.earthquake.nshmp.data.Interpolator; +import gov.usgs.earthquake.nshmp.internal.Parsing; +import gov.usgs.earthquake.nshmp.internal.Parsing.Delimiter; +import gov.usgs.earthquake.nshmp.www.ServletUtil; +import gov.usgs.earthquake.nshmp.www.meta.Metadata; +import gov.usgs.earthquake.nshmp.www.meta.Status; + +/** + * AWS Lambda function to read in a curves file from AWS S3 and create slices at + * return periods interest. <br> + * + * The results are written to S3 as map.csv bucket. + */ +@SuppressWarnings("unused") +public class HazardResultSliceLambda implements RequestStreamHandler { + + private static final AmazonS3 S3 = AmazonS3ClientBuilder.defaultClient(); + + private static final String RATE_FMT = "%.8e"; + private static final Function<Double, String> FORMATTER = Parsing.formatDoubleFunction(RATE_FMT); + + private static final int NUMBER_OF_HEADERS = 3; + private static final String CONTENT_TYPE = "text/csv"; + + private static final Interpolator INTERPOLATOR = Interpolator.builder() + .logx() + .logy() + .decreasingX() + .build(); + + @Override + public void handleRequest( + InputStream input, + OutputStream output, + Context context) throws IOException { + LambdaHelper lambdaHelper = new LambdaHelper(input, output, context); + String requestBucket = ""; + + try { + RequestData request = GSON.fromJson(lambdaHelper.requestJson, RequestData.class); + lambdaHelper.logger.log("Request Data: " + GSON.toJson(request) + "\n"); + requestBucket = request.bucket + "/" + request.key; + checkRequest(request); + Response response = processRequest(request); + String json = GSON.toJson(response, Response.class); + lambdaHelper.logger.log("Result: " + json + "\n"); + output.write(json.getBytes()); + output.close(); + } catch (Exception e) { + lambdaHelper.logger.log("\nError: " + Throwables.getStackTraceAsString(e) + "\n\n"); + String message = Metadata.errorMessage(requestBucket, e, false); + output.write(message.getBytes()); + } + } + + private static Response processRequest(RequestData request) throws IOException { + List<InterpolatedData> data = readCurveFile(request); + String outputBucket = request.bucket + "/" + request.key; + StringBuilder csv = new StringBuilder(); + createHeaderString(csv, request); + createDataString(csv, data); + writeResults(request, outputBucket, csv.toString().getBytes(Charsets.UTF_8)); + return new Response(request, outputBucket); + } + + private static List<InterpolatedData> readCurveFile(RequestData request) throws IOException { + S3Object object = S3.getObject(request.bucket, request.key + "/" + CURVES_FILE); + S3ObjectInputStream input = object.getObjectContent(); + BufferedReader reader = new BufferedReader(new InputStreamReader(input)); + List<String> lines = reader.lines().collect(Collectors.toList()); + reader.close(); + + Optional<List<String>> header = lines.stream() + .filter(line -> !line.startsWith("#")) + .findFirst() + .map(line -> Parsing.splitToList(line, Delimiter.COMMA)); + + checkState(header.isPresent(), "Curve file is empty"); + + List<String> keys = header.get().subList(0, NUMBER_OF_HEADERS); + List<Double> imls = header.get().subList(NUMBER_OF_HEADERS, header.get().size()) + .stream() + .map(iml -> Double.parseDouble(iml)) + .collect(Collectors.toList()); + + List<InterpolatedData> data = new ArrayList<>(); + lines.stream() + .filter(line -> !line.startsWith("#")) + .skip(1) + .forEach(line -> { + data.add(curveToInterpolatedData(request, line, keys, imls)); + }); + + return data; + } + + private static InterpolatedData curveToInterpolatedData( + RequestData request, + String line, + List<String> keys, + List<Double> imls) { + List<String> values = Parsing.splitToList(line, Delimiter.COMMA); + List<Double> gms = values.subList(NUMBER_OF_HEADERS, values.size()) + .stream() + .map(gm -> Double.parseDouble(gm)) + .collect(Collectors.toList()); + values = values.subList(0, NUMBER_OF_HEADERS); + + Site site = buildSite(keys, values); + List<Double> interpolatedValues = request.slices.stream() + .map(returnPeriod -> INTERPOLATOR.findX(imls, gms, returnPeriod)) + .collect(Collectors.toList()); + + return new InterpolatedData(site, interpolatedValues); + } + + private static Site buildSite(List<String> keys, List<String> values) { + Double lat = null; + Double lon = null; + String name = null; + + for (int index = 0; index < keys.size(); index++) { + String key = keys.get(index); + String value = values.get(index); + + switch (key) { + case Keys.LAT: + lat = Double.parseDouble(value); + break; + case Keys.LON: + lon = Double.parseDouble(value); + break; + case Keys.NAME: + name = value; + break; + default: + throw new IllegalStateException("Unsupported site key: " + key); + } + } + + return Site.builder() + .location(lat, lon) + .name(name) + .build(); + } + + private static void checkRequest(RequestData request) { + if (request.bucket == null) { + throw new RuntimeException("Request does not contain a S3 bucket"); + } + + if (request.key == null) { + throw new RuntimeException("Request does not contain a S3 key"); + } + + if (request.slices == null) { + throw new RuntimeException("Request does not contain returnPeriods"); + } + } + + private static void createDataString(StringBuilder builder, List<InterpolatedData> data) { + data.forEach(datum -> { + List<String> locData = Lists.newArrayList( + datum.site.name, + String.format("%.5f", datum.site.location.lon()), + String.format("%.5f", datum.site.location.lat())); + builder.append(toLine(locData, datum.values) + "\n"); + }); + } + + private static String toLine( + Iterable<String> strings, + Iterable<Double> values) { + return Parsing.join( + Iterables.concat(strings, Iterables.transform(values, FORMATTER::apply)), + Delimiter.COMMA); + } + + private static void createHeaderString(StringBuilder builder, RequestData request) { + List<String> header = Lists.newArrayList(Keys.NAME, Keys.LON, Keys.LAT); + builder.append(toLine(header, request.slices) + "\n"); + } + + private static void writeResults( + RequestData request, + String outputBucket, + byte[] result) throws IOException { + ObjectMetadata metadata = new ObjectMetadata(); + + InputStream input = new ByteArrayInputStream(result); + metadata.setContentType(CONTENT_TYPE); + metadata.setContentLength(result.length); + PutObjectRequest putRequest = new PutObjectRequest( + request.bucket, + request.key + "/" + MAP_FILE, + input, + metadata); + S3.putObject(putRequest); + input.close(); + } + + static class RequestData { + String bucket; + String key; + List<Double> slices; + + private RequestData(Builder builder) { + bucket = builder.bucket; + key = builder.key; + slices = builder.slices; + } + + static Builder builder() { + return new Builder(); + } + + static class Builder { + private String bucket; + private String key; + private List<Double> slices; + + Builder bucket(String bucket) { + this.bucket = bucket; + return this; + } + + Builder key(String key) { + this.key = key; + return this; + } + + Builder slices(List<Double> slices) { + this.slices = slices; + return this; + } + + RequestData build() { + return new RequestData(this); + } + + } + + } + + private static class Response { + final String status; + final String date; + final RequestData request; + final String csv; + + Response(RequestData request, String outputBucket) { + status = Status.SUCCESS.toString(); + date = ZonedDateTime.now().format(ServletUtil.DATE_FMT); + this.request = request; + this.csv = outputBucket + "/" + MAP_FILE; + } + + } + + private static class InterpolatedData { + Site site; + List<Double> values; + + InterpolatedData(Site site, List<Double> values) { + this.site = site; + this.values = values; + } + } + + private static class Keys { + static final String LAT = "lat"; + static final String LON = "lon"; + static final String NAME = "name"; + } + +} diff --git a/src/gov/usgs/earthquake/nshmp/aws/HazardResultsMetadataLambda.java b/src/gov/usgs/earthquake/nshmp/aws/HazardResultsMetadataLambda.java new file mode 100644 index 000000000..1d1183e4c --- /dev/null +++ b/src/gov/usgs/earthquake/nshmp/aws/HazardResultsMetadataLambda.java @@ -0,0 +1,314 @@ +package gov.usgs.earthquake.nshmp.aws; + +import static gov.usgs.earthquake.nshmp.aws.Util.CURVES_FILE; +import static gov.usgs.earthquake.nshmp.aws.Util.MAP_FILE; +import static gov.usgs.earthquake.nshmp.www.ServletUtil.GSON; + +import java.io.ByteArrayInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.time.ZonedDateTime; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.TreeSet; +import java.util.stream.Collectors; + +import com.amazonaws.services.lambda.runtime.Context; +import com.amazonaws.services.lambda.runtime.RequestStreamHandler; +import com.amazonaws.services.s3.AmazonS3; +import com.amazonaws.services.s3.AmazonS3ClientBuilder; +import com.amazonaws.services.s3.model.ListObjectsV2Request; +import com.amazonaws.services.s3.model.ListObjectsV2Result; +import com.amazonaws.services.s3.model.ObjectMetadata; +import com.amazonaws.services.s3.model.PutObjectRequest; +import com.google.common.base.Enums; +import com.google.common.base.Throwables; + +import gov.usgs.earthquake.nshmp.aws.Util.LambdaHelper; +import gov.usgs.earthquake.nshmp.calc.DataType; +import gov.usgs.earthquake.nshmp.eq.model.SourceType; +import gov.usgs.earthquake.nshmp.gmm.Gmm; +import gov.usgs.earthquake.nshmp.gmm.Imt; +import gov.usgs.earthquake.nshmp.internal.Parsing; +import gov.usgs.earthquake.nshmp.internal.Parsing.Delimiter; +import gov.usgs.earthquake.nshmp.www.ServletUtil; +import gov.usgs.earthquake.nshmp.www.meta.Metadata; +import gov.usgs.earthquake.nshmp.www.meta.Status; + +/** + * AWS Lambda function to list all hazard results in the nshmp-hazout S3 bucket + * that contain a map.csv file. + */ +@SuppressWarnings("unused") +public class HazardResultsMetadataLambda implements RequestStreamHandler { + + private static final AmazonS3 S3 = AmazonS3ClientBuilder.defaultClient(); + + private static final int IMT_DIR_BACK_FROM_TOTAL = 2; + private static final int IMT_DIR_BACK_FROM_SOURCE = 4; + private static final String S3_BUCKET = "nshmp-hazout"; + private static final String RESULT_BUCKET = "nshmp-haz-lambda"; + private static final String RESULT_KEY = "nshmp-haz-aws-results-metadata.json"; + + @Override + public void handleRequest( + InputStream input, + OutputStream output, + Context context) throws IOException { + LambdaHelper lambdaHelper = new LambdaHelper(input, output, context); + + try { + Response response = processRequest(); + String json = GSON.toJson(response, Response.class); + uploadResults(json); + output.write(json.getBytes()); + output.close(); + } catch (Exception e) { + lambdaHelper.logger.log("\nError: " + Throwables.getStackTraceAsString(e) + "\n\n"); + String message = Metadata.errorMessage("", e, false); + output.write(message.getBytes()); + } + } + + private static Response processRequest() { + Map<String, CurvesMapResult> curvesMapResults = new HashMap<>(); + Set<String> users = getUsers(); + + for (String file : new String[] { CURVES_FILE, MAP_FILE }) { + List<HazardResults> hazardResults = listObjects(users, file); + CurvesMapResult result = new CurvesMapResult(users, hazardResults); + curvesMapResults.put(file, result); + } + + Result result = new Result(curvesMapResults.get(CURVES_FILE), curvesMapResults.get(MAP_FILE)); + return new Response(result); + } + + private static List<HazardResults> listObjects(Set<String> users, String file) { + ListObjectsV2Request request = new ListObjectsV2Request() + .withBucketName(S3_BUCKET) + .withDelimiter(file); + ListObjectsV2Result s3Result; + List<S3Listing> s3Listings = new ArrayList<>(); + + do { + s3Result = S3.listObjectsV2(request); + s3Result.getCommonPrefixes() + .stream() + .map(key -> keyToHazardListing(key)) + .forEach(listing -> s3Listings.add(listing)); + + request.setContinuationToken(s3Result.getNextContinuationToken()); + } while (s3Result.isTruncated()); + + return transformS3Listing(users, s3Listings); + } + + private static List<HazardResults> transformS3Listing(Set<String> users, List<S3Listing> s3Listings) { + List<HazardResults> hazardResults = new ArrayList<>(); + + users.forEach(user -> { + TreeSet<String> resultDirectories = s3Listings.stream() + .filter(listing -> listing.user.equals(user)) + .map(listing -> listing.resultPrefix) + .collect(Collectors.toCollection(TreeSet::new)); + + resultDirectories.forEach(resultPrefix -> { + List<S3Listing> s3Filteredlistings = s3Listings.parallelStream() + .filter(listing -> listing.user.equals(user)) + .filter(listing -> listing.resultPrefix.equals(resultPrefix)) + .collect(Collectors.toList()); + + List<HazardListing> listings = s3Filteredlistings.parallelStream() + .map(listing -> s3ListingToHazardListing(listing)) + .collect(Collectors.toList()); + + S3Listing s3Listing = s3Filteredlistings.get(0); + String path = s3Listing.path.split(resultPrefix)[0]; + String s3Path = s3Listing.user + "/" + path + resultPrefix; + + hazardResults.add(new HazardResults( + user, + s3Listing.bucket, + resultPrefix, + s3Path, + listings)); + }); + }); + + return hazardResults; + } + + private static HazardListing s3ListingToHazardListing(S3Listing s3Listing) { + return new HazardListing(s3Listing.dataType, s3Listing.path, s3Listing.file); + } + + private static S3Listing keyToHazardListing(String key) { + List<String> keys = Parsing.splitToList(key, Delimiter.SLASH); + HazardDataType<?> dataType = getDataType(keys); + String user = keys.get(0); + String file = keys.get(keys.size() - 1); + String path = keys.subList(1, keys.size() - 1) + .stream() + .collect(Collectors.joining("/")); + + return new S3Listing(user, S3_BUCKET, path, file, dataType); + } + + private static Set<String> getUsers() { + ListObjectsV2Request request = new ListObjectsV2Request() + .withBucketName(S3_BUCKET) + .withDelimiter("/"); + + ListObjectsV2Result listing = S3.listObjectsV2(request); + + return listing.getCommonPrefixes().stream() + .map(prefix -> prefix.replace("/", "")) + .collect(Collectors.toCollection(TreeSet::new)); + } + + private static HazardDataType<?> getDataType(List<String> keys) { + String sourceType = keys.get(keys.size() - IMT_DIR_BACK_FROM_TOTAL); + HazardDataType<?> dataType = null; + String resultDirectory = null; + Imt imt = null; + + if (Enums.getIfPresent(SourceType.class, sourceType).isPresent()) { + imt = Imt.valueOf(keys.get(keys.size() - IMT_DIR_BACK_FROM_SOURCE)); + resultDirectory = keys.get(keys.size() - IMT_DIR_BACK_FROM_SOURCE - 1); + SourceType type = SourceType.valueOf(sourceType); + dataType = new HazardDataType<SourceType>(imt, DataType.SOURCE, type, resultDirectory); + } else if (Enums.getIfPresent(Gmm.class, sourceType).isPresent()) { + imt = Imt.valueOf(keys.get(keys.size() - IMT_DIR_BACK_FROM_SOURCE)); + resultDirectory = keys.get(keys.size() - IMT_DIR_BACK_FROM_SOURCE - 1); + Gmm type = Gmm.valueOf(sourceType); + dataType = new HazardDataType<Gmm>(imt, DataType.GMM, type, resultDirectory); + } else if (Enums.getIfPresent(Imt.class, sourceType).isPresent()) { + Imt type = Imt.valueOf(sourceType); + resultDirectory = keys.get(keys.size() - IMT_DIR_BACK_FROM_TOTAL - 1); + imt = type; + dataType = new HazardDataType<Imt>(imt, DataType.TOTAL, type, resultDirectory); + } else { + throw new RuntimeException("Source type [" + sourceType + "] not supported"); + } + + return dataType; + } + + private static void uploadResults(String results) { + byte[] bytes = results.getBytes(); + ByteArrayInputStream input = new ByteArrayInputStream(bytes); + ObjectMetadata metadata = new ObjectMetadata(); + metadata.setContentLength(bytes.length); + metadata.setContentType("application/json"); + + PutObjectRequest request = new PutObjectRequest( + RESULT_BUCKET, + RESULT_KEY, + input, + metadata); + + S3.putObject(request); + } + + static class HazardDataType<E extends Enum<E>> { + final Imt imt; + final DataType type; + final transient String resultPrefix; + final E sourceType; + + HazardDataType(Imt imt, DataType type, E sourceType, String resultPrefix) { + this.imt = imt; + this.type = type; + this.resultPrefix = resultPrefix; + this.sourceType = sourceType; + } + } + + private static class HazardResults { + final String user; + final String bucket; + final String resultPrefix; + final String path; + final List<HazardListing> listings; + + HazardResults( + String user, + String bucket, + String resultPrefix, + String path, + List<HazardListing> listings) { + this.user = user; + this.bucket = bucket; + this.resultPrefix = resultPrefix; + this.path = path; + this.listings = listings; + } + } + + private static class HazardListing { + final HazardDataType<?> dataType; + final String file; + final String path; + + HazardListing(HazardDataType<?> dataType, String path, String file) { + this.dataType = dataType; + this.file = file; + this.path = path; + } + } + + private static class S3Listing { + final String user; + final String bucket; + final String path; + final String file; + final String resultPrefix; + final HazardDataType<?> dataType; + + S3Listing(String user, String bucket, String path, String file, HazardDataType<?> dataType) { + this.user = user; + this.bucket = bucket; + this.path = path; + this.file = file; + this.resultPrefix = dataType.resultPrefix; + this.dataType = dataType; + } + } + + private static class CurvesMapResult { + final Set<String> users; + final List<HazardResults> hazardResults; + + CurvesMapResult(Set<String> users, List<HazardResults> hazardResults) { + this.users = users; + this.hazardResults = hazardResults; + } + } + + private static class Result { + final CurvesMapResult curves; + final CurvesMapResult map; + + Result(CurvesMapResult curves, CurvesMapResult map) { + this.curves = curves; + this.map = map; + } + } + + private static class Response { + final String status; + final String date; + final Result result; + + Response(Result result) { + status = Status.SUCCESS.toString(); + date = ZonedDateTime.now().format(ServletUtil.DATE_FMT); + this.result = result; + } + } +} diff --git a/src/gov/usgs/earthquake/nshmp/aws/HazardResultsSlicerLambda.java b/src/gov/usgs/earthquake/nshmp/aws/HazardResultsSlicerLambda.java new file mode 100644 index 000000000..340dc58ea --- /dev/null +++ b/src/gov/usgs/earthquake/nshmp/aws/HazardResultsSlicerLambda.java @@ -0,0 +1,257 @@ +package gov.usgs.earthquake.nshmp.aws; + +import static gov.usgs.earthquake.nshmp.aws.Util.CURVES_FILE; +import static gov.usgs.earthquake.nshmp.www.ServletUtil.GSON; + +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.time.ZonedDateTime; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.concurrent.CompletableFuture; +import java.util.stream.Collectors; + +import com.amazonaws.services.ec2.AmazonEC2; +import com.amazonaws.services.ec2.AmazonEC2ClientBuilder; +import com.amazonaws.services.ec2.model.DescribeInstancesRequest; +import com.amazonaws.services.ec2.model.DescribeInstancesResult; +import com.amazonaws.services.ec2.model.Instance; +import com.amazonaws.services.ec2.model.Reservation; +import com.amazonaws.services.lambda.AWSLambda; +import com.amazonaws.services.lambda.AWSLambdaClientBuilder; +import com.amazonaws.services.lambda.model.InvokeRequest; +import com.amazonaws.services.lambda.model.InvokeResult; +import com.amazonaws.services.lambda.runtime.Context; +import com.amazonaws.services.lambda.runtime.RequestStreamHandler; +import com.amazonaws.services.s3.AmazonS3; +import com.amazonaws.services.s3.AmazonS3ClientBuilder; +import com.amazonaws.services.s3.model.ObjectListing; +import com.google.common.base.Throwables; + +import gov.usgs.earthquake.nshmp.aws.Util.LambdaHelper; +import gov.usgs.earthquake.nshmp.internal.Parsing; +import gov.usgs.earthquake.nshmp.internal.Parsing.Delimiter; +import gov.usgs.earthquake.nshmp.www.ServletUtil; +import gov.usgs.earthquake.nshmp.www.meta.Metadata; +import gov.usgs.earthquake.nshmp.www.meta.Status; + +/** + * AWS Lambda function to read in hazard results from S3 and to create slices of + * return periods of interest. + * + * @see HazardResultSliceLambda + */ +@SuppressWarnings("unused") +public class HazardResultsSlicerLambda implements RequestStreamHandler { + + private static final AmazonS3 S3 = AmazonS3ClientBuilder.defaultClient(); + private static final AmazonEC2 EC2 = AmazonEC2ClientBuilder.defaultClient(); + private static final AWSLambda LAMBDA_CLIENT = AWSLambdaClientBuilder.defaultClient(); + + private static final String LAMBDA_CALL = "nshmp-haz-result-slice"; + private static final String ZIP_LAMBDA_CALL = "nshmp-haz-zip-results"; + private static final String INSTANCE_STATUS = "terminated"; + + private static final int MAX_INSTANCE_CHECK = 100; + private static final int INSTANCE_CHECK_TIMEOUT = 10 * 1000; + + @Override + public void handleRequest( + InputStream input, + OutputStream output, + Context context) throws IOException { + LambdaHelper lambdaHelper = new LambdaHelper(input, output, context); + String requestBucket = ""; + + try { + RequestData request = GSON.fromJson(lambdaHelper.requestJson, RequestData.class); + requestBucket = String.format("%s/%s", request.bucket, request.key); + lambdaHelper.logger.log("Request Data: " + GSON.toJson(request) + "\n\n"); + checkRequest(request); + checkBucket(request); + Response response = processRequest(lambdaHelper, request); + output.write(GSON.toJson(response, Response.class).getBytes()); + } catch (Exception e) { + lambdaHelper.logger.log("\nError: " + Throwables.getStackTraceAsString(e) + "\n\n"); + String message = Metadata.errorMessage(requestBucket, e, false); + output.write(message.getBytes()); + } + } + + private static Response processRequest( + LambdaHelper lambdaHelper, + RequestData request) throws IOException, InterruptedException { + ObjectListing objectListing = S3.listObjects(request.bucket, request.key); + List<CompletableFuture<Void>> futures = new ArrayList<>(); + + objectListing.getObjectSummaries() + .parallelStream() + .filter(summary -> summary.getKey().endsWith(CURVES_FILE)) + .forEach(summary -> { + String name = summary.getKey(); + lambdaHelper.logger.log("Reading: " + name + "\n"); + try { + futures.add(processCurveFile(request, lambdaHelper, name)); + } catch (IOException e) { + throw new RuntimeException(e); + } + }); + + futures.forEach(CompletableFuture::join); + lambdaHelper.logger.log("Zipping results"); + zipResults(request); + return new Response(request); + } + + private static CompletableFuture<Void> processCurveFile( + RequestData request, + LambdaHelper lambdaHelper, + String curvesPath) throws IOException { + return readCurveFile(request, curvesPath) + .thenAcceptAsync(result -> { + checkLambdaResponse(result); + }); + } + + private static CompletableFuture<InvokeResult> readCurveFile( + RequestData request, + String curvesPath) throws IOException { + List<String> names = Arrays.stream(curvesPath.split("/")) + .collect(Collectors.toList()); + names.remove(names.size() - 1); + String key = Parsing.join(names, Delimiter.SLASH); + + HazardResultSliceLambda.RequestData lambdaRequest = HazardResultSliceLambda.RequestData + .builder() + .bucket(request.bucket) + .key(key) + .slices(request.slices) + .build(); + + InvokeRequest invokeRequest = new InvokeRequest() + .withFunctionName(LAMBDA_CALL) + .withPayload(GSON.toJson(lambdaRequest)); + + return CompletableFuture.supplyAsync(() -> { + return LAMBDA_CLIENT.invoke(invokeRequest); + }); + } + + private static void checkRequest(RequestData request) { + if (request.bucket == null) { + throw new RuntimeException("Request does not contain a S3 bucket"); + } + + if (request.key == null) { + throw new RuntimeException("Request does not contain a S3 key"); + } + + if (request.slices == null) { + throw new RuntimeException("Request does not contain slices"); + } + } + + private static void checkBucket(RequestData request) { + if (!S3.doesBucketExistV2(request.bucket)) { + throw new RuntimeException(String.format("S3 bucket [%s] does not exist", request.bucket)); + } + } + + private static void zipResults(RequestData request) throws InterruptedException { + InvokeRequest invokeRequest = new InvokeRequest() + .withFunctionName(ZIP_LAMBDA_CALL) + .withPayload(GSON.toJson(request)); + + InvokeResult result = LAMBDA_CLIENT.invoke(invokeRequest); + checkLambdaResponse(result); + + ZipResultsResponse response = GSON.fromJson( + new String(result.getPayload().array()), + ZipResultsResponse.class); + + waitForInstance(response); + } + + private static void waitForInstance(ZipResultsResponse response) throws InterruptedException { + for (int ii = 0; ii < MAX_INSTANCE_CHECK; ii++) { + DescribeInstancesRequest request = new DescribeInstancesRequest() + .withInstanceIds(response.result.instanceId); + + DescribeInstancesResult instances = EC2.describeInstances(request); + if (isTerminated(instances)) { + return; + } + + Thread.sleep(INSTANCE_CHECK_TIMEOUT); + } + } + + private static boolean isTerminated(DescribeInstancesResult instances) { + for (Reservation reservation : instances.getReservations()) { + for (Instance instance : reservation.getInstances()) { + if (INSTANCE_STATUS.equals(instance.getState().getName())) { + return true; + } + } + } + + return false; + } + + private static void checkLambdaResponse(InvokeResult result) { + try { + LambdaResponse response = GSON.fromJson( + new String(result.getPayload().array()), + LambdaResponse.class); + + if (Status.ERROR.toString().equals(response.status)) { + throw new RuntimeException(response.message); + } + } catch (Exception e) { + throw new RuntimeException(e); + } + } + + private static class LambdaResponse { + String status; + String message; + } + + private static class ZipResultsResponse extends LambdaResponse { + ZipResult result; + ZipRequest request; + + private static class ZipRequest { + String bucket; + String key; + } + + private static class ZipResult { + String path; + String instanceId; + } + } + + private static class RequestData { + String bucket; + String key; + List<Double> slices; + } + + private static class Response { + final String status; + final String date; + final RequestData request; + final String outputBucket; + + Response(RequestData request) { + status = Status.SUCCESS.toString(); + date = ZonedDateTime.now().format(ServletUtil.DATE_FMT); + this.request = request; + this.outputBucket = String.format("%s/%s", request.bucket, request.key); + } + } + +} diff --git a/src/gov/usgs/earthquake/nshmp/aws/Util.java b/src/gov/usgs/earthquake/nshmp/aws/Util.java new file mode 100644 index 000000000..07a2b6d78 --- /dev/null +++ b/src/gov/usgs/earthquake/nshmp/aws/Util.java @@ -0,0 +1,41 @@ +package gov.usgs.earthquake.nshmp.aws; + +import java.io.BufferedReader; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.io.OutputStream; +import java.io.UnsupportedEncodingException; + +import com.amazonaws.services.lambda.runtime.Context; +import com.amazonaws.services.lambda.runtime.LambdaLogger; +import com.google.gson.JsonObject; +import com.google.gson.JsonParser; + +public class Util { + + static final String CURVES_FILE = "curves.csv"; + static final String MAP_FILE = "map.csv"; + + /** + * Parse the Lambda function {@code InputStream} into an {@code JsonObject}. + */ + static class LambdaHelper { + JsonObject requestJson; + Context context; + LambdaLogger logger; + OutputStream output; + + LambdaHelper(InputStream input, OutputStream output, Context context) + throws UnsupportedEncodingException { + logger = context.getLogger(); + this.context = context; + this.output = output; + + BufferedReader reader = new BufferedReader(new InputStreamReader(input)); + JsonParser parser = new JsonParser(); + + requestJson = parser.parse(reader).getAsJsonObject(); + } + } + +} diff --git a/src/gov/usgs/earthquake/nshmp/www/DeaggEpsilonService.java b/src/gov/usgs/earthquake/nshmp/www/DeaggEpsilonService.java new file mode 100644 index 000000000..0d1096041 --- /dev/null +++ b/src/gov/usgs/earthquake/nshmp/www/DeaggEpsilonService.java @@ -0,0 +1,356 @@ +package gov.usgs.earthquake.nshmp.www; + +import static com.google.common.base.Preconditions.checkNotNull; +import static gov.usgs.earthquake.nshmp.www.ServletUtil.GSON; +import static gov.usgs.earthquake.nshmp.www.ServletUtil.MODEL_CACHE_CONTEXT_ID; +import static gov.usgs.earthquake.nshmp.www.ServletUtil.emptyRequest; +import static gov.usgs.earthquake.nshmp.www.Util.readBoolean; +import static gov.usgs.earthquake.nshmp.www.Util.readDouble; +import static gov.usgs.earthquake.nshmp.www.Util.Key.BASIN; +import static gov.usgs.earthquake.nshmp.www.Util.Key.LATITUDE; +import static gov.usgs.earthquake.nshmp.www.Util.Key.LONGITUDE; +import static gov.usgs.earthquake.nshmp.www.Util.Key.MODEL; +import static gov.usgs.earthquake.nshmp.www.Util.Key.VS30; + +import java.io.IOException; +import java.io.InputStream; +import java.net.URL; +import java.time.ZonedDateTime; +import java.util.Arrays; +import java.util.EnumMap; +import java.util.List; +import java.util.Map; +import java.util.Map.Entry; +import java.util.Properties; +import java.util.Set; + +import javax.servlet.ServletContext; +import javax.servlet.ServletException; +import javax.servlet.annotation.WebServlet; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +import com.google.common.cache.LoadingCache; +import com.google.common.collect.ImmutableList; + +import gov.usgs.earthquake.nshmp.calc.CalcConfig; +import gov.usgs.earthquake.nshmp.calc.Deaggregation; +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.eq.model.HazardModel; +import gov.usgs.earthquake.nshmp.geo.Location; +import gov.usgs.earthquake.nshmp.gmm.Imt; +import gov.usgs.earthquake.nshmp.www.ServletUtil.TimedTask; +import gov.usgs.earthquake.nshmp.www.ServletUtil.Timer; +import gov.usgs.earthquake.nshmp.www.meta.Metadata; +import gov.usgs.earthquake.nshmp.www.meta.Status; + +/** + * Hazard deaggregation service. + * + * @author Peter Powers + */ +@SuppressWarnings("unused") +@WebServlet( + name = "Epsilon Deaggregation Service (experimental)", + description = "USGS NSHMP Hazard Deaggregator", + urlPatterns = { "/deagg-epsilon" }) +public final class DeaggEpsilonService extends NshmpServlet { + + /* Developer notes: See HazardService. */ + + private LoadingCache<Model, HazardModel> modelCache; + private URL basinUrl; + + private static final String USAGE = SourceServices.GSON.toJson( + new SourceServices.ResponseData()); + + @Override + @SuppressWarnings("unchecked") + public void init() throws ServletException { + + ServletContext context = getServletConfig().getServletContext(); + Object modelCache = context.getAttribute(MODEL_CACHE_CONTEXT_ID); + this.modelCache = (LoadingCache<Model, HazardModel>) modelCache; + + try (InputStream config = + DeaggService2.class.getResourceAsStream("/config.properties")) { + + checkNotNull(config, "Missing config.properties"); + + Properties props = new Properties(); + props.load(config); + if (props.containsKey("basin_host")) { + /* + * TODO Site builder tests if service is working, which may be + * inefficient for single call services. + */ + URL url = new URL(props.getProperty("basin_host") + "/nshmp-site-ws/basin/local-data"); + this.basinUrl = url; + } + } catch (IOException | NullPointerException e) { + throw new ServletException(e); + } + } + + @Override + protected void doGet( + HttpServletRequest request, + HttpServletResponse response) + throws ServletException, IOException { + + UrlHelper urlHelper = urlHelper(request, response); + + if (emptyRequest(request)) { + urlHelper.writeResponse(USAGE); + return; + } + + try { + RequestData requestData = buildRequestData(request); + + /* Submit as task to job executor */ + Deagg2Task task = new Deagg2Task(urlHelper.url, getServletContext(), requestData); + Result result = ServletUtil.TASK_EXECUTOR.submit(task).get(); + GSON.toJson(result, response.getWriter()); + + } catch (Exception e) { + String message = Metadata.errorMessage(urlHelper.url, e, false); + response.getWriter().print(message); + getServletContext().log(urlHelper.url, e); + } + } + + /* Reduce query string key-value pairs. */ + static RequestData buildRequestData(HttpServletRequest request) { + + try { + + /* process query '?' request */ + List<Model> models = readModelsFromQuery(request); + double lon = readDouble(LONGITUDE, request); + double lat = readDouble(LATITUDE, request); + Map<Imt, Double> imtImls = readImtsFromQuery(request); + double vs30 = readDouble(VS30, request); + boolean basin = readBoolean(BASIN, request); + + return new RequestData( + models, + lon, + lat, + imtImls, + vs30, + basin); + + } catch (Exception e) { + throw new IllegalArgumentException("Error parsing request URL", e); + } + } + + private static List<Model> readModelsFromQuery(HttpServletRequest request) { + String[] ids = Util.readValues(MODEL, request); + return Arrays.stream(ids) + .map(Model::valueOf) + .distinct() + .collect(ImmutableList.toImmutableList()); + } + + /* Create map of IMT to deagg IML. */ + private static Map<Imt, Double> readImtsFromQuery(HttpServletRequest request) { + EnumMap<Imt, Double> imtImls = new EnumMap<>(Imt.class); + for (Entry<String, String[]> param : request.getParameterMap().entrySet()) { + if (isImtParam(param.getKey())) { + imtImls.put( + Imt.valueOf(param.getKey()), + Double.valueOf(param.getValue()[0])); + } + } + return imtImls; + } + + private static boolean isImtParam(String key) { + return key.equals("PGA") || key.startsWith("SA"); + } + + private class Deagg2Task extends TimedTask<Result> { + + RequestData data; + + Deagg2Task(String url, ServletContext context, RequestData data) { + super(url, context); + this.data = data; + } + + @Override + Result calc() throws Exception { + Deaggregation deagg = calcDeagg(data); + + return new Result.Builder() + .requestData(data) + .url(url) + .timer(timer) + .deagg(deagg) + .build(); + } + } + + /* + * Developer notes: + * + * We're opting here to fetch basin terms ourselves. If we were to set the + * basin provider in the config, which requires additions to config, the URL + * is tested every time a site is created for a servlet request. While this + * worked for maps it's not good here. + * + * Site has logic for parsing the basin service response, which perhaps it + * shouldn't. TODO is it worth decomposing data objects and services + */ + Deaggregation calcDeagg(RequestData data) { + Location loc = Location.create(data.latitude, data.longitude); + + Site site = Site.builder() + .location(Location.create(data.latitude, data.longitude)) + .basinDataProvider(data.basin ? this.basinUrl : null) + .vs30(data.vs30) + .build(); + + Hazard[] hazards = new Hazard[data.models.size()]; + for (int i = 0; i < data.models.size(); i++) { + HazardModel model = modelCache.getUnchecked(data.models.get(i)); + hazards[i] = process(model, site, data.imtImls.keySet()); + } + Hazard hazard = Hazard.merge(hazards); + return Deaggregation.atImls(hazard, data.imtImls, ServletUtil.CALC_EXECUTOR); + } + + private static Hazard process(HazardModel model, Site site, Set<Imt> imts) { + CalcConfig config = CalcConfig.Builder + .copyOf(model.config()) + .imts(imts) + .build(); + // System.out.println(config); + return HazardCalcs.hazard(model, config, site, ServletUtil.CALC_EXECUTOR); + } + + static final class RequestData { + + final List<Model> models; + final double latitude; + final double longitude; + final Map<Imt, Double> imtImls; + final double vs30; + final boolean basin; + + RequestData( + List<Model> models, + double longitude, + double latitude, + Map<Imt, Double> imtImls, + double vs30, + boolean basin) { + + this.models = models; + this.latitude = latitude; + this.longitude = longitude; + this.imtImls = imtImls; + this.vs30 = vs30; + this.basin = basin; + } + } + + private static final class ResponseData { + + final List<Model> models; + final double longitude; + final double latitude; + final String imt; + final double iml; + final double vs30; + final String rlabel = "Closest Distance, rRup (km)"; + final String mlabel = "Magnitude (Mw)"; + final String εlabel = "% Contribution to Hazard"; + final Object εbins; + + ResponseData(Deaggregation deagg, RequestData request, Imt imt) { + this.models = request.models; + this.longitude = request.longitude; + this.latitude = request.latitude; + this.imt = imt.toString(); + this.iml = request.imtImls.get(imt); + this.vs30 = request.vs30; + this.εbins = deagg.εBins(); + } + } + + private static final class Response { + + final ResponseData metadata; + final Object data; + + Response(ResponseData metadata, Object data) { + this.metadata = metadata; + this.data = data; + } + } + + private static final class Result { + + final String status = Status.SUCCESS.toString(); + final String date = ZonedDateTime.now().format(ServletUtil.DATE_FMT); + final String url; + final Object server; + final List<Response> response; + + Result(String url, Object server, List<Response> response) { + this.url = url; + this.server = server; + this.response = response; + } + + static final class Builder { + + String url; + Timer timer; + RequestData request; + Deaggregation deagg; + + Builder deagg(Deaggregation deagg) { + this.deagg = deagg; + return this; + } + + Builder url(String url) { + this.url = url; + return this; + } + + Builder timer(Timer timer) { + this.timer = timer; + return this; + } + + Builder requestData(RequestData request) { + this.request = request; + return this; + } + + Result build() { + ImmutableList.Builder<Response> responseListBuilder = ImmutableList.builder(); + + for (Imt imt : request.imtImls.keySet()) { + ResponseData responseData = new ResponseData(deagg, request, imt); + Object deaggs = deagg.toJsonCompact(imt); + Response response = new Response(responseData, deaggs); + responseListBuilder.add(response); + } + + List<Response> responseList = responseListBuilder.build(); + Object server = Metadata.serverData(ServletUtil.THREAD_COUNT, timer); + + return new Result(url, server, responseList); + } + } + } + +} diff --git a/src/gov/usgs/earthquake/nshmp/www/DeaggService.java b/src/gov/usgs/earthquake/nshmp/www/DeaggService.java new file mode 100644 index 000000000..18b3b8d88 --- /dev/null +++ b/src/gov/usgs/earthquake/nshmp/www/DeaggService.java @@ -0,0 +1,235 @@ +package gov.usgs.earthquake.nshmp.www; + +import static gov.usgs.earthquake.nshmp.www.ServletUtil.GSON; +import static gov.usgs.earthquake.nshmp.www.ServletUtil.emptyRequest; + +import java.io.IOException; +import java.time.ZonedDateTime; +import java.util.List; +import java.util.Optional; + +import javax.servlet.ServletContext; +import javax.servlet.ServletException; +import javax.servlet.annotation.WebServlet; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +import com.google.common.base.Joiner; +import com.google.common.collect.ImmutableList; + +import gov.usgs.earthquake.nshmp.calc.Deaggregation; +import gov.usgs.earthquake.nshmp.calc.Hazard; +import gov.usgs.earthquake.nshmp.calc.HazardCalcs; +import gov.usgs.earthquake.nshmp.calc.Vs30; +import gov.usgs.earthquake.nshmp.gmm.Imt; +import gov.usgs.earthquake.nshmp.internal.Parsing; +import gov.usgs.earthquake.nshmp.internal.Parsing.Delimiter; +import gov.usgs.earthquake.nshmp.www.HazardService.RequestData; +import gov.usgs.earthquake.nshmp.www.ServletUtil.TimedTask; +import gov.usgs.earthquake.nshmp.www.ServletUtil.Timer; +import gov.usgs.earthquake.nshmp.www.meta.Edition; +import gov.usgs.earthquake.nshmp.www.meta.Metadata; +import gov.usgs.earthquake.nshmp.www.meta.Region; +import gov.usgs.earthquake.nshmp.www.meta.Status; + +/** + * Hazard deaggregation service. + * + * @author Peter Powers + */ +@SuppressWarnings("unused") +@WebServlet( + name = "Deaggregation Service", + description = "USGS NSHMP Hazard Deaggregator", + urlPatterns = { + "/deagg", + "/deagg/*" }) +public final class DeaggService extends NshmpServlet { + + /* Developer notes: See HazardService. */ + + @Override + protected void doGet( + HttpServletRequest request, + HttpServletResponse response) + throws ServletException, IOException { + + UrlHelper urlHelper = urlHelper(request, response); + String query = request.getQueryString(); + Optional<String> pathInfo = Optional.ofNullable(request.getPathInfo()); + + if (emptyRequest(request)) { + urlHelper.writeResponse(Metadata.DEAGG_USAGE); + return; + } + + if (!ServletUtil.checkRequestIp(request)) { + String message = Metadata.tooManyRequestsMessage(urlHelper.url); + response.setHeader("Cache-Control", "no-store, no-cache, must-revalidate"); + response.getWriter().print(message); + return; + } + + if (pathInfo.isPresent() && pathInfo.get().equals("/iplist")) { + String message = Joiner.on("\n").join(ServletUtil.IP_COUNT.entrySet()); + response.setHeader("Cache-Control", "no-store, no-cache, must-revalidate"); + response.getWriter().print(message); + return; + } + + RequestData requestData; + try { + if (query != null) { + /* process query '?' request */ + requestData = HazardService.buildRequest(request); + } else { + /* process slash-delimited request */ + List<String> params = Parsing.splitToList(pathInfo.get(), Delimiter.SLASH); + if (params.size() < 7) { + urlHelper.writeResponse(Metadata.DEAGG_USAGE); + return; + } + requestData = HazardService.buildRequest(params); + } + + /* Submit as task to job executor */ + DeaggTask task = new DeaggTask(urlHelper.url, getServletContext(), requestData); + Result result = ServletUtil.TASK_EXECUTOR.submit(task).get(); + String resultStr = GSON.toJson(result); + response.getWriter().print(resultStr); + + } catch (Exception e) { + String message = Metadata.errorMessage(urlHelper.url, e, false); + response.getWriter().print(message); + getServletContext().log(urlHelper.url, e); + } + } + + private static class DeaggTask extends TimedTask<Result> { + + RequestData data; + + DeaggTask(String url, ServletContext context, RequestData data) { + super(url, context); + this.data = data; + } + + @Override + Result calc() throws Exception { + + Hazard hazard = HazardService.calcHazard(data, context); + Deaggregation deagg = HazardCalcs.deaggReturnPeriod( + hazard, + data.returnPeriod.getAsDouble(), + ServletUtil.CALC_EXECUTOR); + + return new Result.Builder() + .requestData(data) + .url(url) + .timer(timer) + .deagg(deagg) + .build(); + } + } + + private static final class ResponseData { + + final Edition edition; + final Region region; + final double latitude; + final double longitude; + final Imt imt; + final double returnperiod; + final Vs30 vs30; + final String rlabel = "Closest Distance, rRup (km)"; + final String mlabel = "Magnitude (Mw)"; + final String εlabel = "% Contribution to Hazard"; + final Object εbins; + + ResponseData(Deaggregation deagg, RequestData request, Imt imt) { + this.edition = request.edition; + this.region = request.region; + this.longitude = request.longitude; + this.latitude = request.latitude; + this.imt = imt; + this.returnperiod = request.returnPeriod.getAsDouble(); + this.vs30 = request.vs30; + this.εbins = deagg.εBins(); + } + } + + private static final class Response { + + final ResponseData metadata; + final Object data; + + Response(ResponseData metadata, Object data) { + this.metadata = metadata; + this.data = data; + } + } + + private static final String TOTAL_KEY = "Total"; + + private static final class Result { + + final String status = Status.SUCCESS.toString(); + final String date = ZonedDateTime.now().format(ServletUtil.DATE_FMT); + final String url; + final Object server; + final List<Response> response; + + Result(String url, Object server, List<Response> response) { + this.url = url; + this.server = server; + this.response = response; + } + + static final class Builder { + + String url; + Timer timer; + RequestData request; + Deaggregation deagg; + + Builder deagg(Deaggregation deagg) { + this.deagg = deagg; + return this; + } + + Builder url(String url) { + this.url = url; + return this; + } + + Builder timer(Timer timer) { + this.timer = timer; + return this; + } + + Builder requestData(RequestData request) { + this.request = request; + return this; + } + + Result build() { + + ImmutableList.Builder<Response> responseListBuilder = ImmutableList.builder(); + for (Imt imt : request.imts) { + ResponseData responseData = new ResponseData( + deagg, + request, + imt); + Object deaggs = deagg.toJson(imt); + Response response = new Response(responseData, deaggs); + responseListBuilder.add(response); + } + List<Response> responseList = responseListBuilder.build(); + Object server = Metadata.serverData(ServletUtil.THREAD_COUNT, timer); + + return new Result(url, server, responseList); + } + } + } + +} diff --git a/src/gov/usgs/earthquake/nshmp/www/DeaggService2.java b/src/gov/usgs/earthquake/nshmp/www/DeaggService2.java new file mode 100644 index 000000000..94deb23b0 --- /dev/null +++ b/src/gov/usgs/earthquake/nshmp/www/DeaggService2.java @@ -0,0 +1,384 @@ +package gov.usgs.earthquake.nshmp.www; + +import static com.google.common.base.Preconditions.checkNotNull; +import static gov.usgs.earthquake.nshmp.www.ServletUtil.GSON; +import static gov.usgs.earthquake.nshmp.www.ServletUtil.MODEL_CACHE_CONTEXT_ID; +import static gov.usgs.earthquake.nshmp.www.ServletUtil.emptyRequest; +import static gov.usgs.earthquake.nshmp.www.Util.readBoolean; +import static gov.usgs.earthquake.nshmp.www.Util.readDouble; +import static gov.usgs.earthquake.nshmp.www.Util.readValue; +import static gov.usgs.earthquake.nshmp.www.Util.Key.BASIN; +import static gov.usgs.earthquake.nshmp.www.Util.Key.IMT; +import static gov.usgs.earthquake.nshmp.www.Util.Key.LATITUDE; +import static gov.usgs.earthquake.nshmp.www.Util.Key.LONGITUDE; +import static gov.usgs.earthquake.nshmp.www.Util.Key.MODEL; +import static gov.usgs.earthquake.nshmp.www.Util.Key.RETURNPERIOD; +import static gov.usgs.earthquake.nshmp.www.Util.Key.VS30; + +import java.io.IOException; +import java.io.InputStream; +import java.net.URL; +import java.time.ZonedDateTime; +import java.util.Arrays; +import java.util.EnumSet; +import java.util.List; +import java.util.Optional; +import java.util.Properties; + +import javax.servlet.ServletContext; +import javax.servlet.ServletException; +import javax.servlet.annotation.WebServlet; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +import com.google.common.cache.LoadingCache; +import com.google.common.collect.ImmutableList; + +import gov.usgs.earthquake.nshmp.calc.CalcConfig; +import gov.usgs.earthquake.nshmp.calc.Deaggregation; +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.eq.model.HazardModel; +import gov.usgs.earthquake.nshmp.geo.Location; +import gov.usgs.earthquake.nshmp.gmm.Imt; +import gov.usgs.earthquake.nshmp.internal.Parsing; +import gov.usgs.earthquake.nshmp.internal.Parsing.Delimiter; +import gov.usgs.earthquake.nshmp.www.ServletUtil.TimedTask; +import gov.usgs.earthquake.nshmp.www.ServletUtil.Timer; +import gov.usgs.earthquake.nshmp.www.meta.Metadata; +import gov.usgs.earthquake.nshmp.www.meta.Status; + +/** + * Hazard deaggregation service. + * + * @author Peter Powers + */ +@Deprecated +@SuppressWarnings("unused") +@WebServlet( + name = "Deaggregation Service (new)", + description = "USGS NSHMP Hazard Deaggregator", + urlPatterns = { + "/deagg2", + "/deagg2/*" }) +public final class DeaggService2 extends NshmpServlet { + + /* Developer notes: See HazardService. */ + + private LoadingCache<Model, HazardModel> modelCache; + private URL basinUrl; + + private static final String USAGE = SourceServices.GSON.toJson( + new SourceServices.ResponseData()); + + @Override + @SuppressWarnings("unchecked") + public void init() throws ServletException { + + ServletContext context = getServletConfig().getServletContext(); + Object modelCache = context.getAttribute(MODEL_CACHE_CONTEXT_ID); + this.modelCache = (LoadingCache<Model, HazardModel>) modelCache; + + try (InputStream config = + DeaggService2.class.getResourceAsStream("/config.properties")) { + + checkNotNull(config, "Missing config.properties"); + + Properties props = new Properties(); + props.load(config); + if (props.containsKey("basin_host")) { + /* + * TODO Site builder tests if service is working, which may be + * inefficient for single call services. + */ + URL url = new URL(props.getProperty("basin_host") + "/nshmp-site-ws/basin"); + this.basinUrl = url; + } + } catch (IOException | NullPointerException e) { + throw new ServletException(e); + } + } + + @Override + protected void doGet( + HttpServletRequest request, + HttpServletResponse response) + throws ServletException, IOException { + + UrlHelper urlHelper = urlHelper(request, response); + + if (emptyRequest(request)) { + urlHelper.writeResponse(USAGE); + return; + } + + try { + RequestData requestData = buildRequestData(request); + + /* Submit as task to job executor */ + Deagg2Task task = new Deagg2Task(urlHelper.url, getServletContext(), requestData); + Result result = ServletUtil.TASK_EXECUTOR.submit(task).get(); + GSON.toJson(result, response.getWriter()); + + } catch (Exception e) { + String message = Metadata.errorMessage(urlHelper.url, e, false); + response.getWriter().print(message); + getServletContext().log(urlHelper.url, e); + } + } + + /* Reduce query string key-value pairs. */ + static RequestData buildRequestData(HttpServletRequest request) { + + try { + + List<Model> models; + double lon; + double lat; + Imt imt; + double vs30; + double returnPeriod; + boolean basin; + + if (request.getQueryString() != null) { + /* process query '?' request */ + models = readModelsFromQuery(request); + lon = readDouble(LONGITUDE, request); + lat = readDouble(LATITUDE, request); + imt = readValue(IMT, request, Imt.class); + vs30 = readDouble(VS30, request); + returnPeriod = readDouble(RETURNPERIOD, request); + basin = readBoolean(BASIN, request); + + } else { + /* process slash-delimited request */ + List<String> params = Parsing.splitToList( + request.getPathInfo(), + Delimiter.SLASH); + models = readModelsFromString(params.get(0)); + lon = Double.valueOf(params.get(1)); + lat = Double.valueOf(params.get(2)); + imt = Imt.valueOf(params.get(3)); + vs30 = Double.valueOf(params.get(4)); + returnPeriod = Double.valueOf(params.get(5)); + basin = Boolean.valueOf(params.get(6)); + } + + return new RequestData( + models, + lon, + lat, + imt, + vs30, + returnPeriod, + basin); + + } catch (Exception e) { + throw new IllegalArgumentException("Error parsing request URL", e); + } + } + + private static List<Model> readModelsFromString(String models) { + return Parsing.splitToList(models, Delimiter.COMMA).stream() + .map(Model::valueOf) + .distinct() + .collect(ImmutableList.toImmutableList()); + } + + private static List<Model> readModelsFromQuery(HttpServletRequest request) { + String[] ids = Util.readValues(MODEL, request); + return Arrays.stream(ids) + .map(Model::valueOf) + .distinct() + .collect(ImmutableList.toImmutableList()); + } + + private class Deagg2Task extends TimedTask<Result> { + + RequestData data; + + Deagg2Task(String url, ServletContext context, RequestData data) { + super(url, context); + this.data = data; + } + + @Override + Result calc() throws Exception { + Deaggregation deagg = calcDeagg(data); + + return new Result.Builder() + .requestData(data) + .url(url) + .timer(timer) + .deagg(deagg) + .build(); + } + } + + /* + * Developer notes: + * + * We're opting here to fetch basin terms ourselves. If we were to set the + * basin provider in the config, which requires additions to config, the URL + * is tested every time a site is created for a servlet request. While this + * worked for maps it's not good here. + * + * Site has logic for parsing the basin service response, which perhaps it + * shouldn't. TODO is it worth decomposing data objects and services + */ + Deaggregation calcDeagg(RequestData data) { + Location loc = Location.create(data.latitude, data.longitude); + + Site site = Site.builder() + .location(Location.create(data.latitude, data.longitude)) + .basinDataProvider(data.basin ? this.basinUrl : null) + .vs30(data.vs30) + .build(); + + Hazard[] hazards = new Hazard[data.models.size()]; + for (int i = 0; i < data.models.size(); i++) { + HazardModel model = modelCache.getUnchecked(data.models.get(i)); + hazards[i] = process(model, site, data.imt); + } + Hazard hazard = Hazard.merge(hazards); + return HazardCalcs.deaggReturnPeriod( + hazard, + data.returnPeriod, + ServletUtil.CALC_EXECUTOR); + } + + private static Hazard process(HazardModel model, Site site, Imt imt) { + CalcConfig config = CalcConfig.Builder + .copyOf(model.config()) + .imts(EnumSet.of(imt)) + .build(); + return HazardCalcs.hazard(model, config, site, ServletUtil.CALC_EXECUTOR); + } + + static final class RequestData { + + final List<Model> models; + final double latitude; + final double longitude; + final Imt imt; + final double vs30; + final double returnPeriod; + final boolean basin; + + RequestData( + List<Model> models, + double longitude, + double latitude, + Imt imt, + double vs30, + double returnPeriod, + boolean basin) { + + this.models = models; + this.latitude = latitude; + this.longitude = longitude; + this.imt = imt; + this.vs30 = vs30; + this.returnPeriod = returnPeriod; + this.basin = basin; + } + } + + private static final class ResponseData { + + final List<Model> models; + final double longitude; + final double latitude; + final Imt imt; + final double vs30; + final double returnperiod; + final String rlabel = "Closest Distance, rRup (km)"; + final String mlabel = "Magnitude (Mw)"; + final String εlabel = "% Contribution to Hazard"; + final Object εbins; + + ResponseData(Deaggregation deagg, RequestData request, Imt imt) { + this.models = request.models; + this.longitude = request.longitude; + this.latitude = request.latitude; + this.imt = imt; + this.vs30 = request.vs30; + this.returnperiod = request.returnPeriod; + this.εbins = deagg.εBins(); + } + } + + private static final class Response { + + final ResponseData metadata; + final Object data; + + Response(ResponseData metadata, Object data) { + this.metadata = metadata; + this.data = data; + } + } + + private static final class Result { + + final String status = Status.SUCCESS.toString(); + final String date = ZonedDateTime.now().format(ServletUtil.DATE_FMT); + final String url; + final Object server; + final List<Response> response; + + Result(String url, Object server, List<Response> response) { + this.url = url; + this.server = server; + this.response = response; + } + + static final class Builder { + + String url; + Timer timer; + RequestData request; + Deaggregation deagg; + + Builder deagg(Deaggregation deagg) { + this.deagg = deagg; + return this; + } + + Builder url(String url) { + this.url = url; + return this; + } + + Builder timer(Timer timer) { + this.timer = timer; + return this; + } + + Builder requestData(RequestData request) { + this.request = request; + return this; + } + + Result build() { + + ImmutableList.Builder<Response> responseListBuilder = ImmutableList.builder(); + Imt imt = request.imt; + ResponseData responseData = new ResponseData( + deagg, + request, + imt); + Object deaggs = deagg.toJson(imt); + Response response = new Response(responseData, deaggs); + responseListBuilder.add(response); + + List<Response> responseList = responseListBuilder.build(); + Object server = Metadata.serverData(ServletUtil.THREAD_COUNT, timer); + + return new Result(url, server, responseList); + } + } + } + +} diff --git a/src/gov/usgs/earthquake/nshmp/www/GmmServices.java b/src/gov/usgs/earthquake/nshmp/www/GmmServices.java new file mode 100644 index 000000000..6a902e84e --- /dev/null +++ b/src/gov/usgs/earthquake/nshmp/www/GmmServices.java @@ -0,0 +1,701 @@ +package gov.usgs.earthquake.nshmp.www; + +import static com.google.common.base.Preconditions.checkArgument; +import static gov.usgs.earthquake.nshmp.ResponseSpectra.spectra; +import static gov.usgs.earthquake.nshmp.gmm.GmmInput.Field.VSINF; +import static gov.usgs.earthquake.nshmp.gmm.Imt.AI; +import static gov.usgs.earthquake.nshmp.gmm.Imt.PGV; +import static gov.usgs.earthquake.nshmp.www.Util.readValue; +import static gov.usgs.earthquake.nshmp.www.Util.Key.IMT; +import static gov.usgs.earthquake.nshmp.www.meta.Metadata.errorMessage; + +import java.io.BufferedReader; +import java.io.IOException; +import java.lang.reflect.Type; +import java.time.ZonedDateTime; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Comparator; +import java.util.EnumSet; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Map.Entry; +import java.util.Optional; +import java.util.Set; +import java.util.TreeSet; +import java.util.stream.Collectors; + +import javax.servlet.ServletException; +import javax.servlet.annotation.WebServlet; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +import com.google.common.base.Enums; +import com.google.common.collect.FluentIterable; +import com.google.common.collect.Range; +import com.google.common.collect.Sets; +import com.google.common.primitives.Doubles; +import com.google.gson.Gson; +import com.google.gson.GsonBuilder; +import com.google.gson.JsonElement; +import com.google.gson.JsonObject; +import com.google.gson.JsonSerializationContext; +import com.google.gson.JsonSerializer; + +import gov.usgs.earthquake.nshmp.GroundMotions; +import gov.usgs.earthquake.nshmp.GroundMotions.DistanceResult; +import gov.usgs.earthquake.nshmp.ResponseSpectra.MultiResult; +import gov.usgs.earthquake.nshmp.data.Data; +import gov.usgs.earthquake.nshmp.data.XySequence; +import gov.usgs.earthquake.nshmp.gmm.Gmm; +import gov.usgs.earthquake.nshmp.gmm.GmmInput; +import gov.usgs.earthquake.nshmp.gmm.GmmInput.Builder; +import gov.usgs.earthquake.nshmp.gmm.GmmInput.Constraints; +import gov.usgs.earthquake.nshmp.gmm.GmmInput.Field; +import gov.usgs.earthquake.nshmp.gmm.Imt; +import gov.usgs.earthquake.nshmp.internal.Parsing; +import gov.usgs.earthquake.nshmp.internal.Parsing.Delimiter; +import gov.usgs.earthquake.nshmp.www.meta.EnumParameter; +import gov.usgs.earthquake.nshmp.www.meta.ParamType; +import gov.usgs.earthquake.nshmp.www.meta.Status; +import gov.usgs.earthquake.nshmp.www.meta.Util; + +@WebServlet( + name = "Ground Motion Model Services", + description = "Utilities for working with ground motion models", + urlPatterns = { + "/gmm", + "/gmm/*" }) +public class GmmServices extends NshmpServlet { + private static final long serialVersionUID = 1L; + + private static final Gson GSON; + + private static final String GMM_KEY = "gmm"; + private static final String RMIN_KEY = "rMin"; + private static final String RMAX_KEY = "rMax"; + private static final String IMT_KEY = "imt"; + private static final int ROUND = 5; + + static { + GSON = new GsonBuilder() + .setPrettyPrinting() + .serializeNulls() + .disableHtmlEscaping() + .registerTypeAdapter(Double.class, new Util.NaNSerializer()) + .registerTypeAdapter(Parameters.class, new Parameters.Serializer()) + .registerTypeAdapter(Imt.class, new Util.EnumSerializer<Imt>()) + .registerTypeAdapter(Constraints.class, new Util.ConstraintsSerializer()) + .create(); + } + + @Override + protected void doGet( + HttpServletRequest request, + HttpServletResponse response) + throws ServletException, IOException { + + UrlHelper urlHelper = urlHelper(request, response); + Service service = getService(request); + + try { + /* At a minimum, Gmms must be defined. */ + if (!hasGMM(request, service, urlHelper)) return; + + Map<String, String[]> params = request.getParameterMap(); + + ResponseData svcResponse = processRequest(service, params, urlHelper); + + response.getWriter().print(GSON.toJson(svcResponse)); + } catch (Exception e) { + String message = errorMessage(urlHelper.url, e, false); + response.getWriter().print(message); + e.printStackTrace(); + } + } + + @Override + protected void doPost( + HttpServletRequest request, + HttpServletResponse response) + throws ServletException, IOException { + + BufferedReader requestReader = request.getReader(); + UrlHelper urlHelper = urlHelper(request, response); + Service service = getService(request); + + try { + /* At a minimum, Gmms must be defined. */ + if (!hasGMM(request, service, urlHelper)) return; + + String[] gmmParams = request.getParameterValues(GMM_KEY); + + List<String> requestData = requestReader.lines().collect(Collectors.toList()); + + if (requestData.isEmpty()) { + throw new IllegalStateException("Post data is empty"); + } + + List<String> keys = Parsing.splitToList(requestData.get(0), Delimiter.COMMA); + + ResponseDataPost svcResponse = new ResponseDataPost(service, urlHelper); + + List<ResponseData> gmmResponses = requestData.subList(1, requestData.size()) + .parallelStream() + .filter((line) -> !line.startsWith("#") && !line.trim().isEmpty()) + .map((line) -> { + List<String> values = Parsing.splitToList(line, Delimiter.COMMA); + + Map<String, String[]> params = new HashMap<>(); + params.put(GMM_KEY, gmmParams); + + int index = 0; + + for (String key : keys) { + String value = values.get(index); + if ("null".equals(value.toLowerCase())) continue; + + params.put(key, new String[] { value }); + index++; + } + + return processRequest(service, params, urlHelper); + }) + .collect(Collectors.toList()); + + svcResponse.setResponse(gmmResponses); + response.getWriter().print(GSON.toJson(svcResponse)); + } catch (Exception e) { + String message = errorMessage(urlHelper.url, e, false); + response.getWriter().print(message); + e.printStackTrace(); + } + } + + static class RequestData { + Set<Gmm> gmms; + GmmInput input; + + RequestData(Map<String, String[]> params) { + this.gmms = buildGmmSet(params); + this.input = buildInput(params); + } + } + + static class RequestDataDistance extends RequestData { + String imt; + double minDistance; + double maxDistance; + + RequestDataDistance( + Map<String, String[]> params, + String imt, + double rMin, + double rMax) { + + super(params); + + this.imt = imt; + minDistance = rMin; + maxDistance = rMax; + } + } + + static class ResponseDataPost { + String name; + String status = Status.SUCCESS.toString(); + String date = ZonedDateTime.now().format(ServletUtil.DATE_FMT); + String url; + Object server; + List<ResponseData> response; + + ResponseDataPost(Service service, UrlHelper urlHelper) { + name = service.resultName; + + server = gov.usgs.earthquake.nshmp.www.meta.Metadata.serverData(1, ServletUtil.timer()); + + url = urlHelper.url; + } + + void setResponse(List<ResponseData> response) { + this.response = response; + } + } + + static class ResponseData { + String name; + String status = Status.SUCCESS.toString(); + String date = ZonedDateTime.now().format(ServletUtil.DATE_FMT); + String url; + Object server; + RequestData request; + GmmXYDataGroup means; + GmmXYDataGroup sigmas; + + ResponseData(Service service, RequestData request) { + name = service.resultName; + + server = gov.usgs.earthquake.nshmp.www.meta.Metadata.serverData(1, ServletUtil.timer()); + + this.request = request; + + means = GmmXYDataGroup.create( + service.groupNameMean, + service.xLabel, + service.yLabelMedian); + + sigmas = GmmXYDataGroup.create( + service.groupNameSigma, + service.xLabel, + service.yLabelSigma); + } + + void setXY( + Map<Gmm, List<Double>> x, + Map<Gmm, List<Double>> means, + Map<Gmm, List<Double>> sigmas) { + + for (Gmm gmm : means.keySet()) { + XySequence xyMeans = XySequence.create( + x.get(gmm), + Data.round(ROUND, Data.exp(new ArrayList<>(means.get(gmm))))); + this.means.add(gmm.name(), gmm.toString(), xyMeans, gmm); + + XySequence xySigmas = XySequence.create( + x.get(gmm), + Data.round(ROUND, new ArrayList<>(sigmas.get(gmm)))); + this.sigmas.add(gmm.name(), gmm.toString(), xySigmas, gmm); + } + } + + } + + private static class GmmXYDataGroup extends XY_DataGroup { + + GmmXYDataGroup(String name, String xLabel, String yLabel) { + super(name, xLabel, yLabel); + } + + public static GmmXYDataGroup create(String name, String xLabel, String yLabel) { + return new GmmXYDataGroup(name, xLabel, yLabel); + } + + public GmmXYDataGroup add(String id, String name, XySequence data, Gmm gmm) { + this.data.add(new GmmSeries(id, name, data, gmm)); + return this; + } + + static class GmmSeries extends XY_DataGroup.Series { + final Constraints constraints; + final TreeSet<String> supportedImts; + + GmmSeries(String id, String label, XySequence data, Gmm gmm) { + super(id, label, data); + constraints = gmm.constraints(); + supportedImts = gmm.supportedIMTs().stream() + .map(imt -> imt.name()) + .collect(Collectors.toCollection(TreeSet::new)); + } + } + } + + static ResponseData processRequest( + Service service, + Map<String, String[]> params, + UrlHelper urlHelper) { + ResponseData svcResponse = null; + + switch (service) { + case DISTANCE: + case HW_FW: + svcResponse = processRequestDistance(service, params); + break; + case SPECTRA: + svcResponse = processRequestSpectra(service, params); + break; + default: + throw new IllegalStateException("Service not supported [" + service + "]"); + } + + svcResponse.url = urlHelper.url; + return svcResponse; + } + + static ResponseData processRequestDistance( + Service service, Map<String, String[]> params) { + + boolean isLogSpace = service.equals(Service.DISTANCE) ? true : false; + Imt imt = readValue(IMT, params, Imt.class); + double rMin = Double.valueOf(params.get(RMIN_KEY)[0]); + double rMax = Double.valueOf(params.get(RMAX_KEY)[0]); + + RequestDataDistance request = new RequestDataDistance( + params, imt.toString(), rMin, rMax); + + DistanceResult result = GroundMotions.distanceGroundMotions( + request.gmms, request.input, imt, rMin, rMax, isLogSpace); + + ResponseData response = new ResponseData(service, request); + response.setXY(result.distance, result.means, result.sigmas); + + return response; + } + + private static ResponseData processRequestSpectra( + Service service, Map<String, String[]> params) { + + RequestData request = new RequestData(params); + MultiResult result = spectra(request.gmms, request.input, false); + + ResponseData response = new ResponseData(service, request); + response.setXY(result.periods, result.means, result.sigmas); + + return response; + } + + static Set<Gmm> buildGmmSet(Map<String, String[]> params) { + checkArgument(params.containsKey(GMM_KEY), + "Missing ground motion model key: " + GMM_KEY); + return Sets.newEnumSet( + FluentIterable + .from(params.get(GMM_KEY)) + .transform(Enums.stringConverter(Gmm.class)), + Gmm.class); + } + + static GmmInput buildInput(Map<String, String[]> params) { + + Builder builder = GmmInput.builder().withDefaults(); + for (Entry<String, String[]> entry : params.entrySet()) { + if (entry.getKey().equals(GMM_KEY) || entry.getKey().equals(IMT_KEY) || + entry.getKey().equals(RMAX_KEY) || entry.getKey().equals(RMIN_KEY)) + continue; + Field id = Field.fromString(entry.getKey()); + String value = entry.getValue()[0]; + if (value.equals("")) { + continue; + } + builder.set(id, value); + } + return builder.build(); + } + + static final class Metadata { + + String status = Status.USAGE.toString(); + String description; + String syntax; + Parameters parameters; + + Metadata(Service service) { + this.syntax = "%s://%s/nshmp-haz-ws/gmm" + service.pathInfo + "?"; + this.description = service.description; + this.parameters = new Parameters(service); + } + } + + /* + * Placeholder class; all parameter serialization is done via the custom + * Serializer. Service reference needed serialize(). + */ + static final class Parameters { + + private final Service service; + + Parameters(Service service) { + this.service = service; + } + + static final class Serializer implements JsonSerializer<Parameters> { + + @Override + public JsonElement serialize( + Parameters meta, + Type type, + JsonSerializationContext context) { + + JsonObject root = new JsonObject(); + + if (!meta.service.equals(Service.SPECTRA)) { + Set<Imt> imtSet = EnumSet.complementOf(EnumSet.range(PGV, AI)); + final EnumParameter<Imt> imts; + imts = new EnumParameter<>( + "Intensity measure type", + ParamType.STRING, + imtSet); + root.add(IMT_KEY, context.serialize(imts)); + } + + /* Serialize input fields. */ + Constraints defaults = Constraints.defaults(); + for (Field field : Field.values()) { + Param param = createGmmInputParam(field, defaults.get(field)); + JsonElement fieldElem = context.serialize(param); + root.add(field.id, fieldElem); + } + + /* Add only add those Gmms that belong to a Group. */ + List<Gmm> gmms = Arrays.stream(Gmm.Group.values()) + .flatMap(group -> group.gmms().stream()) + .sorted(Comparator.comparing(Object::toString)) + .distinct() + .collect(Collectors.toList()); + + GmmParam gmmParam = new GmmParam( + GMM_NAME, + GMM_INFO, + gmms); + root.add(GMM_KEY, context.serialize(gmmParam)); + + /* Add gmm groups. */ + GroupParam groups = new GroupParam( + GROUP_NAME, + GROUP_INFO, + EnumSet.allOf(Gmm.Group.class)); + root.add(GROUP_KEY, context.serialize(groups)); + + return root; + } + } + } + + @SuppressWarnings("unchecked") + private static Param createGmmInputParam( + Field field, + Optional<?> constraint) { + return (field == VSINF) ? new BooleanParam(field) + : new NumberParam(field, (Range<Double>) constraint.get()); + } + + /* + * Marker interface for spectra parameters. This was previously implemented as + * an abstract class for label, info, and units, but Gson serialized subclass + * fields before parent fields. To maintain a preferred order, one can write + * custom serializers or repeat these four fields in each implementation. + */ + private static interface Param {} + + @SuppressWarnings("unused") + private static final class NumberParam implements Param { + + final String label; + final String info; + final String units; + final Double min; + final Double max; + final Double value; + + NumberParam(GmmInput.Field field, Range<Double> constraint) { + this(field, constraint, field.defaultValue); + } + + NumberParam(GmmInput.Field field, Range<Double> constraint, Double value) { + this.label = field.label; + this.info = field.info; + this.units = field.units.orElse(null); + this.min = constraint.lowerEndpoint(); + this.max = constraint.upperEndpoint(); + this.value = Doubles.isFinite(value) ? value : null; + } + } + + @SuppressWarnings("unused") + private static final class BooleanParam implements Param { + + final String label; + final String info; + final boolean value; + + BooleanParam(GmmInput.Field field) { + this(field, field.defaultValue == 1.0); + } + + BooleanParam(GmmInput.Field field, boolean value) { + this.label = field.label; + this.info = field.info; + this.value = value; + } + } + + private static final String GMM_NAME = "Ground Motion Models"; + private static final String GMM_INFO = "Empirical models of ground motion"; + + @SuppressWarnings("unused") + private static class GmmParam implements Param { + + final String label; + final String info; + final List<Value> values; + + GmmParam(String label, String info, List<Gmm> gmms) { + this.label = label; + this.info = info; + this.values = gmms.stream() + .map(gmm -> new Value(gmm)) + .collect(Collectors.toList()); + } + + private static class Value { + + final String id; + final String label; + final ArrayList<String> supportedImts; + final Constraints constraints; + + Value(Gmm gmm) { + this.id = gmm.name(); + this.label = gmm.toString(); + this.supportedImts = SupportedImts(gmm.supportedIMTs()); + this.constraints = gmm.constraints(); + } + } + + private static ArrayList<String> SupportedImts(Set<Imt> imts) { + ArrayList<String> supportedImts = new ArrayList<>(); + + for (Imt imt : imts) { + supportedImts.add(imt.name()); + } + + return supportedImts; + } + + } + + private static final String GROUP_KEY = "group"; + private static final String GROUP_NAME = "Ground Motion Model Groups"; + private static final String GROUP_INFO = "Groups of related ground motion models "; + + @SuppressWarnings("unused") + private static final class GroupParam implements Param { + + final String label; + final String info; + final List<Value> values; + + GroupParam(String label, String info, Set<Gmm.Group> groups) { + this.label = label; + this.info = info; + this.values = new ArrayList<>(); + for (Gmm.Group group : groups) { + this.values.add(new Value(group)); + } + } + + private static class Value { + + final String id; + final String label; + final List<Gmm> data; + + Value(Gmm.Group group) { + this.id = group.name(); + this.label = group.toString(); + this.data = group.gmms(); + } + } + } + + private static enum Service { + + DISTANCE( + "Ground Motion Vs. Distance", + "Compute ground motion Vs. distance", + "/distance", + "Means", + "Sigmas", + "Distance (km)", + "Median ground motion (g)", + "Standard deviation"), + + HW_FW( + "Hanging Wall Effect", + "Compute hanging wall effect on ground motion Vs. distance", + "/hw-fw", + "Means", + "Sigmas", + "Distance (km)", + "Median ground motion (g)", + "Standard deviation"), + + SPECTRA( + "Deterministic Response Spectra", + "Compute deterministic response spectra", + "/spectra", + "Means", + "Sigmas", + "Period (s)", + "Median ground motion (g)", + "Standard deviation"); + + final String name; + final String description; + final String pathInfo; + final String resultName; + final String groupNameMean; + final String groupNameSigma; + final String xLabel; + final String yLabelMedian; + final String yLabelSigma; + + private Service( + String name, String description, + String pathInfo, String groupNameMean, + String groupNameSigma, String xLabel, + String yLabelMedian, String yLabelSigma) { + this.name = name; + this.description = description; + this.resultName = name + " Results"; + this.pathInfo = pathInfo; + this.groupNameMean = groupNameMean; + this.groupNameSigma = groupNameSigma; + this.xLabel = xLabel; + this.yLabelMedian = yLabelMedian; + this.yLabelSigma = yLabelSigma; + } + + } + + private static Service getService(HttpServletRequest request) { + Service service = null; + String pathInfo = request.getPathInfo(); + + switch (pathInfo) { + case PathInfo.DISTANCE: + service = Service.DISTANCE; + break; + case PathInfo.HW_FW: + service = Service.HW_FW; + break; + case PathInfo.SPECTRA: + service = Service.SPECTRA; + break; + default: + throw new IllegalStateException("Unsupported service [" + pathInfo + "]"); + } + + return service; + } + + private static final class PathInfo { + private static final String DISTANCE = "/distance"; + private static final String HW_FW = "/hw-fw"; + private static final String SPECTRA = "/spectra"; + } + + private static boolean hasGMM( + HttpServletRequest request, + Service service, + UrlHelper urlHelper) + throws IOException { + + String gmmParam = request.getParameter(GMM_KEY); + if (gmmParam != null) return true; + + String usage = GSON.toJson(new Metadata(service)); + urlHelper.writeResponse(usage); + return false; + } + +} diff --git a/src/gov/usgs/earthquake/nshmp/www/HazardService.java b/src/gov/usgs/earthquake/nshmp/www/HazardService.java new file mode 100644 index 000000000..fabf7506d --- /dev/null +++ b/src/gov/usgs/earthquake/nshmp/www/HazardService.java @@ -0,0 +1,527 @@ +package gov.usgs.earthquake.nshmp.www; + +import static com.google.common.base.Preconditions.checkState; +import static gov.usgs.earthquake.nshmp.calc.HazardExport.curvesBySource; +import static gov.usgs.earthquake.nshmp.www.ServletUtil.GSON; +import static gov.usgs.earthquake.nshmp.www.ServletUtil.MODEL_CACHE_CONTEXT_ID; +import static gov.usgs.earthquake.nshmp.www.ServletUtil.emptyRequest; +import static gov.usgs.earthquake.nshmp.www.Util.readDouble; +import static gov.usgs.earthquake.nshmp.www.Util.readValue; +import static gov.usgs.earthquake.nshmp.www.Util.readValues; +import static gov.usgs.earthquake.nshmp.www.Util.Key.EDITION; +import static gov.usgs.earthquake.nshmp.www.Util.Key.IMT; +import static gov.usgs.earthquake.nshmp.www.Util.Key.LATITUDE; +import static gov.usgs.earthquake.nshmp.www.Util.Key.LONGITUDE; +import static gov.usgs.earthquake.nshmp.www.Util.Key.REGION; +import static gov.usgs.earthquake.nshmp.www.Util.Key.RETURNPERIOD; +import static gov.usgs.earthquake.nshmp.www.Util.Key.VS30; +import static gov.usgs.earthquake.nshmp.www.meta.Region.CEUS; +import static gov.usgs.earthquake.nshmp.www.meta.Region.COUS; +import static gov.usgs.earthquake.nshmp.www.meta.Region.WUS; + +import java.io.IOException; +import java.time.ZonedDateTime; +import java.util.EnumMap; +import java.util.List; +import java.util.Map; +import java.util.Optional; +import java.util.OptionalDouble; +import java.util.Set; + +import javax.servlet.ServletContext; +import javax.servlet.ServletException; +import javax.servlet.annotation.WebServlet; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +import com.google.common.base.Joiner; +import com.google.common.cache.LoadingCache; +import com.google.common.collect.ImmutableList; + +import gov.usgs.earthquake.nshmp.calc.CalcConfig; +import gov.usgs.earthquake.nshmp.calc.CalcConfig.Builder; +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.Vs30; +import gov.usgs.earthquake.nshmp.data.XySequence; +import gov.usgs.earthquake.nshmp.eq.model.HazardModel; +import gov.usgs.earthquake.nshmp.eq.model.SourceType; +import gov.usgs.earthquake.nshmp.geo.Location; +import gov.usgs.earthquake.nshmp.gmm.Imt; +import gov.usgs.earthquake.nshmp.internal.Parsing; +import gov.usgs.earthquake.nshmp.internal.Parsing.Delimiter; +import gov.usgs.earthquake.nshmp.www.ServletUtil.TimedTask; +import gov.usgs.earthquake.nshmp.www.ServletUtil.Timer; +import gov.usgs.earthquake.nshmp.www.meta.Edition; +import gov.usgs.earthquake.nshmp.www.meta.Metadata; +import gov.usgs.earthquake.nshmp.www.meta.Region; +import gov.usgs.earthquake.nshmp.www.meta.Status; + +/** + * Probabilisitic seismic hazard calculation service. + * + * @author Peter Powers + */ +@SuppressWarnings("unused") +@WebServlet( + name = "Hazard Service", + description = "USGS NSHMP Hazard Curve Calculator", + urlPatterns = { + "/hazard", + "/hazard/*" }) +public final class HazardService extends NshmpServlet { + + /* + * Developer notes: + * + * The HazardService and DeaggService are very similar. Deagg delegates to a + * package method HazardService.hazardCalc() to obtain a Hazard object, which + * it then deaggregates. This method may combine Hazard objects from CEUS and + * WUS models, otherwise it runs a single model. HazardService.RequestData + * objects are common to both services, with the understanding that Optional + * fields (1) 'imts' will always contain a Set<Imt> with a single entry for + * deagg, and that (2) 'returnPeriod' will be absent for hazard. + * + * Nshmp-haz calculations are designed to leverage all available processors by + * default distributing work using the ServletUtil.CALC_EXECUTOR. This can + * create problems in a servlet environment, however, because Tomcat does not + * support a single threaded request queue where requests are processed as + * they are received with the next task starting only once the prior has + * finished. One can really only limit the maximum number of simultaneous + * requests. When multiple requests are received in a short span, Tomcat will + * attempt to run hazard or deagg calculations simultaneously. The net effect + * is that there can be out of memory problems as too many results are + * retained, and multiple requests do not return until all are finished. + * + * To address this, requests of HazardService and DeaggService are submitted + * as tasks to the single-threaded ServletUtil.TASK_EXECUTOR and are processed + * one-at-a-time in the order received. + */ + + /* + * IMTs: PGA, SA0P20, SA1P00 TODO this need to be updated to the result of + * polling all models and supports needs to be updated to specific models + * + * Regions: COUS, WUS, CEUS, [HI, AK, GM, AS, SAM, ...] + * + * vs30: 180, 259, 360, 537, 760, 1150, 2000 + * + * 2014 updated values + * + * vs30: 185, 260, 365, 530, 760, 1080, 2000 + * + */ + + @Override + protected void doGet( + HttpServletRequest request, + HttpServletResponse response) + throws ServletException, IOException { + + UrlHelper urlHelper = urlHelper(request, response); + String query = request.getQueryString(); + Optional<String> pathInfo = Optional.ofNullable(request.getPathInfo()); + + if (emptyRequest(request)) { + urlHelper.writeResponse(Metadata.HAZARD_USAGE); + return; + } + + if (!ServletUtil.checkRequestIp(request)) { + String message = Metadata.tooManyRequestsMessage(urlHelper.url); + response.setHeader("Cache-Control", "no-store, no-cache, must-revalidate"); + response.getWriter().print(message); + return; + } + + if (pathInfo.isPresent()) { + if (pathInfo.get().equals("/iplist")) { + String message = Joiner.on("\n").join(ServletUtil.IP_COUNT.entrySet()); + response.setHeader("Cache-Control", "no-store, no-cache, must-revalidate"); + response.getWriter().print(message); + return; + } + } + + RequestData requestData; + try { + if (query != null) { + /* process query '?' request */ + requestData = buildRequest(request); + } else { + /* process slash-delimited request */ + List<String> params = Parsing.splitToList(pathInfo.get(), Delimiter.SLASH); + if (params.size() < 6) { + urlHelper.writeResponse(Metadata.HAZARD_USAGE); + return; + } + requestData = buildRequest(params); + } + + /* Submit as task to job executor */ + HazardTask task = new HazardTask(urlHelper.url, getServletContext(), requestData); + Result result = ServletUtil.TASK_EXECUTOR.submit(task).get(); + // GSON.toJson(result, response.getWriter()); TODO test and use elsewhere? + String resultStr = GSON.toJson(result); + response.getWriter().print(resultStr); + + } catch (Exception e) { + String message = Metadata.errorMessage(urlHelper.url, e, false); + response.getWriter().print(message); + getServletContext().log(urlHelper.url, e); + } + } + + /* + * Reduce query string key-value pairs. This method is shared with deagg. + * Deagg must supply a single Imt. See RequestData notes below. + */ + static RequestData buildRequest(HttpServletRequest request) { + + Map<String, String[]> paramMap = request.getParameterMap(); + + /* Read params as for hazard. */ + double lon = readDouble(LONGITUDE, request); + double lat = readDouble(LATITUDE, request); + Vs30 vs30 = Vs30.fromValue(readDouble(VS30, request)); + Edition edition = readValue(EDITION, request, Edition.class); + Region region = ServletUtil.checkRegion( + readValue(REGION, request, Region.class), + lon); + Set<Imt> supportedImts = Metadata.commonImts(edition, region); + Set<Imt> imts = paramMap.containsKey(IMT.toString()) + ? readValues(IMT, request, Imt.class) + : supportedImts; + OptionalDouble returnPeriod = OptionalDouble.empty(); + + /* Possibly update for deagg. */ + if (paramMap.containsKey(RETURNPERIOD.toString())) { + returnPeriod = OptionalDouble.of(readDouble(RETURNPERIOD, request)); + } + + return new RequestData( + edition, + region, + lon, + lat, + imts, + vs30, + returnPeriod); + } + + /* + * Reduce slash-delimited request. This method is shared with deagg. Deagg + * must supply a single Imt. See RequestData notes below. + */ + static RequestData buildRequest(List<String> params) { + + /* Read params as for hazard */ + double lon = Double.valueOf(params.get(2)); + double lat = Double.valueOf(params.get(3)); + Vs30 vs30 = Vs30.fromValue(Double.valueOf(params.get(5))); + Edition edition = Enum.valueOf(Edition.class, params.get(0)); + Region region = ServletUtil.checkRegion( + Enum.valueOf(Region.class, params.get(1)), + lon); + Set<Imt> supportedImts = Metadata.commonImts(edition, region); + Set<Imt> imts = (params.get(4).equalsIgnoreCase("any")) + ? supportedImts + : readValues(params.get(4), Imt.class); + OptionalDouble returnPeriod = OptionalDouble.empty(); + + /* Possibly update for deagg. */ + if (params.size() == 7) { + returnPeriod = OptionalDouble.of(Double.valueOf(params.get(6))); + } + + return new RequestData( + edition, + region, + lon, + lat, + imts, + vs30, + returnPeriod); + } + + private static class HazardTask extends TimedTask<Result> { + + final RequestData data; + + HazardTask(String url, ServletContext context, RequestData data) { + super(url, context); + this.data = data; + } + + @Override + Result calc() throws Exception { + Hazard hazard = calcHazard(data, context); + return new Result.Builder() + .requestData(data) + .url(url) + .timer(timer) + .hazard(hazard) + .build(); + } + } + + /* Also used by DeaggService */ + static Hazard calcHazard(RequestData data, ServletContext context) { + + Location loc = Location.create(data.latitude, data.longitude); + Site.Builder siteBuilder = Site.builder().location(loc).vs30(data.vs30.value()); + + @SuppressWarnings("unchecked") + LoadingCache<Model, HazardModel> modelCache = + (LoadingCache<Model, HazardModel>) context.getAttribute(MODEL_CACHE_CONTEXT_ID); + + // TODO cache calls should be using checked get(id) + + // May include trailing 'B' for 2014B + String baseYear = data.edition.name().substring(1); + + /* + * When combining (merging) Hazard, the config from the first supplied + * Hazard is used for the result. This means, for example, the exceedance + * model used for deaggregation may be different than that used to compute + * the original hazard curves. Because the CEUS exceedance model, + * NSHM_CEUS_MAX_INTENSITY, is really just a 3σ truncation model except + * close to New Madrid when fixed maximum values apply, it is okay to just + * use the WUS 3σ truncation exceedance model in the CEUS-WUS overlap zone. + * However, it is important to have the WUS result be first in the merge() + * call below. + */ + if (data.region == COUS) { + + Model wusId = Model.valueOf(WUS.name() + "_" + baseYear); + HazardModel wusModel = modelCache.getUnchecked(wusId); + Site site = siteBuilder + .basinDataProvider(wusModel.config().siteData.basinDataProvider) + .build(); + Hazard wusResult = process(wusModel, site, data.imts); + + String ceusYear = baseYear.equals("2014B") ? "2014" : baseYear; + Model ceusId = Model.valueOf(CEUS.name() + "_" + ceusYear); + HazardModel ceusModel = modelCache.getUnchecked(ceusId); + Hazard ceusResult = process(ceusModel, site, data.imts); + + return Hazard.merge(wusResult, ceusResult); + } + + String year = (baseYear.equals("2014B") && data.region == Region.CEUS) + ? "2014" : baseYear; + Model modelId = Model.valueOf(data.region.name() + "_" + year); + HazardModel model = modelCache.getUnchecked(modelId); + Site site = siteBuilder.basinDataProvider(model.config().siteData.basinDataProvider).build(); + return process(model, site, data.imts); + } + + private static Hazard process(HazardModel model, Site site, Set<Imt> imts) { + Builder configBuilder = CalcConfig.Builder.copyOf(model.config()); + configBuilder.imts(imts); + CalcConfig config = configBuilder.build(); + return HazardCalcs.hazard(model, config, site, ServletUtil.CALC_EXECUTOR); + } + + /* + * We use a single request object type for both hazard and deagg. With the + * extension of deagg to support CMS, we need medians and sigmas for other + * spectral periods, but we don't (at least not yet) want to return serialized + * deaggs across all periods. Whereas both underlying programs support + * multiple Imts, the hazard service expects a set of Imts (that may + * correspond to 'any' implying all supportedImts), but the deagg service + * expects a single Imt. To address this, we've added the deaggImt field, + * which will be derived from the singleton imt URL argument as before, but + * hazard will always be computed across the set of Imts supplied so that the + * cms at the deaggImt can be computed. Under the hood the deagg application + * provides support for all specified Imts. Also note that the presence of a + * 'return period' is used to flag deagg service requests. + */ + static final class RequestData { + + final Edition edition; + final Region region; + final double latitude; + final double longitude; + final Set<Imt> imts; + final Vs30 vs30; + final OptionalDouble returnPeriod; + + RequestData( + Edition edition, + Region region, + double longitude, + double latitude, + Set<Imt> imts, + Vs30 vs30, + OptionalDouble returnPeriod) { + + this.edition = edition; + this.region = region; + this.latitude = latitude; + this.longitude = longitude; + this.imts = imts; + this.vs30 = vs30; + this.returnPeriod = returnPeriod; + } + } + + private static final class ResponseData { + + final Edition edition; + final Region region; + final double latitude; + final double longitude; + final Imt imt; + final Vs30 vs30; + final String xlabel = "Ground Motion (g)"; + final String ylabel = "Annual Frequency of Exceedence"; + final List<Double> xvalues; + + ResponseData(RequestData request, Imt imt, List<Double> xvalues) { + this.edition = request.edition; + this.region = request.region; + this.longitude = request.longitude; + this.latitude = request.latitude; + this.imt = imt; + this.vs30 = request.vs30; + this.xvalues = xvalues; + } + } + + private static final class Response { + + final ResponseData metadata; + final List<Curve> data; + + Response(ResponseData metadata, List<Curve> data) { + this.metadata = metadata; + this.data = data; + } + } + + private static final class Curve { + + final String component; + final List<Double> yvalues; + + Curve(String component, List<Double> yvalues) { + this.component = component; + this.yvalues = yvalues; + } + } + + private static final String TOTAL_KEY = "Total"; + + private static final class Result { + + final String status = Status.SUCCESS.toString(); + final String date = ZonedDateTime.now().format(ServletUtil.DATE_FMT); + final String url; + final Object server; + final List<Response> response; + + Result(String url, Object server, List<Response> response) { + this.url = url; + this.server = server; + this.response = response; + } + + static final class Builder { + + String url; + Timer timer; + RequestData request; + + Map<Imt, Map<SourceType, XySequence>> componentMaps; + Map<Imt, XySequence> totalMap; + Map<Imt, List<Double>> xValuesLinearMap; + + Builder hazard(Hazard hazardResult) { + checkState(totalMap == null, "Hazard has already been added to this builder"); + + componentMaps = new EnumMap<>(Imt.class); + totalMap = new EnumMap<>(Imt.class); + xValuesLinearMap = new EnumMap<>(Imt.class); + + Map<Imt, Map<SourceType, XySequence>> typeTotalMaps = curvesBySource(hazardResult); + + for (Imt imt : hazardResult.curves().keySet()) { + + // total curve + hazardResult.curves().get(imt).addToMap(imt, totalMap); + + // component curves + Map<SourceType, XySequence> typeTotalMap = typeTotalMaps.get(imt); + Map<SourceType, XySequence> componentMap = componentMaps.get(imt); + if (componentMap == null) { + componentMap = new EnumMap<>(SourceType.class); + componentMaps.put(imt, componentMap); + } + + for (SourceType type : typeTotalMap.keySet()) { + typeTotalMap.get(type).addToMap(type, componentMap); + } + + xValuesLinearMap.put( + imt, + hazardResult.config().hazard.modelCurve(imt).xValues()); + } + return this; + } + + Builder url(String url) { + this.url = url; + return this; + } + + Builder timer(Timer timer) { + this.timer = timer; + return this; + } + + Builder requestData(RequestData request) { + this.request = request; + return this; + } + + Result build() { + ImmutableList.Builder<Response> responseListBuilder = ImmutableList.builder(); + + for (Imt imt : totalMap.keySet()) { + + ResponseData responseData = new ResponseData( + request, + imt, + xValuesLinearMap.get(imt)); + + ImmutableList.Builder<Curve> curveListBuilder = ImmutableList.builder(); + + // total curve + Curve totalCurve = new Curve( + TOTAL_KEY, + totalMap.get(imt).yValues()); + curveListBuilder.add(totalCurve); + + // component curves + Map<SourceType, XySequence> typeMap = componentMaps.get(imt); + for (SourceType type : typeMap.keySet()) { + Curve curve = new Curve( + type.toString(), + typeMap.get(type).yValues()); + curveListBuilder.add(curve); + } + + Response response = new Response(responseData, curveListBuilder.build()); + responseListBuilder.add(response); + } + + List<Response> responseList = responseListBuilder.build(); + Object server = Metadata.serverData(ServletUtil.THREAD_COUNT, timer); + + return new Result(url, server, responseList); + } + } + } +} diff --git a/src/gov/usgs/earthquake/nshmp/www/HazardService2.java b/src/gov/usgs/earthquake/nshmp/www/HazardService2.java new file mode 100644 index 000000000..c82de2c23 --- /dev/null +++ b/src/gov/usgs/earthquake/nshmp/www/HazardService2.java @@ -0,0 +1,383 @@ +package gov.usgs.earthquake.nshmp.www; + +import static com.google.common.base.Preconditions.checkState; +import static gov.usgs.earthquake.nshmp.calc.HazardExport.curvesBySource; +import static gov.usgs.earthquake.nshmp.www.ServletUtil.GSON; +import static gov.usgs.earthquake.nshmp.www.ServletUtil.MODEL_CACHE_CONTEXT_ID; +import static gov.usgs.earthquake.nshmp.www.ServletUtil.emptyRequest; +import static gov.usgs.earthquake.nshmp.www.Util.readDouble; +import static gov.usgs.earthquake.nshmp.www.Util.readValue; +import static gov.usgs.earthquake.nshmp.www.Util.Key.LATITUDE; +import static gov.usgs.earthquake.nshmp.www.Util.Key.LONGITUDE; +import static gov.usgs.earthquake.nshmp.www.Util.Key.MODEL; +import static gov.usgs.earthquake.nshmp.www.Util.Key.VS30; + +import java.io.IOException; +import java.time.ZonedDateTime; +import java.util.EnumMap; +import java.util.List; +import java.util.Map; + +import javax.servlet.ServletContext; +import javax.servlet.ServletException; +import javax.servlet.annotation.WebServlet; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +import com.google.common.cache.LoadingCache; +import com.google.common.collect.ImmutableList; + +import gov.usgs.earthquake.nshmp.calc.CalcConfig; +import gov.usgs.earthquake.nshmp.calc.CalcConfig.Builder; +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.Vs30; +import gov.usgs.earthquake.nshmp.data.XySequence; +import gov.usgs.earthquake.nshmp.eq.model.HazardModel; +import gov.usgs.earthquake.nshmp.eq.model.SourceType; +import gov.usgs.earthquake.nshmp.geo.Location; +import gov.usgs.earthquake.nshmp.gmm.Imt; +import gov.usgs.earthquake.nshmp.internal.Parsing; +import gov.usgs.earthquake.nshmp.internal.Parsing.Delimiter; +import gov.usgs.earthquake.nshmp.www.ServletUtil.TimedTask; +import gov.usgs.earthquake.nshmp.www.ServletUtil.Timer; +import gov.usgs.earthquake.nshmp.www.SourceServices.SourceModel; +import gov.usgs.earthquake.nshmp.www.meta.Metadata; +import gov.usgs.earthquake.nshmp.www.meta.Status; + +/** + * Probabilisitic seismic hazard calculation service. + * + * @author Peter Powers + */ +@SuppressWarnings("unused") +@WebServlet( + name = "Hazard Service 2", + description = "USGS NSHMP Hazard Curve Calculator", + urlPatterns = { + "/haz", + "/haz/*" }) +public final class HazardService2 extends NshmpServlet { + + /* + * Developer notes: + * + * Updated hazard service that identifies models directly, instead of + * editions, to simplify model comparison. Models are defined by a region and + * year. This service computes hazard for all supported IMTs and a single + * vs30. + * + * As with the existing hazard service, calculations are designed to leverage + * all available processors by default, distributing work using the + * ServletUtil.CALC_EXECUTOR. This can create problems in a servlet + * environment, however, because Tomcat does not support a single threaded + * request queue where requests are processed as they are received with the + * next task starting only once the prior has finished. One can really only + * limit the maximum number of simultaneous requests. When multiple requests + * are received in a short span, Tomcat will attempt to run hazard or deagg + * calculations simultaneously. The net effect is that there can be out of + * memory problems as too many results are retained, and multiple requests do + * not return until all are finished. + * + * To address this, requests are submitted as tasks to the single-threaded + * ServletUtil.TASK_EXECUTOR and are processed one-at-a-time in the order + * received. + * + * TODO Add support for multi model requests in order to combine models per + * the original hazard service. + */ + + private LoadingCache<Model, HazardModel> modelCache; + + private static final String USAGE = SourceServices.GSON.toJson( + new SourceServices.ResponseData()); + + @Override + @SuppressWarnings("unchecked") + public void init() { + ServletContext context = getServletConfig().getServletContext(); + Object modelCache = context.getAttribute(MODEL_CACHE_CONTEXT_ID); + this.modelCache = (LoadingCache<Model, HazardModel>) modelCache; + } + + @Override + protected void doGet( + HttpServletRequest request, + HttpServletResponse response) + throws ServletException, IOException { + + UrlHelper urlHelper = urlHelper(request, response); + + if (emptyRequest(request)) { + urlHelper.writeResponse(USAGE); + return; + } + + try { + RequestData requestData = buildRequestData(request); + + /* Submit as task to job executor */ + Hazard2Task task = new Hazard2Task(urlHelper.url, getServletContext(), requestData); + Result result = ServletUtil.TASK_EXECUTOR.submit(task).get(); + GSON.toJson(result, response.getWriter()); + + } catch (Exception e) { + String message = Metadata.errorMessage(urlHelper.url, e, false); + response.getWriter().print(message); + getServletContext().log(urlHelper.url, e); + } + } + + /* Reduce query string key-value pairs. */ + static RequestData buildRequestData(HttpServletRequest request) { + + try { + + Model model; + double lon; + double lat; + Vs30 vs30; + + if (request.getQueryString() != null) { + /* process query '?' request */ + model = readValue(MODEL, request, Model.class); + lon = readDouble(LONGITUDE, request); + lat = readDouble(LATITUDE, request); + vs30 = Vs30.fromValue(readDouble(VS30, request)); + + } else { + /* process slash-delimited request */ + List<String> params = Parsing.splitToList( + request.getPathInfo(), + Delimiter.SLASH); + model = Model.valueOf(params.get(0)); + lon = Double.valueOf(params.get(1)); + lat = Double.valueOf(params.get(2)); + vs30 = Vs30.fromValue(Double.valueOf(params.get(3))); + } + + return new RequestData( + model, + lon, + lat, + vs30); + + } catch (Exception e) { + throw new IllegalArgumentException("Error parsing request URL", e); + } + } + + private class Hazard2Task extends TimedTask<Result> { + + final RequestData data; + + Hazard2Task(String url, ServletContext context, RequestData data) { + super(url, context); + this.data = data; + } + + @Override + Result calc() throws Exception { + Hazard hazard = calcHazard(data, context); + return new Result.Builder() + .requestData(data) + .url(url) + .timer(timer) + .hazard(hazard) + .build(); + } + } + + Hazard calcHazard(RequestData data, ServletContext context) { + Location loc = Location.create(data.latitude, data.longitude); + HazardModel model = modelCache.getUnchecked(data.model); + Builder configBuilder = CalcConfig.Builder.copyOf(model.config()); + configBuilder.imts(data.model.imts); + CalcConfig config = configBuilder.build(); + + Site site = Site.builder() + .basinDataProvider(config.siteData.basinDataProvider) + .location(loc) + .vs30(data.vs30.value()) + .build(); + + return HazardCalcs.hazard(model, config, site, ServletUtil.CALC_EXECUTOR); + } + + static final class RequestData { + + final Model model; + final double latitude; + final double longitude; + final Vs30 vs30; + + RequestData( + Model model, + double longitude, + double latitude, + Vs30 vs30) { + + this.model = model; + this.latitude = latitude; + this.longitude = longitude; + this.vs30 = vs30; + } + } + + private static final class ResponseData { + + final SourceModel model; + final double latitude; + final double longitude; + final Imt imt; + final Vs30 vs30; + final String xlabel = "Ground Motion (g)"; + final String ylabel = "Annual Frequency of Exceedence"; + final List<Double> xvalues; + + ResponseData(RequestData request, Imt imt, List<Double> xvalues) { + this.model = new SourceModel(request.model); + this.latitude = request.latitude; + this.longitude = request.longitude; + this.imt = imt; + this.vs30 = request.vs30; + this.xvalues = xvalues; + } + } + + private static final class Response { + + final ResponseData metadata; + final List<Curve> data; + + Response(ResponseData metadata, List<Curve> data) { + this.metadata = metadata; + this.data = data; + } + } + + private static final class Curve { + + final String component; + final List<Double> yvalues; + + Curve(String component, List<Double> yvalues) { + this.component = component; + this.yvalues = yvalues; + } + } + + private static final String TOTAL_KEY = "Total"; + + private static final class Result { + + final String status = Status.SUCCESS.toString(); + final String date = ZonedDateTime.now().format(ServletUtil.DATE_FMT); + final String url; + final Object server; + final List<Response> response; + + Result(String url, Object server, List<Response> response) { + this.url = url; + this.server = server; + this.response = response; + } + + static final class Builder { + + String url; + Timer timer; + RequestData request; + + Map<Imt, Map<SourceType, XySequence>> componentMaps; + Map<Imt, XySequence> totalMap; + Map<Imt, List<Double>> xValuesLinearMap; + + Builder hazard(Hazard hazardResult) { + checkState(totalMap == null, "Hazard has already been added to this builder"); + + componentMaps = new EnumMap<>(Imt.class); + totalMap = new EnumMap<>(Imt.class); + xValuesLinearMap = new EnumMap<>(Imt.class); + + Map<Imt, Map<SourceType, XySequence>> typeTotalMaps = curvesBySource(hazardResult); + + for (Imt imt : hazardResult.curves().keySet()) { + + // total curve + hazardResult.curves().get(imt).addToMap(imt, totalMap); + + // component curves + Map<SourceType, XySequence> typeTotalMap = typeTotalMaps.get(imt); + Map<SourceType, XySequence> componentMap = componentMaps.get(imt); + if (componentMap == null) { + componentMap = new EnumMap<>(SourceType.class); + componentMaps.put(imt, componentMap); + } + + for (SourceType type : typeTotalMap.keySet()) { + typeTotalMap.get(type).addToMap(type, componentMap); + } + + xValuesLinearMap.put( + imt, + hazardResult.config().hazard.modelCurve(imt).xValues()); + } + return this; + } + + Builder url(String url) { + this.url = url; + return this; + } + + Builder timer(Timer timer) { + this.timer = timer; + return this; + } + + Builder requestData(RequestData request) { + this.request = request; + return this; + } + + Result build() { + ImmutableList.Builder<Response> responseListBuilder = ImmutableList.builder(); + + for (Imt imt : totalMap.keySet()) { + + ResponseData responseData = new ResponseData( + request, + imt, + xValuesLinearMap.get(imt)); + + ImmutableList.Builder<Curve> curveListBuilder = ImmutableList.builder(); + + // total curve + Curve totalCurve = new Curve( + TOTAL_KEY, + totalMap.get(imt).yValues()); + curveListBuilder.add(totalCurve); + + // component curves + Map<SourceType, XySequence> typeMap = componentMaps.get(imt); + for (SourceType type : typeMap.keySet()) { + Curve curve = new Curve( + type.toString(), + typeMap.get(type).yValues()); + curveListBuilder.add(curve); + } + + Response response = new Response(responseData, curveListBuilder.build()); + responseListBuilder.add(response); + } + + List<Response> responseList = responseListBuilder.build(); + Object server = Metadata.serverData(ServletUtil.THREAD_COUNT, timer); + + return new Result(url, server, responseList); + } + } + } +} diff --git a/src/gov/usgs/earthquake/nshmp/www/Model.java b/src/gov/usgs/earthquake/nshmp/www/Model.java new file mode 100644 index 000000000..489c08c5b --- /dev/null +++ b/src/gov/usgs/earthquake/nshmp/www/Model.java @@ -0,0 +1,85 @@ +package gov.usgs.earthquake.nshmp.www; + +import static gov.usgs.earthquake.nshmp.calc.Vs30.*; +import static gov.usgs.earthquake.nshmp.gmm.Imt.*; +import static gov.usgs.earthquake.nshmp.www.meta.Region.*; + +import java.nio.file.Paths; +import java.util.EnumSet; +import java.util.Set; + +import com.google.common.collect.ImmutableList; +import com.google.common.collect.Sets; + +import gov.usgs.earthquake.nshmp.calc.Vs30; +import gov.usgs.earthquake.nshmp.gmm.Imt; +import gov.usgs.earthquake.nshmp.internal.Parsing; +import gov.usgs.earthquake.nshmp.internal.Parsing.Delimiter; +import gov.usgs.earthquake.nshmp.www.meta.Region; + +enum Model { + + AK_2007( + EnumSet.of(PGA, SA0P1, SA0P2, SA0P3, SA0P5, SA1P0, SA2P0), + EnumSet.of(VS_760)), + + CEUS_2008( + EnumSet.of(PGA, SA0P1, SA0P2, SA0P3, SA0P5, SA1P0, SA2P0), + EnumSet.of(VS_760, VS_2000)), + + WUS_2008( + EnumSet.of(PGA, SA0P1, SA0P2, SA0P3, SA0P5, SA0P75, SA1P0, SA2P0, SA3P0), + EnumSet.of(VS_1150, VS_760, VS_537, VS_360, VS_259, VS_180)), + + CEUS_2014( + EnumSet.of(PGA, SA0P1, SA0P2, SA0P3, SA0P5, SA1P0, SA2P0), + EnumSet.of(VS_760, VS_2000)), + + WUS_2014( + EnumSet.of(PGA, SA0P1, SA0P2, SA0P3, SA0P5, SA0P75, SA1P0, SA2P0, SA3P0), + EnumSet.of(VS_1150, VS_760, VS_537, VS_360, VS_259, VS_180)), + + WUS_2014B( + EnumSet.of(PGA, SA0P1, SA0P2, SA0P3, SA0P5, SA0P75, SA1P0, SA2P0, SA3P0, SA4P0, SA5P0), + EnumSet.of(VS_1150, VS_760, VS_537, VS_360, VS_259, VS_180)), + + CEUS_2018( + EnumSet.of(PGA, SA0P1, SA0P2, SA0P3, SA0P5, SA0P75, SA1P0, SA2P0, SA3P0, SA4P0, SA5P0), + EnumSet.of(VS_1150, VS_760, VS_537, VS_360, VS_259, VS_180)), + + WUS_2018( + EnumSet.of(PGA, SA0P1, SA0P2, SA0P3, SA0P5, SA0P75, SA1P0, SA2P0, SA3P0, SA4P0, SA5P0), + EnumSet.of(VS_1150, VS_760, VS_537, VS_360, VS_259, VS_180)), + + HI_2020( + EnumSet.of(PGA, SA0P1, SA0P2, SA0P3, SA0P5, SA0P75, SA1P0, SA2P0, SA3P0, SA5P0), + EnumSet.of(VS_1150, VS_760, VS_537, VS_360, VS_259, VS_180)); + + private static final String MODEL_DIR = "models"; + + final Set<Imt> imts; + final Set<Vs30> vs30s; + + final String path; + final String name; + final Region region; + final String year; + + private Model(Set<Imt> imts, Set<Vs30> vs30s) { + this.imts = Sets.immutableEnumSet(imts); + this.vs30s = Sets.immutableEnumSet(vs30s); + region = deriveRegion(name()); + year = name().substring(name().lastIndexOf('_') + 1); + path = Paths.get("/", MODEL_DIR) + .resolve(region.name().toLowerCase()) + .resolve(year.toLowerCase()) + .toString(); + name = Parsing.join( + ImmutableList.of(year, region.label, "Hazard Model"), + Delimiter.SPACE); + } + + private static Region deriveRegion(String s) { + return s.startsWith("AK") ? AK : s.startsWith("WUS") ? WUS : s.startsWith("HI") ? HI : CEUS; + } +} diff --git a/src/gov/usgs/earthquake/nshmp/www/NshmpServlet.java b/src/gov/usgs/earthquake/nshmp/www/NshmpServlet.java new file mode 100644 index 000000000..95eb5a92d --- /dev/null +++ b/src/gov/usgs/earthquake/nshmp/www/NshmpServlet.java @@ -0,0 +1,108 @@ +package gov.usgs.earthquake.nshmp.www; + +import java.io.IOException; + +import javax.servlet.ServletException; +import javax.servlet.http.HttpServlet; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +/** + * Custom NSHMP servlet implementation and URL helper class. + * + * <p>All nshmp-haz-ws services should extend this class. This class sets custom + * response headers and provides a helper class to ensure serialized response + * URLs propagate the correct host and protocol from requests on USGS servers + * and caches that may have been forwarded. + * + * <p>Class provides one convenience method, + * {@code urlHelper.writeResponse(String)}, to write a servlet response wherein + * any URL strings may be formatted with the correct protocol and host. Such URL + * strings should start with: + * + * "%s://%s/service-name/..." + * + * @author Peter Powers + */ +public abstract class NshmpServlet extends HttpServlet { + + @Override + protected void service( + HttpServletRequest request, + HttpServletResponse response) + throws ServletException, IOException { + + /* + * Set CORS headers and content type. + * + * Because nshmp-haz-ws services may be called by both the USGS website, + * other websites, and directly by 3rd party applications, reponses + * generated by direct requests will not have the necessary header + * information that would be required by security protocols for web + * requests. This means that any initial direct request will pollute + * intermediate caches with a response that a browser will deem invalid. + */ + response.setContentType("application/json; charset=UTF-8"); + response.setHeader("Access-Control-Allow-Origin", "*"); + response.setHeader("Access-Control-Allow-Methods", "*"); + response.setHeader("Access-Control-Allow-Headers", "accept,origin,authorization,content-type"); + + super.service(request, response); + } + + public static UrlHelper urlHelper(HttpServletRequest request, HttpServletResponse response) + throws IOException { + return new UrlHelper(request, response); + } + + public static class UrlHelper { + + private final HttpServletResponse response; + private final String host; + private final String protocol; + public final String url; + + UrlHelper(HttpServletRequest request, HttpServletResponse response) { + /* + * Check custom header for a forwarded protocol so generated links can use + * the same protocol and not cause mixed content errors. + */ + String sourceHost = request.getHeader("x-source-host"); + String host = sourceHost == null ? request.getServerName() : sourceHost; + String sourceProtocol = request.getHeader("x-source-proto"); + String protocol = sourceProtocol == null + ? request.getHeader("X-FORWARDED-PROTO") + : sourceProtocol; + if (protocol == null) { + /* Not a forwarded request. Honor reported protocol and port. */ + protocol = request.getScheme(); + host += ":" + request.getServerPort(); + } + + /* + * For convenience, store a url field with the (possibly updated) request + * protocol and + */ + String url = String.format("%s://%s%s", protocol, host, request.getPathInfo()); + String query = request.getQueryString(); + if (query != null) url = String.format("%s?%s", url, query); + + this.response = response; + this.host = host; + this.protocol = protocol; + this.url = url; + } + + /** + * Convenience method to update a string response with the correct protocol + * and host in URLs. URL strings should start with: + * + * "%s://%s/service-name/..." + */ + public void writeResponse(String usage) throws IOException { + // TODO had to add duplicate fields to handle haz and g syntax strings + response.getWriter().printf(usage, protocol, host, protocol, host); + } + } + +} diff --git a/src/gov/usgs/earthquake/nshmp/www/RateService.java b/src/gov/usgs/earthquake/nshmp/www/RateService.java new file mode 100644 index 000000000..0fa5420ad --- /dev/null +++ b/src/gov/usgs/earthquake/nshmp/www/RateService.java @@ -0,0 +1,424 @@ +package gov.usgs.earthquake.nshmp.www; + +import static com.google.common.base.Preconditions.checkState; +import static gov.usgs.earthquake.nshmp.calc.ValueFormat.ANNUAL_RATE; +import static gov.usgs.earthquake.nshmp.calc.ValueFormat.POISSON_PROBABILITY; +import static gov.usgs.earthquake.nshmp.www.ServletUtil.GSON; +import static gov.usgs.earthquake.nshmp.www.ServletUtil.MODEL_CACHE_CONTEXT_ID; +import static gov.usgs.earthquake.nshmp.www.ServletUtil.emptyRequest; +import static gov.usgs.earthquake.nshmp.www.Util.readDouble; +import static gov.usgs.earthquake.nshmp.www.Util.readValue; +import static gov.usgs.earthquake.nshmp.www.Util.Key.DISTANCE; +import static gov.usgs.earthquake.nshmp.www.Util.Key.EDITION; +import static gov.usgs.earthquake.nshmp.www.Util.Key.LATITUDE; +import static gov.usgs.earthquake.nshmp.www.Util.Key.LONGITUDE; +import static gov.usgs.earthquake.nshmp.www.Util.Key.REGION; +import static gov.usgs.earthquake.nshmp.www.Util.Key.TIMESPAN; +import static gov.usgs.earthquake.nshmp.www.meta.Region.CEUS; +import static gov.usgs.earthquake.nshmp.www.meta.Region.WUS; + +import java.io.IOException; +import java.time.ZonedDateTime; +import java.util.List; +import java.util.Map.Entry; +import java.util.Optional; +import java.util.concurrent.Callable; +import java.util.concurrent.ExecutionException; + +import javax.servlet.ServletContext; +import javax.servlet.ServletException; +import javax.servlet.annotation.WebServlet; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +import com.google.common.cache.LoadingCache; +import com.google.common.collect.ImmutableList; +import com.google.common.util.concurrent.ListenableFuture; + +import gov.usgs.earthquake.nshmp.calc.CalcConfig; +import gov.usgs.earthquake.nshmp.calc.CalcConfig.Builder; +import gov.usgs.earthquake.nshmp.calc.EqRate; +import gov.usgs.earthquake.nshmp.calc.Site; +import gov.usgs.earthquake.nshmp.calc.ValueFormat; +import gov.usgs.earthquake.nshmp.data.XySequence; +import gov.usgs.earthquake.nshmp.eq.model.HazardModel; +import gov.usgs.earthquake.nshmp.eq.model.SourceType; +import gov.usgs.earthquake.nshmp.geo.Location; +import gov.usgs.earthquake.nshmp.internal.Parsing; +import gov.usgs.earthquake.nshmp.internal.Parsing.Delimiter; +import gov.usgs.earthquake.nshmp.www.ServletUtil.Timer; +import gov.usgs.earthquake.nshmp.www.meta.Edition; +import gov.usgs.earthquake.nshmp.www.meta.Metadata; +import gov.usgs.earthquake.nshmp.www.meta.Region; +import gov.usgs.earthquake.nshmp.www.meta.Status; + +/** + * Earthquake probability and rate calculation service. + * + * @author Peter Powers + */ +@SuppressWarnings("unused") +@WebServlet( + name = "Earthquake Probability & Rate Service", + description = "USGS NSHMP Earthquake Probability & Rate Calculator", + urlPatterns = { + "/rate", + "/rate/*", + "/probability", + "/probability/*" }) +public final class RateService extends NshmpServlet { + + /* + * 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. + */ + + @Override + protected void doGet( + HttpServletRequest request, + HttpServletResponse response) + throws ServletException, IOException { + + Timer timer = ServletUtil.timer(); + + UrlHelper urlHelper = urlHelper(request, response); + String query = request.getQueryString(); + String pathInfo = request.getPathInfo(); + String service = request.getServletPath(); + + ValueFormat format = service.equals("/rate") ? ANNUAL_RATE : POISSON_PROBABILITY; + String usage = (format == ANNUAL_RATE) ? Metadata.RATE_USAGE : Metadata.PROBABILITY_USAGE; + int paramCount = (format == ANNUAL_RATE) ? 5 : 6; + + if (emptyRequest(request)) { + urlHelper.writeResponse(usage); + return; + } + + RequestData requestData; + try { + if (query != null) { + /* process query '?' request */ + requestData = buildRequest(request, format); + } else { + /* process slash-delimited request */ + List<String> params = Parsing.splitToList(pathInfo, Delimiter.SLASH); + if (params.size() < paramCount) { + urlHelper.writeResponse(usage); + return; + } + requestData = buildRequest(params, format); + } + + EqRate rates = calc(requestData, getServletContext()); + Result result = new Result.Builder() + .requestData(requestData) + .url(urlHelper.url) + .timer(timer) + .rates(rates) + .build(); + String resultStr = GSON.toJson(result); + response.getWriter().print(resultStr); + + } catch (Exception e) { + String message = Metadata.errorMessage(urlHelper.url, e, false); + response.getWriter().print(message); + getServletContext().log(urlHelper.url, e); + } + } + + /* Reduce query string key-value pairs */ + private RequestData buildRequest(HttpServletRequest request, ValueFormat format) { + + Optional<Double> timespan = (format == POISSON_PROBABILITY) + ? Optional.of(readDouble(TIMESPAN, request)) : Optional.<Double> empty(); + + return new RequestData( + readValue(EDITION, request, Edition.class), + readValue(REGION, request, Region.class), + readDouble(LONGITUDE, request), + readDouble(LATITUDE, request), + readDouble(DISTANCE, request), + timespan); + } + + /* Reduce slash-delimited request */ + private RequestData buildRequest(List<String> params, ValueFormat format) { + + Optional<Double> timespan = (format == POISSON_PROBABILITY) + ? Optional.of(Double.valueOf(params.get(5))) : Optional.<Double> empty(); + + return new RequestData( + Enum.valueOf(Edition.class, params.get(0)), + Enum.valueOf(Region.class, params.get(1)), + Double.valueOf(params.get(2)), + Double.valueOf(params.get(3)), + Double.valueOf(params.get(4)), + timespan); + } + + /* + * TODO delete if not needed + * + * Currently unused, however, will be used if it makes sense to submit jobs to + * TASK_EXECUTOR. + */ + private static class RateTask implements Callable<Result> { + + final String url; + final RequestData data; + final ServletContext context; + final Timer timer; + + RateTask(String url, RequestData data, ServletContext context) { + this.url = url; + this.data = data; + this.context = context; + this.timer = ServletUtil.timer(); + } + + @Override + public Result call() throws Exception { + EqRate rates = calc(data, context); + return new Result.Builder() + .requestData(data) + .url(url) + .timer(timer) + .rates(rates) + .build(); + } + } + + private static EqRate calc(RequestData data, ServletContext context) + throws InterruptedException, ExecutionException { + + Location location = Location.create(data.latitude, data.longitude); + Site site = Site.builder().location(location).build(); + + double distance = data.distance; + + @SuppressWarnings("unchecked") + LoadingCache<Model, HazardModel> modelCache = + (LoadingCache<Model, HazardModel>) context.getAttribute(MODEL_CACHE_CONTEXT_ID); + + EqRate rates; + + /* + * 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. + */ + Optional<Double> emptyTimespan = Optional.<Double> empty(); + + // May include trailing 'B' for 2014B + String baseYear = data.edition.name().substring(1); + + if (data.region == Region.COUS) { + + Model wusId = Model.valueOf(WUS.name() + "_" + baseYear); + HazardModel wusModel = modelCache.get(wusId); + ListenableFuture<EqRate> wusRates = process(wusModel, site, distance, emptyTimespan); + + String ceusYear = baseYear.equals("2014B") ? "2014" : baseYear; + Model ceusId = Model.valueOf(CEUS.name() + "_" + ceusYear); + HazardModel ceusModel = modelCache.get(ceusId); + ListenableFuture<EqRate> ceusRates = process(ceusModel, site, distance, emptyTimespan); + + rates = EqRate.combine(wusRates.get(), ceusRates.get()); + + } else { + + String year = (baseYear.equals("2014B") && data.region == Region.CEUS) + ? "2014" : baseYear; + Model modelId = Model.valueOf(data.region.name() + "_" + year); + + HazardModel model = modelCache.get(modelId); + rates = process(model, site, distance, emptyTimespan).get(); + } + + if (data.timespan.isPresent()) { + rates = EqRate.toCumulative(rates); + rates = EqRate.toPoissonProbability(rates, data.timespan.get()); + } + return rates; + } + + private static ListenableFuture<EqRate> process( + HazardModel model, + Site site, + double distance, + Optional<Double> timespan) { + + Builder configBuilder = CalcConfig.Builder + .copyOf(model.config()) + .distance(distance); + if (timespan.isPresent()) { + /* Also sets value format to Poisson probability. */ + configBuilder.timespan(timespan.get()); + } + CalcConfig config = configBuilder.build(); + Callable<EqRate> task = EqRate.callable(model, config, site); + return ServletUtil.CALC_EXECUTOR.submit(task); + } + + static final class RequestData { + + final Edition edition; + final Region region; + final double latitude; + final double longitude; + final double distance; + final Optional<Double> timespan; + + RequestData( + Edition edition, + Region region, + double longitude, + double latitude, + double distance, + Optional<Double> timespan) { + + this.edition = edition; + this.region = region; + this.latitude = latitude; + this.longitude = longitude; + this.distance = distance; + this.timespan = timespan; + } + } + + private static final class ResponseData { + + final Edition edition; + final Region region; + final double latitude; + final double longitude; + final double distance; + final Double timespan; + + final String xlabel = "Magnitude (Mw)"; + final String ylabel; + + ResponseData(RequestData request) { + boolean isProbability = request.timespan.isPresent(); + this.edition = request.edition; + this.region = request.region; + 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); + } + } + + private static final class Response { + + final ResponseData metadata; + final List<Sequence> data; + + Response(ResponseData metadata, List<Sequence> data) { + this.metadata = metadata; + this.data = data; + } + } + + /* + * TODO 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; + } + } + + private static final String TOTAL_KEY = "Total"; + + private static final class Result { + + final String status = Status.SUCCESS.toString(); + final String date = ZonedDateTime.now().format(ServletUtil.DATE_FMT); + final String url; + final Object server; + final Response response; + + Result(String url, Object server, Response response) { + this.url = url; + this.server = server; + this.response = response; + } + + static final class Builder { + + String url; + Timer timer; + RequestData request; + EqRate rates; + + Builder rates(EqRate rates) { + checkState(this.rates == null, "Rate data has already been added to this builder"); + this.rates = rates; + return this; + } + + Builder url(String url) { + this.url = url; + return this; + } + + Builder timer(Timer timer) { + this.timer = timer; + return this; + } + + Builder requestData(RequestData request) { + this.request = request; + return this; + } + + Result build() { + + ImmutableList.Builder<Sequence> sequenceListBuilder = ImmutableList.builder(); + + /* Total mfd. */ + XySequence total = (!rates.totalMfd.isClear()) ? rates.totalMfd.trim() : rates.totalMfd; + Sequence totalOut = new Sequence( + TOTAL_KEY, + total.xValues(), + total.yValues()); + sequenceListBuilder.add(totalOut); + + /* Source type mfds. */ + for (Entry<SourceType, XySequence> entry : rates.typeMfds.entrySet()) { + XySequence type = entry.getValue(); + if (type.isClear()) { + continue; + } + type = type.trim(); + Sequence typeOut = new Sequence( + entry.getKey().toString(), + type.xValues(), + type.yValues()); + sequenceListBuilder.add(typeOut); + } + + ResponseData responseData = new ResponseData(request); + Object server = Metadata.serverData(ServletUtil.THREAD_COUNT, timer); + Response response = new Response(responseData, sequenceListBuilder.build()); + + return new Result(url, server, response); + } + } + } +} diff --git a/src/gov/usgs/earthquake/nshmp/www/ServletUtil.java b/src/gov/usgs/earthquake/nshmp/www/ServletUtil.java new file mode 100644 index 000000000..0ebdaa479 --- /dev/null +++ b/src/gov/usgs/earthquake/nshmp/www/ServletUtil.java @@ -0,0 +1,329 @@ +package gov.usgs.earthquake.nshmp.www; + +import static com.google.common.base.Strings.isNullOrEmpty; +import static gov.usgs.earthquake.nshmp.www.meta.Region.CEUS; +import static gov.usgs.earthquake.nshmp.www.meta.Region.COUS; +import static gov.usgs.earthquake.nshmp.www.meta.Region.WUS; +import static java.lang.Runtime.getRuntime; + +import java.net.URI; +import java.net.URL; +import java.nio.file.FileSystem; +import java.nio.file.FileSystemNotFoundException; +import java.nio.file.FileSystems; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.time.format.DateTimeFormatter; +import java.util.HashMap; +import java.util.Map; +import java.util.concurrent.Callable; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.stream.Stream; + +import javax.servlet.ServletContext; +import javax.servlet.ServletContextEvent; +import javax.servlet.ServletContextListener; +import javax.servlet.annotation.WebListener; +import javax.servlet.http.HttpServletRequest; + +import com.google.common.base.Stopwatch; +import com.google.common.cache.CacheBuilder; +import com.google.common.cache.CacheLoader; +import com.google.common.cache.LoadingCache; +import com.google.common.util.concurrent.ListeningExecutorService; +import com.google.common.util.concurrent.MoreExecutors; +import com.google.gson.Gson; +import com.google.gson.GsonBuilder; + +import gov.usgs.earthquake.nshmp.calc.Site; +import gov.usgs.earthquake.nshmp.calc.ValueFormat; +import gov.usgs.earthquake.nshmp.calc.Vs30; +import gov.usgs.earthquake.nshmp.eq.model.HazardModel; +import gov.usgs.earthquake.nshmp.gmm.Imt; +import gov.usgs.earthquake.nshmp.www.meta.Edition; +import gov.usgs.earthquake.nshmp.www.meta.ParamType; +import gov.usgs.earthquake.nshmp.www.meta.Region; +import gov.usgs.earthquake.nshmp.www.meta.Util; + +/** + * Servlet utility objects and methods. + * + * @author Peter Powers + */ +@SuppressWarnings("javadoc") +@WebListener +public class ServletUtil implements ServletContextListener { + + /* + * Some shared resources may be accessed statically, others, such as models, + * depend on a context-param and may be accessed as context attributes. + */ + + public static final DateTimeFormatter DATE_FMT = DateTimeFormatter.ofPattern( + "yyyy-MM-dd'T'HH:mm:ssXXX"); + + static final ListeningExecutorService CALC_EXECUTOR; + static final ExecutorService TASK_EXECUTOR; + + static final int THREAD_COUNT; + + public static final Gson GSON; + + static final String MODEL_CACHE_CONTEXT_ID = "model.cache"; + + static Model[] INSTALLED_MODELS; + + private static boolean throttleIp = true; + private static final String THROTTLE_IP_KEY = "throttleIp"; + + static { + /* TODO modified for deagg-epsilon branch; should be context var */ + THREAD_COUNT = getRuntime().availableProcessors(); + CALC_EXECUTOR = MoreExecutors.listeningDecorator(Executors.newFixedThreadPool(THREAD_COUNT)); + TASK_EXECUTOR = Executors.newSingleThreadExecutor(); + GSON = new GsonBuilder() + .registerTypeAdapter(Edition.class, new Util.EnumSerializer<Edition>()) + .registerTypeAdapter(Region.class, new Util.EnumSerializer<Region>()) + .registerTypeAdapter(Imt.class, new Util.EnumSerializer<Imt>()) + .registerTypeAdapter(Vs30.class, new Util.EnumSerializer<Vs30>()) + .registerTypeAdapter(ValueFormat.class, new Util.EnumSerializer<ValueFormat>()) + .registerTypeAdapter(Double.class, new Util.DoubleSerializer()) + .registerTypeAdapter(ParamType.class, new Util.ParamTypeSerializer()) + .registerTypeAdapter(Site.class, new Util.SiteSerializer()) + .disableHtmlEscaping() + .serializeNulls() + .setPrettyPrinting() + .create(); + } + + @Override + public void contextDestroyed(ServletContextEvent e) { + CALC_EXECUTOR.shutdown(); + TASK_EXECUTOR.shutdown(); + } + + @Override + public void contextInitialized(ServletContextEvent e) { + + final ServletContext context = e.getServletContext(); + + String throttle = System.getProperty(THROTTLE_IP_KEY); + throttleIp = throttle != null ? Boolean.parseBoolean(throttle) : throttleIp; + + INSTALLED_MODELS = Stream.of(Model.values()) + .filter(model -> { + Path path = Paths.get(context.getRealPath(model.path)); + return Files.isDirectory(path); + }).toArray(Model[]::new); + + final LoadingCache<Model, HazardModel> modelCache = CacheBuilder.newBuilder().build( + new CacheLoader<Model, HazardModel>() { + @Override + public HazardModel load(Model model) { + return loadModel(context, model); + } + }); + context.setAttribute(MODEL_CACHE_CONTEXT_ID, modelCache); + } + + private static HazardModel loadModel(ServletContext context, Model model) { + Path path; + URL url; + URI uri; + String uriString; + String[] uriParts; + FileSystem fs; + + try { + url = context.getResource(model.path); + uri = new URI(url.toString().replace(" ", "%20")); + uriString = uri.toString(); + + /* + * When the web sevice is deployed inside a WAR file (and not unpacked by + * the servlet container) model resources will not exist on disk as + * otherwise expected. In this case, load the resources directly out of + * the WAR file as well. This is slower, but with the preload option + * enabled it may be less of an issue if the models are already in memory. + */ + + if (uriString.indexOf("!") != -1) { + uriParts = uri.toString().split("!"); + + try { + fs = FileSystems.getFileSystem( + URI.create(uriParts[0])); + } catch (FileSystemNotFoundException fnx) { + fs = FileSystems.newFileSystem( + URI.create(uriParts[0]), + new HashMap<String, String>()); + } + + path = fs.getPath(uriParts[1].replaceAll("%20", " ")); + } else { + path = Paths.get(uri); + } + + return HazardModel.load(path); + + } catch (Exception e) { + throw new RuntimeException(e); + } + } + + static boolean emptyRequest(HttpServletRequest request) { + return isNullOrEmpty(request.getQueryString()) && + (request.getPathInfo() == null || request.getPathInfo().equals("/")); + } + + static Timer timer() { + return new Timer(); + } + + /* + * Simple timer object. The servlet timer just runs. The calculation timer can + * be started later. + */ + public static final class Timer { + + Stopwatch servlet = Stopwatch.createStarted(); + Stopwatch calc = Stopwatch.createUnstarted(); + + Timer start() { + calc.start(); + return this; + } + + public String servletTime() { + return servlet.toString(); + } + + public String calcTime() { + return calc.toString(); + } + } + + abstract static class TimedTask<T> implements Callable<T> { + + final String url; + final ServletContext context; + final Timer timer; + + TimedTask(String url, ServletContext context) { + this.url = url; + this.context = context; + this.timer = ServletUtil.timer(); + } + + abstract T calc() throws Exception; + + @Override + public T call() throws Exception { + timer.start(); + return calc(); + } + } + + /* + * For sites located west of -115 (in the WUS but not in the CEUS-WUS overlap + * zone) and site classes of vs30=760, client requests come in with + * region=COUS, thereby limiting the conversion of imt=any to the set of + * periods supported by both models. In order for the service to return what + * the client suggests should be returned, we need to do an addiitional + * longitude check. TODO clean; fix client eq-hazard-tool + */ + static Region checkRegion(Region region, double lon) { + if (region == COUS) { + return (lon <= WUS.uimaxlongitude) ? WUS : (lon >= CEUS.uiminlongitude) ? CEUS : COUS; + } + return region; + } + + /* + * IP based access throttling. + */ + + // static final Map<String, Long> IP_TIME = new HashMap<>(); + static final Map<String, Integer> IP_COUNT = new HashMap<>(); + // static final int IP_MAX_REQUESTS = 20; + // static final long IP_WINDOW_MS = 300000; + + /* blacklist */ + static final String BLOCK_1 = "98.26.65.16"; + + static boolean checkRequestIp(HttpServletRequest request) { + // if (!throttleIp) { + // return true; + // } + + String ip = getClientIp(request); + IP_COUNT.merge(ip, 1, Integer::sum); + return !ip.startsWith(BLOCK_1); + + // long cTime = System.currentTimeMillis(); + // + // IP_TIME.putIfAbsent(ip, cTime); + // IP_COUNT.putIfAbsent(ip, 0); + // + // long delta = cTime - IP_TIME.get(ip); + // + // /* + // * (1) If the number of requests is below 20 and ten minutes has passed + // * since the last request, reset the count and time reference, otherwise + // * keep the time of the first request, increment the request count, and + // * allow request to proceed. + // */ + // if (IP_COUNT.get(ip) < IP_MAX_REQUESTS) { + // if (delta > IP_WINDOW_MS) { + // IP_COUNT.put(ip, 0); + // IP_TIME.put(ip, cTime); + // return true; + // } + // IP_COUNT.merge(ip, 1, Integer::sum); + // return true; + // } + // + // /* + // * (2) If the number of recent requests is greater than 20 and less than + // 10 + // * minutes have passed since the first request, reject request. + // */ + // if (delta < IP_WINDOW_MS) { + // return false; + // } + // + // /* + // * (3) If the number of recent requests is greater than 20 and 10 to 20 + // * minutes has passed since the first request, update the reference time + // to + // * the present and allow the request to proceed. This limits requests to + // * once per ten minutes if the user has made a large number of requests in + // * rapid succesion. + // */ + // if (delta < IP_WINDOW_MS * 2) { + // IP_TIME.put(ip, cTime); + // return true; + // } + // + // /* + // * (4) Otherwise, it's been longer than 20 minutes. Reset count and time. + // */ + // IP_COUNT.put(ip, 0); + // IP_TIME.put(ip, cTime); + // return true; + } + + private static String getClientIp(HttpServletRequest request) { + String remoteAddr = ""; + if (request != null) { + remoteAddr = request.getHeader("X-FORWARDED-FOR"); + if (remoteAddr == null || "".equals(remoteAddr)) { + remoteAddr = request.getRemoteAddr(); + } + } + return remoteAddr; + } + +} diff --git a/src/gov/usgs/earthquake/nshmp/www/SourceServices.java b/src/gov/usgs/earthquake/nshmp/www/SourceServices.java new file mode 100644 index 000000000..22dfb8ced --- /dev/null +++ b/src/gov/usgs/earthquake/nshmp/www/SourceServices.java @@ -0,0 +1,258 @@ +package gov.usgs.earthquake.nshmp.www; + +import static gov.usgs.earthquake.nshmp.www.meta.Metadata.serverData; +import static gov.usgs.earthquake.nshmp.www.ServletUtil.INSTALLED_MODELS; + +import java.io.IOException; +import java.lang.reflect.Type; +import java.util.EnumSet; +import java.util.List; +import java.util.Set; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +import javax.servlet.ServletContext; +import javax.servlet.ServletException; +import javax.servlet.annotation.WebServlet; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +import com.google.gson.Gson; +import com.google.gson.GsonBuilder; +import com.google.gson.JsonElement; +import com.google.gson.JsonObject; +import com.google.gson.JsonSerializationContext; +import com.google.gson.JsonSerializer; + +import gov.usgs.earthquake.nshmp.calc.Vs30; +import gov.usgs.earthquake.nshmp.gmm.Imt; +import gov.usgs.earthquake.nshmp.www.meta.DoubleParameter; +import gov.usgs.earthquake.nshmp.www.meta.EnumParameter; +import gov.usgs.earthquake.nshmp.www.meta.ParamType; +import gov.usgs.earthquake.nshmp.www.meta.Region; +import gov.usgs.earthquake.nshmp.www.meta.Status; +import gov.usgs.earthquake.nshmp.www.meta.Util; + +/** + * Entry point for services related to source models. Current services: + * <ul><li>nshmp-haz-ws/source/</li></ul> + * + * @author Brandon Clayton + * @author Peter Powers + */ +@WebServlet( + name = "Source Services", + description = "Utilities for querying earthquake source models", + urlPatterns = { + "/source", + "/source/*" }) +@SuppressWarnings("unused") +public class SourceServices extends NshmpServlet { + private static final long serialVersionUID = 1L; + static final Gson GSON; + + static { + GSON = new GsonBuilder() + .registerTypeAdapter(Imt.class, new Util.EnumSerializer<Imt>()) + .registerTypeAdapter(ParamType.class, new Util.ParamTypeSerializer()) + .registerTypeAdapter(Vs30.class, new Util.EnumSerializer<Vs30>()) + .registerTypeAdapter(Region.class, new RegionSerializer()) + .disableHtmlEscaping() + .serializeNulls() + .setPrettyPrinting() + .create(); + } + + @Override + protected void doGet( + HttpServletRequest request, + HttpServletResponse response) + throws ServletException, IOException { + + ResponseData svcResponse = null; + try { + svcResponse = new ResponseData(); + String jsonString = GSON.toJson(svcResponse); + response.getWriter().print(jsonString); + } catch (Exception e) { + e.printStackTrace(); + } + } + + /* + * TODO service metadata should be in same package as services (why + * ResponseData is currently public); rename meta package to + */ + static final class ResponseData { + + final String name; + final String description; + final String status; + final String syntax; + final String deaggSyntax; + final Object server; + final Parameters parameters; + + ResponseData() { + this.name = "Source Models"; + this.description = "Installed source model listing"; + this.syntax = "%s://%s/nshmp-haz-ws/haz/{model}/{longitude}/{latitude}/{vs30}"; + this.deaggSyntax = "%s://%s/nshmp-haz-ws/deagg2/{model}/{longitude}/{latitude}/{imt}/{vs30}/{returnPeriod}/{basin}"; + this.status = Status.USAGE.toString(); + this.server = serverData(ServletUtil.THREAD_COUNT, ServletUtil.timer()); + this.parameters = new Parameters(); + } + } + + static class Parameters { + SourceModelsParameter models; + EnumParameter<Region> region; + DoubleParameter returnPeriod; + EnumParameter<Imt> imt; + EnumParameter<Vs30> vs30; + + Parameters() { + models = new SourceModelsParameter( + "Source models", + ParamType.STRING, + Stream.of(INSTALLED_MODELS) + .map(SourceModel::new) + .collect(Collectors.toList())); + + region = new EnumParameter<>( + "Region", + ParamType.STRING, + EnumSet.allOf(Region.class)); + + returnPeriod = new DoubleParameter( + "Return period (in years)", + ParamType.NUMBER, + 100.0, + 1e6); + + imt = new EnumParameter<>( + "Intensity measure type", + ParamType.STRING, + modelUnionImts()); + + vs30 = new EnumParameter<>( + "Site soil (Vs30)", + ParamType.STRING, + modelUnionVs30s()); + } + } + + private static class SourceModelsParameter { + private final String label; + private final ParamType type; + private final List<SourceModel> values; + + SourceModelsParameter(String label, ParamType type, List<SourceModel> values) { + this.label = label; + this.type = type; + this.values = values; + } + } + + /* Union of IMTs across all models. */ + static Set<Imt> modelUnionImts() { + return EnumSet.copyOf(Stream.of(INSTALLED_MODELS) + .flatMap(model -> model.imts.stream()) + .collect(Collectors.toSet())); + } + + /* Union of Vs30s across all models. */ + static Set<Vs30> modelUnionVs30s() { + return EnumSet.copyOf(Stream.of(INSTALLED_MODELS) + .flatMap(model -> model.vs30s.stream()) + .collect(Collectors.toSet())); + } + + static class SourceModel { + int displayorder; + int id; + String region; + String display; + String path; + String value; + String year; + ModelConstraints supports; + + SourceModel(Model model) { + this.display = model.name; + this.displayorder = model.ordinal(); + this.id = model.ordinal(); + this.region = model.region.name(); + this.path = model.path; + this.supports = new ModelConstraints(model); + this.value = model.toString(); + this.year = model.year; + } + } + + private static class ModelConstraints { + + final List<String> imt; + final List<String> vs30; + + ModelConstraints(Model model) { + this.imt = Util.enumsToNameList(model.imts); + this.vs30 = Util.enumsToStringList( + model.vs30s, + vs30 -> vs30.name().substring(3)); + } + } + + 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(); + } + } + + // TODO align with enum serializer if possible; consider service attribute + // enum + // TODO test removal of ui-min/max-lon/lat + static final class RegionSerializer implements JsonSerializer<Region> { + + @Override + public JsonElement serialize(Region region, Type typeOfSrc, JsonSerializationContext context) { + JsonObject json = new JsonObject(); + + json.addProperty(Attributes.VALUE.toLowerCase(), region.name()); + json.addProperty(Attributes.DISPLAY.toLowerCase(), region.toString()); + + json.addProperty(Attributes.MINLATITUDE.toLowerCase(), region.minlatitude); + json.addProperty(Attributes.MAXLATITUDE.toLowerCase(), region.maxlatitude); + json.addProperty(Attributes.MINLONGITUDE.toLowerCase(), region.minlongitude); + json.addProperty(Attributes.MAXLONGITUDE.toLowerCase(), region.maxlongitude); + + return json; + } + } + +} diff --git a/src/gov/usgs/earthquake/nshmp/www/Util.java b/src/gov/usgs/earthquake/nshmp/www/Util.java new file mode 100644 index 000000000..201021f5e --- /dev/null +++ b/src/gov/usgs/earthquake/nshmp/www/Util.java @@ -0,0 +1,150 @@ +package gov.usgs.earthquake.nshmp.www; + +import static com.google.common.base.Preconditions.checkNotNull; +import static com.google.common.base.Preconditions.checkState; + +import java.util.Arrays; +import java.util.Map; +import java.util.Set; +import java.util.stream.Collectors; + +import javax.servlet.ServletRequest; + +import gov.usgs.earthquake.nshmp.internal.Parsing; +import gov.usgs.earthquake.nshmp.internal.Parsing.Delimiter; + +public class Util { + + /** + * Returns the value of a servlet request parameter as a boolean. + * + * @param key of value to get + * @param request servlet request + */ + public static <E extends Enum<E>> boolean readBoolean(E key, ServletRequest request) { + return Boolean.valueOf(readValue(key, request)); + } + + /** + * Returns the value of a servlet request parameter as a double. + * + * @param key of value to get + * @param request servlet request + */ + public static <E extends Enum<E>> double readDouble(E key, ServletRequest request) { + return Double.valueOf(readValue(key, request)); + } + + /** + * Returns the value of a servlet request parameter as an integer. + * + * @param key of value to get + * @param request servlet request + */ + public static <E extends Enum<E>> int readInteger(E key, ServletRequest request) { + return Integer.valueOf(readValue(key, request)); + } + + /** + * Returns the value of a servlet request parameter as a string. + * + * @param key of value to get + * @param request servlet request + */ + public static <E extends Enum<E>> String readValue(E key, ServletRequest request) { + return readValues(key, request)[0]; + } + + /** + * Returns the value of a servlet request parameter as a enum of specified + * type. + * + * @param key of value to get + * @param request servlet request + * @param type of enum to return + */ + public static <T extends Enum<T>, E extends Enum<E>> T readValue( + E key, + ServletRequest request, + Class<T> type) { + return Enum.valueOf(type, readValue(key, request)); + } + + /** + * Returns the value of a servlet request parameter as a string array. + * + * @param key of value to get + * @param request servlet request + */ + public static <E extends Enum<E>> String[] readValues(E key, ServletRequest request) { + return checkNotNull( + request.getParameterValues(key.toString()), + "Missing query key [" + key.toString() + "]"); + } + + /** + * Returns the value of a servlet request parameter as a enum set of specified + * type. + * + * @param key of value to get + * @param request servlet request + * @param type of enum to return + */ + public static <T extends Enum<T>, E extends Enum<E>> Set<T> readValues( + E key, + ServletRequest request, + Class<T> type) { + + return Arrays.stream(readValues(key, request)) + .map((name) -> Enum.valueOf(type, name)) + .collect(Collectors.toSet()); + } + + enum Key { + EDITION, + REGION, + MODEL, + VS30, + LATITUDE, + LONGITUDE, + IMT, + RETURNPERIOD, + DISTANCE, + FORMAT, + TIMESPAN, + BASIN; + + private String label; + + private Key() { + label = name().toLowerCase(); + } + + @Override + public String toString() { + return label; + } + } + + static <T extends Enum<T>> Set<T> readValues(String values, Class<T> type) { + return Parsing.splitToList(values, Delimiter.COMMA).stream() + .map((name) -> Enum.valueOf(type, name)) + .collect(Collectors.toSet()); + } + + static <E extends Enum<E>> String readValue(E key, Map<String, String[]> paramMap) { + String keyStr = key.toString(); + String[] values = paramMap.get(keyStr); + checkNotNull(values, "Missing query key: %s", keyStr); + checkState(values.length > 0, "Empty value array for key: %s", key); + return values[0]; + } + + static <T extends Enum<T>, E extends Enum<E>> T readValue( + E key, + Map<String, String[]> paramMap, + Class<T> type) { + return Enum.valueOf(type, readValue(key, paramMap)); + } + +} diff --git a/src/gov/usgs/earthquake/nshmp/www/UtilitiesService.java b/src/gov/usgs/earthquake/nshmp/www/UtilitiesService.java new file mode 100644 index 000000000..dd9f44c26 --- /dev/null +++ b/src/gov/usgs/earthquake/nshmp/www/UtilitiesService.java @@ -0,0 +1,131 @@ +package gov.usgs.earthquake.nshmp.www; + +import java.io.IOException; +import java.io.PrintWriter; +import java.util.EnumSet; +import java.util.HashMap; +import java.util.Map; + +import javax.servlet.ServletException; +import javax.servlet.annotation.WebServlet; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +import gov.usgs.earthquake.nshmp.geo.json.Feature; +import gov.usgs.earthquake.nshmp.geo.json.GeoJson; +import gov.usgs.earthquake.nshmp.geo.json.Properties; +import gov.usgs.earthquake.nshmp.internal.NshmpSite; + +@WebServlet( + name = "Utilities Service", + description = "USGS NSHMP Web Service Utilities", + urlPatterns = { + "/util", + "/util/*" }) +@SuppressWarnings("javadoc") +public class UtilitiesService extends NshmpServlet { + + @Override + protected void doGet( + HttpServletRequest request, + HttpServletResponse response) + throws ServletException, IOException { + + PrintWriter out = response.getWriter(); + String utilUrl = "/nshmp-haz-ws/apps/util.html"; + + String pathInfo = request.getPathInfo(); + + switch (pathInfo) { + case "/testsites": + out.println(proccessTestSites()); + break; + default: + response.sendRedirect(utilUrl); + } + } + + private static String proccessTestSites() { + Map<String, EnumSet<NshmpSite>> nshmpSites = new HashMap<>(); + nshmpSites.put("ceus", NshmpSite.ceus()); + nshmpSites.put("cous", NshmpSite.cous()); + nshmpSites.put("wus", NshmpSite.wus()); + nshmpSites.put("ak", NshmpSite.alaska()); + nshmpSites.put("facilities", NshmpSite.facilities()); + nshmpSites.put("nehrp", NshmpSite.nehrp()); + nshmpSites.put("nrc", NshmpSite.nrc()); + nshmpSites.put("hawaii", NshmpSite.hawaii()); + + GeoJson.Builder builder = GeoJson.builder(); + + for (String regionKey : nshmpSites.keySet()) { + RegionInfo regionInfo = getRegionInfo(regionKey); + for (NshmpSite site : nshmpSites.get(regionKey)) { + Map<String, Object> properties = Properties.builder() + .put(Key.TITLE, site.toString()) + .put(Key.REGION_ID, regionInfo.regionId) + .put(Key.REGION_TITLE, regionInfo.regionDisplay) + .build(); + + builder.add(Feature.point(site.location()) + .id(site.id()) + .properties(properties) + .build()); + } + } + return builder.toJson(); + } + + private static class Key { + private static final String TITLE = "title"; + private static final String REGION_ID = "regionId"; + private static final String REGION_TITLE = "regionTitle"; + } + + private static class RegionInfo { + private String regionId; + private String regionDisplay; + + private RegionInfo(String regionDisplay, String regionId) { + this.regionId = regionId.toUpperCase(); + this.regionDisplay = regionDisplay; + } + + } + + private static RegionInfo getRegionInfo(String regionId) { + String regionDisplay = ""; + + switch (regionId) { + case "ceus": + regionDisplay = "Central & Eastern US"; + break; + case "cous": + regionDisplay = "Conterminous US"; + break; + case "wus": + regionDisplay = "Western US"; + break; + case "ak": + regionDisplay = "Alaska"; + break; + case "facilities": + regionDisplay = "US National Labs"; + break; + case "nehrp": + regionDisplay = "NEHRP"; + break; + case "nrc": + regionDisplay = "NRC"; + break; + case "hawaii": + regionDisplay = "Hawaii"; + break; + default: + throw new RuntimeException("Region [" + regionId + "] not found"); + } + + return new RegionInfo(regionDisplay, regionId); + } + +} diff --git a/src/gov/usgs/earthquake/nshmp/www/XY_DataGroup.java b/src/gov/usgs/earthquake/nshmp/www/XY_DataGroup.java new file mode 100644 index 000000000..adb6213b5 --- /dev/null +++ b/src/gov/usgs/earthquake/nshmp/www/XY_DataGroup.java @@ -0,0 +1,52 @@ +package gov.usgs.earthquake.nshmp.www; + +import java.util.ArrayList; +import java.util.List; + +import gov.usgs.earthquake.nshmp.data.XySequence; + +/** + * Container class of XY data sequences prior to Json serialization. This + * implementation is for data series that share the same x-values + * + * @author Peter Powers + */ +@SuppressWarnings("unused") +public class XY_DataGroup { + + private final String label; + private final String xLabel; + private final String yLabel; + protected final List<Series> data; + + protected XY_DataGroup(String label, String xLabel, String yLabel) { + this.label = label; + this.xLabel = xLabel; + this.yLabel = yLabel; + this.data = new ArrayList<>(); + } + + /** Create a data group. */ + public static XY_DataGroup create(String name, String xLabel, String yLabel) { + return new XY_DataGroup(name, xLabel, yLabel); + } + + /** Add a data sequence */ + public XY_DataGroup add(String id, String name, XySequence data) { + this.data.add(new Series(id, name, data)); + return this; + } + + static class Series { + private final String id; + private final String label; + private final XySequence data; + + Series(String id, String label, XySequence data) { + this.id = id; + this.label = label; + this.data = data; + } + } + +} diff --git a/src/gov/usgs/earthquake/nshmp/www/meta/Constrained.java b/src/gov/usgs/earthquake/nshmp/www/meta/Constrained.java new file mode 100644 index 000000000..2dbafe63b --- /dev/null +++ b/src/gov/usgs/earthquake/nshmp/www/meta/Constrained.java @@ -0,0 +1,10 @@ +package gov.usgs.earthquake.nshmp.www.meta; + +/** + * Interface implemented by enum parameters that impose restrictions on other + * parameter choices. + */ +@SuppressWarnings("javadoc") +public interface Constrained { + public Constraints constraints(); +} diff --git a/src/gov/usgs/earthquake/nshmp/www/meta/Constraints.java b/src/gov/usgs/earthquake/nshmp/www/meta/Constraints.java new file mode 100644 index 000000000..449d3c7b8 --- /dev/null +++ b/src/gov/usgs/earthquake/nshmp/www/meta/Constraints.java @@ -0,0 +1,7 @@ +package gov.usgs.earthquake.nshmp.www.meta; + +/** + * Marker interface for supported parameter lists and ; individual enums provide + * concrete implementations. + */ +public interface Constraints {} diff --git a/src/gov/usgs/earthquake/nshmp/www/meta/DoubleParameter.java b/src/gov/usgs/earthquake/nshmp/www/meta/DoubleParameter.java new file mode 100644 index 000000000..0f4677d4c --- /dev/null +++ b/src/gov/usgs/earthquake/nshmp/www/meta/DoubleParameter.java @@ -0,0 +1,27 @@ +package gov.usgs.earthquake.nshmp.www.meta; + +@SuppressWarnings({ "javadoc", "unused" }) +public final class DoubleParameter { + + private final String label; + private final ParamType type; + private final Values values; + + public DoubleParameter(String label, ParamType type, double min, double max) { + this.label = label; + this.type = type; + this.values = new Values(min, max); + } + + private final static class Values { + + final double minimum; + final double maximum; + + Values(double min, double max) { + this.minimum = min; + this.maximum = max; + } + } + +} diff --git a/src/gov/usgs/earthquake/nshmp/www/meta/Edition.java b/src/gov/usgs/earthquake/nshmp/www/meta/Edition.java new file mode 100644 index 000000000..9c8ba85a8 --- /dev/null +++ b/src/gov/usgs/earthquake/nshmp/www/meta/Edition.java @@ -0,0 +1,129 @@ +package gov.usgs.earthquake.nshmp.www.meta; + +import static gov.usgs.earthquake.nshmp.gmm.Imt.PGA; +import static gov.usgs.earthquake.nshmp.gmm.Imt.SA0P01; +import static gov.usgs.earthquake.nshmp.gmm.Imt.SA0P02; +import static gov.usgs.earthquake.nshmp.gmm.Imt.SA0P03; +import static gov.usgs.earthquake.nshmp.gmm.Imt.SA0P05; +import static gov.usgs.earthquake.nshmp.gmm.Imt.SA0P075; +import static gov.usgs.earthquake.nshmp.gmm.Imt.SA0P1; +import static gov.usgs.earthquake.nshmp.gmm.Imt.SA0P15; +import static gov.usgs.earthquake.nshmp.gmm.Imt.SA0P2; +import static gov.usgs.earthquake.nshmp.gmm.Imt.SA0P25; +import static gov.usgs.earthquake.nshmp.gmm.Imt.SA0P3; +import static gov.usgs.earthquake.nshmp.gmm.Imt.SA0P4; +import static gov.usgs.earthquake.nshmp.gmm.Imt.SA0P5; +import static gov.usgs.earthquake.nshmp.gmm.Imt.SA0P75; +import static gov.usgs.earthquake.nshmp.gmm.Imt.SA1P0; +import static gov.usgs.earthquake.nshmp.gmm.Imt.SA1P5; +import static gov.usgs.earthquake.nshmp.gmm.Imt.SA2P0; +import static gov.usgs.earthquake.nshmp.gmm.Imt.SA3P0; +import static gov.usgs.earthquake.nshmp.gmm.Imt.SA4P0; +import static gov.usgs.earthquake.nshmp.gmm.Imt.SA5P0; +import static gov.usgs.earthquake.nshmp.gmm.Imt.SA7P5; +import static gov.usgs.earthquake.nshmp.gmm.Imt.SA10P0; +import static gov.usgs.earthquake.nshmp.www.meta.Region.AK; +import static gov.usgs.earthquake.nshmp.www.meta.Region.CEUS; +import static gov.usgs.earthquake.nshmp.www.meta.Region.COUS; +import static gov.usgs.earthquake.nshmp.www.meta.Region.WUS; + +import java.util.EnumSet; +import java.util.Set; + +import gov.usgs.earthquake.nshmp.gmm.Imt; + +@SuppressWarnings({ "javadoc", "unused" }) +public enum Edition implements Constrained { + + E2008( + "Dynamic: Conterminous U.S. 2008", + 100, + EnumSet.of(COUS, CEUS, WUS), + EnumSet.of(PGA, SA0P1, SA0P2, SA0P3, SA0P5, SA0P75, SA1P0, SA2P0, SA3P0)), + + E2014( + "Dynamic: Conterminous U.S. 2014", + 0, + EnumSet.of(COUS, CEUS, WUS), + EnumSet.of(PGA, SA0P1, SA0P2, SA0P3, SA0P5, SA1P0, SA2P0)), + + E2014B( + "Dynamic: Conterminous U.S. 2014 (update)", + -10, + EnumSet.of(COUS, CEUS, WUS), + EnumSet.of(PGA, SA0P1, SA0P2, SA0P3, SA0P5, SA0P75, SA1P0, SA2P0, SA3P0, SA4P0, SA5P0)), + + E2018( + "Dynamic: Conterminous U.S. 2018", + 10, + EnumSet.of(COUS, CEUS, WUS), + EnumSet.of( + PGA, + SA0P01, + SA0P02, + SA0P03, + SA0P05, + SA0P075, + SA0P1, + SA0P15, + SA0P2, + SA0P25, + SA0P3, + SA0P4, + SA0P5, + SA0P75, + SA1P0, + SA1P5, + SA2P0, + SA3P0, + SA4P0, + SA5P0, + SA7P5, + SA10P0)), + + E2007( + "Dynamic: Alaska 2007", + -100, + EnumSet.of(AK), + EnumSet.of(PGA, SA0P1, SA0P2, SA0P3, SA0P5, SA1P0, SA2P0)); + + private final String label; + + /* not serialized */ + private final transient String version; + private final transient Set<Region> regions; + final transient Set<Imt> imts; + + private final Constraints constraints; + + final int displayOrder; + + private Edition( + String label, + int displayOrder, + Set<Region> regions, + Set<Imt> imts) { + + this.version = Versions.modelVersion(name()); + this.label = label + " (" + version + ")"; + this.displayOrder = displayOrder; + this.regions = regions; + this.imts = imts; + this.constraints = new EditionConstraints(regions, imts); + } + + @Override + public String toString() { + return label; + } + + public String version() { + return version; + } + + @Override + public Constraints constraints() { + return constraints; + } + +} diff --git a/src/gov/usgs/earthquake/nshmp/www/meta/EditionConstraints.java b/src/gov/usgs/earthquake/nshmp/www/meta/EditionConstraints.java new file mode 100644 index 000000000..c0bec0d20 --- /dev/null +++ b/src/gov/usgs/earthquake/nshmp/www/meta/EditionConstraints.java @@ -0,0 +1,28 @@ +package gov.usgs.earthquake.nshmp.www.meta; + +import java.util.EnumSet; +import java.util.List; +import java.util.Set; + +import gov.usgs.earthquake.nshmp.calc.Vs30; +import gov.usgs.earthquake.nshmp.gmm.Imt; + +@SuppressWarnings("unused") +class EditionConstraints implements Constraints { + + private final List<String> region; + private final List<String> imt; + private final List<String> vs30; + + EditionConstraints(Set<Region> regions, Set<Imt> imts) { + // converting to Strings here, otherwise EnumSerializer will be used + // and we want a compact list of (possible modified) enum.name()s + this.region = Util.enumsToNameList(regions); + this.imt = Util.enumsToNameList(imts); + Set<Vs30> vs30s = EnumSet.noneOf(Vs30.class); + for (Region region : regions) { + vs30s.addAll(region.vs30s); + } + this.vs30 = Util.enumsToStringList(vs30s, vs30 -> vs30.name().substring(3)); + } +} diff --git a/src/gov/usgs/earthquake/nshmp/www/meta/EnumParameter.java b/src/gov/usgs/earthquake/nshmp/www/meta/EnumParameter.java new file mode 100644 index 000000000..39afa8147 --- /dev/null +++ b/src/gov/usgs/earthquake/nshmp/www/meta/EnumParameter.java @@ -0,0 +1,18 @@ +package gov.usgs.earthquake.nshmp.www.meta; + +import java.util.Set; + +@SuppressWarnings({ "javadoc", "unused" }) +public final class EnumParameter<E extends Enum<E>> { + + private final String label; + private final ParamType type; + private final Set<E> values; + + public EnumParameter(String label, ParamType type, Set<E> values) { + this.label = label; + this.type = type; + this.values = values; + } + +} diff --git a/src/gov/usgs/earthquake/nshmp/www/meta/Metadata.java b/src/gov/usgs/earthquake/nshmp/www/meta/Metadata.java new file mode 100644 index 000000000..5d7359137 --- /dev/null +++ b/src/gov/usgs/earthquake/nshmp/www/meta/Metadata.java @@ -0,0 +1,299 @@ +package gov.usgs.earthquake.nshmp.www.meta; + +import java.util.EnumSet; +import java.util.Set; + +import com.google.common.base.Throwables; +import com.google.common.collect.Sets; +import com.google.gson.annotations.SerializedName; + +import gov.usgs.earthquake.nshmp.calc.Vs30; +import gov.usgs.earthquake.nshmp.geo.Coordinates; +import gov.usgs.earthquake.nshmp.gmm.Imt; +import gov.usgs.earthquake.nshmp.mfd.Mfds; +import gov.usgs.earthquake.nshmp.www.ServletUtil; +import gov.usgs.earthquake.nshmp.www.ServletUtil.Timer; + +/** + * Service metadata, parameterization, and constraint strings, in JSON format. + */ +@SuppressWarnings("javadoc") +public final class Metadata { + + static final String NSHMP_HAZ_URL = "https://github.com/usgs/nshmp-haz"; + static final String NSHMP_HAZ_WS_URL = "https://github.com/usgs/nshmp-haz-ws"; + + /* + * The hazard service needs to report the list of all possible IMTs supported + * even though what can actually be supported is dependent on Edition and + * Region. When the slash delimited service returns 'any', the same logic + * applied on the client needs to be applied on the server to determine what + * 'any' acutally means. See HazardService.buildRequest() methods. This field + * currently set to the IMTs supported by Region.WUS which we know to be the + * union of all periods currently supported by the models used. + */ + private static final Set<Imt> HAZARD_IMTS = Region.WUS.imts; + + private static final String URL_PREFIX = "%s://%s/nshmp-haz-ws"; + + public static final String HAZARD_USAGE = ServletUtil.GSON.toJson( + new Default( + "Compute hazard curve data at a location", + URL_PREFIX + "/hazard/{edition}/{region}/{longitude}/{latitude}/{imt}/{vs30}", + new HazardParameters())); + + public static final String DEAGG_USAGE = ServletUtil.GSON.toJson( + new Deagg( + "Deaggregate hazard at a location", + URL_PREFIX + + "/deagg/{edition}/{region}/{longitude}/{latitude}/{imt}/{vs30}/{returnPeriod}", + new DeaggParameters())); + + public static final String RATE_USAGE = ServletUtil.GSON.toJson( + new Rate( + "Compute incremental earthquake annual-rates at a location", + URL_PREFIX + "/rate/{edition}/{region}/{longitude}/{latitude}/{distance}", + new RateParameters())); + + public static final String PROBABILITY_USAGE = ServletUtil.GSON.toJson( + new Probability( + "Compute cumulative earthquake probabilities P(M ≥ x) at a location", + URL_PREFIX + + "/probability/{edition}/{region}/{longitude}/{latitude}/{distance}/{timespan}", + new ProbabilityParameters())); + + @SuppressWarnings("unused") + private static class Default { + + final String status; + final String description; + final String syntax; + final Object server; + final DefaultParameters parameters; + + private Default( + String description, + String syntax, + DefaultParameters parameters) { + this.status = Status.USAGE.toString(); + this.description = description; + this.syntax = syntax; + this.server = serverData(1, new Timer()); + this.parameters = parameters; + } + } + + public static Object serverData(int threads, Timer timer) { + return new Server(threads, timer); + } + + @SuppressWarnings("unused") + private static class Server { + + final int threads; + final String servlet; + final String calc; + + @SerializedName("nshmp-haz") + final Component nshmpHaz = NSHMP_HAZ_COMPONENT; + + @SerializedName("nshmp-haz-ws") + final Component nshmpHazWs = NSHMP_HAZ_WS_COMPONENT; + + Server(int threads, Timer timer) { + this.threads = threads; + this.servlet = timer.servletTime(); + this.calc = timer.calcTime(); + } + + static Component NSHMP_HAZ_COMPONENT = new Component( + NSHMP_HAZ_URL, + Versions.NSHMP_HAZ_VERSION); + + static Component NSHMP_HAZ_WS_COMPONENT = new Component( + NSHMP_HAZ_WS_URL, + Versions.NSHMP_HAZ_WS_VERSION); + + static final class Component { + + final String url; + final String version; + + Component(String url, String version) { + this.url = url; + this.version = version; + } + } + } + + @SuppressWarnings("unused") + private static class DefaultParameters { + + final EnumParameter<Edition> edition; + final EnumParameter<Region> region; + final DoubleParameter longitude; + final DoubleParameter latitude; + + DefaultParameters() { + + edition = new EnumParameter<>( + "Model edition", + ParamType.STRING, + EnumSet.allOf(Edition.class)); + + region = new EnumParameter<>( + "Model region", + ParamType.STRING, + EnumSet.allOf(Region.class)); + + longitude = new DoubleParameter( + "Longitude (in decimal degrees)", + ParamType.NUMBER, + Coordinates.LON_RANGE.lowerEndpoint(), + Coordinates.LON_RANGE.upperEndpoint()); + + latitude = new DoubleParameter( + "Latitude (in decimal degrees)", + ParamType.NUMBER, + Coordinates.LAT_RANGE.lowerEndpoint(), + Coordinates.LAT_RANGE.upperEndpoint()); + } + } + + @SuppressWarnings("unused") + private static class HazardParameters extends DefaultParameters { + + final EnumParameter<Imt> imt; + final EnumParameter<Vs30> vs30; + + HazardParameters() { + + imt = new EnumParameter<>( + "Intensity measure type", + ParamType.STRING, + HAZARD_IMTS); + + vs30 = new EnumParameter<>( + "Site soil (Vs30)", + ParamType.STRING, + EnumSet.allOf(Vs30.class)); + } + } + + private static class Deagg extends Default { + private Deagg( + String description, + String syntax, + DeaggParameters parameters) { + super(description, syntax, parameters); + } + } + + @SuppressWarnings("unused") + private static class DeaggParameters extends HazardParameters { + + final DoubleParameter returnPeriod; + + DeaggParameters() { + + returnPeriod = new DoubleParameter( + "Return period (in years)", + ParamType.NUMBER, + 1.0, + 4000.0); + } + } + + private static class Rate extends Default { + private Rate( + String description, + String syntax, + RateParameters parameters) { + super(description, syntax, parameters); + } + } + + @SuppressWarnings("unused") + private static class RateParameters extends DefaultParameters { + + final DoubleParameter distance; + + RateParameters() { + distance = new DoubleParameter( + "Cutoff distance (in km)", + ParamType.NUMBER, + 0.01, + 1000.0); + } + } + + private static class Probability extends Default { + private Probability( + String description, + String syntax, + ProbabilityParameters parameters) { + super(description, syntax, parameters); + } + } + + @SuppressWarnings("unused") + private static class ProbabilityParameters extends RateParameters { + + final DoubleParameter timespan; + + ProbabilityParameters() { + timespan = new DoubleParameter( + "Forecast time span (in years)", + ParamType.NUMBER, + Mfds.TIMESPAN_RANGE.lowerEndpoint(), + Mfds.TIMESPAN_RANGE.upperEndpoint()); + } + } + + public static String tooManyRequestsMessage(String url) { + TooManyRequests tmr = new TooManyRequests(url); + return ServletUtil.GSON.toJson(tmr); + } + + static final String TOO_MANY_REQUESTS_MESSAGE = + "Too many requests. Please contact administrator."; + + private static class TooManyRequests { + + final String status = Status.BUSY.toString(); + final String request; + final String message; + + private TooManyRequests(String request) { + this.request = request; + this.message = TOO_MANY_REQUESTS_MESSAGE; + } + } + + public static String errorMessage(String url, Throwable e, boolean trace) { + Error error = new Error(url, e, trace); + return ServletUtil.GSON.toJson(error); + } + + @SuppressWarnings("unused") + private static class Error { + + final String status = Status.ERROR.toString(); + final String request; + final String message; + + private Error(String request, Throwable e, boolean trace) { + this.request = request; + String message = e.getMessage() + " (see logs)"; + if (trace) { + message += "\n" + Throwables.getStackTraceAsString(e); + } + this.message = message; + } + } + + public static Set<Imt> commonImts(Edition edition, Region region) { + return Sets.intersection(edition.imts, region.imts); + } + +} diff --git a/src/gov/usgs/earthquake/nshmp/www/meta/ParamType.java b/src/gov/usgs/earthquake/nshmp/www/meta/ParamType.java new file mode 100644 index 000000000..5d3e1475c --- /dev/null +++ b/src/gov/usgs/earthquake/nshmp/www/meta/ParamType.java @@ -0,0 +1,13 @@ +package gov.usgs.earthquake.nshmp.www.meta; + +@SuppressWarnings("javadoc") +public enum ParamType { + INTEGER, + NUMBER, + STRING; + + @Override + public String toString() { + return name().toLowerCase(); + } +} diff --git a/src/gov/usgs/earthquake/nshmp/www/meta/Region.java b/src/gov/usgs/earthquake/nshmp/www/meta/Region.java new file mode 100644 index 000000000..034e9c7d3 --- /dev/null +++ b/src/gov/usgs/earthquake/nshmp/www/meta/Region.java @@ -0,0 +1,130 @@ +package gov.usgs.earthquake.nshmp.www.meta; + +import static gov.usgs.earthquake.nshmp.calc.Vs30.VS_1150; +import static gov.usgs.earthquake.nshmp.calc.Vs30.VS_180; +import static gov.usgs.earthquake.nshmp.calc.Vs30.VS_2000; +import static gov.usgs.earthquake.nshmp.calc.Vs30.VS_259; +import static gov.usgs.earthquake.nshmp.calc.Vs30.VS_360; +import static gov.usgs.earthquake.nshmp.calc.Vs30.VS_537; +import static gov.usgs.earthquake.nshmp.calc.Vs30.VS_760; +import static gov.usgs.earthquake.nshmp.gmm.Imt.PGA; +import static gov.usgs.earthquake.nshmp.gmm.Imt.SA0P1; +import static gov.usgs.earthquake.nshmp.gmm.Imt.SA0P2; +import static gov.usgs.earthquake.nshmp.gmm.Imt.SA0P3; +import static gov.usgs.earthquake.nshmp.gmm.Imt.SA0P5; +import static gov.usgs.earthquake.nshmp.gmm.Imt.SA0P75; +import static gov.usgs.earthquake.nshmp.gmm.Imt.SA1P0; +import static gov.usgs.earthquake.nshmp.gmm.Imt.SA2P0; +import static gov.usgs.earthquake.nshmp.gmm.Imt.SA3P0; +import static gov.usgs.earthquake.nshmp.gmm.Imt.SA4P0; +import static gov.usgs.earthquake.nshmp.gmm.Imt.SA5P0; + +import java.util.EnumSet; +import java.util.Set; + +import gov.usgs.earthquake.nshmp.calc.Vs30; +import gov.usgs.earthquake.nshmp.gmm.Imt; + +@SuppressWarnings("javadoc") +public enum Region implements Constrained { + + AK( + "Alaska", + new double[] { 48.0, 72.0 }, + new double[] { -200.0, -125.0 }, + new double[] { 48.0, 72.0 }, + new double[] { -200.0, -125.0 }, + EnumSet.of(PGA, SA0P1, SA0P2, SA0P3, SA0P5, SA1P0, SA2P0), + EnumSet.of(VS_760)), + + COUS( + "Conterminous US", + new double[] { 24.6, 50.0 }, + new double[] { -125.0, -65.0 }, + new double[] { 24.6, 50.0 }, + new double[] { -125.0, -65.0 }, + EnumSet.of(PGA, SA0P1, SA0P2, SA0P3, SA0P5, SA1P0, SA2P0), + EnumSet.of(VS_760)), + + CEUS( + "Central & Eastern US", + new double[] { 24.6, 50.0 }, + new double[] { -115.0, -65.0 }, + new double[] { 24.6, 50.0 }, + new double[] { -100.0, -65.0 }, + EnumSet.of(PGA, SA0P1, SA0P2, SA0P3, SA0P5, SA1P0, SA2P0), + EnumSet.of(VS_2000, VS_760)), + + HI( + "Hawaii", + new double[] { 18.0, 23.0 }, + new double[] { -161.0, -154.0 }, + new double[] { 18.0, 23.0 }, + new double[] { -161.0, -154.0 }, + EnumSet.of(PGA, SA0P1, SA0P2, SA0P3, SA0P5, SA0P75, SA1P0, SA2P0, SA3P0, SA5P0), + EnumSet.of(VS_1150, VS_760, VS_537, VS_360, VS_259, VS_180)), + + WUS( + "Western US", + new double[] { 24.6, 50.0 }, + new double[] { -125.0, -100.0 }, + new double[] { 24.6, 50.0 }, + new double[] { -125.0, -115.0 }, + EnumSet.of(PGA, SA0P1, SA0P2, SA0P3, SA0P5, SA0P75, SA1P0, SA2P0, SA3P0, SA4P0, SA5P0), + EnumSet.of(VS_1150, VS_760, VS_537, VS_360, VS_259, VS_180)); + + public final String label; + + public final double minlatitude; + public final double maxlatitude; + public final double minlongitude; + public final double maxlongitude; + + public final double uiminlatitude; + public final double uimaxlatitude; + public final double uiminlongitude; + public final double uimaxlongitude; + + /* not serialized */ + final transient Set<Imt> imts; + final transient Set<Vs30> vs30s; + + private final Constraints constraints; + + private Region( + String label, + double[] latRange, + double[] lonRange, + double[] uiLatRange, + double[] uiLonRange, + Set<Imt> imts, + Set<Vs30> vs30s) { + + this.label = label; + + this.minlatitude = latRange[0]; + this.maxlatitude = latRange[1]; + this.minlongitude = lonRange[0]; + this.maxlongitude = lonRange[1]; + + this.uiminlatitude = uiLatRange[0]; + this.uimaxlatitude = uiLatRange[1]; + this.uiminlongitude = uiLonRange[0]; + this.uimaxlongitude = uiLonRange[1]; + + this.imts = imts; + this.vs30s = vs30s; + + this.constraints = new RegionConstraints(imts, vs30s); + } + + @Override + public String toString() { + return label; + } + + @Override + public Constraints constraints() { + return constraints; + } +} diff --git a/src/gov/usgs/earthquake/nshmp/www/meta/RegionConstraints.java b/src/gov/usgs/earthquake/nshmp/www/meta/RegionConstraints.java new file mode 100644 index 000000000..604764612 --- /dev/null +++ b/src/gov/usgs/earthquake/nshmp/www/meta/RegionConstraints.java @@ -0,0 +1,21 @@ +package gov.usgs.earthquake.nshmp.www.meta; + +import java.util.List; +import java.util.Set; + +import gov.usgs.earthquake.nshmp.calc.Vs30; +import gov.usgs.earthquake.nshmp.gmm.Imt; + +@SuppressWarnings("unused") +class RegionConstraints implements Constraints { + + private final List<String> imt; + private final List<String> vs30; + + RegionConstraints(Set<Imt> imts, Set<Vs30> vs30s) { + // converting to Strings here, otherwise EnumSerializer will be used + // and we want a compact list of (possible modified) enum.name()s + this.imt = Util.enumsToNameList(imts); + this.vs30 = Util.enumsToStringList(vs30s, vs30 -> vs30.name().substring(3)); + } +} diff --git a/src/gov/usgs/earthquake/nshmp/www/meta/Status.java b/src/gov/usgs/earthquake/nshmp/www/meta/Status.java new file mode 100644 index 000000000..f48acc512 --- /dev/null +++ b/src/gov/usgs/earthquake/nshmp/www/meta/Status.java @@ -0,0 +1,20 @@ +package gov.usgs.earthquake.nshmp.www.meta; + +/** + * Service request status identifier. + * + * @author Peter Powers + */ +@SuppressWarnings("javadoc") +public enum Status { + + BUSY, + ERROR, + SUCCESS, + USAGE; + + @Override + public String toString() { + return name().toLowerCase(); + } +} diff --git a/src/gov/usgs/earthquake/nshmp/www/meta/Util.java b/src/gov/usgs/earthquake/nshmp/www/meta/Util.java new file mode 100644 index 000000000..78449e579 --- /dev/null +++ b/src/gov/usgs/earthquake/nshmp/www/meta/Util.java @@ -0,0 +1,159 @@ +package gov.usgs.earthquake.nshmp.www.meta; + +import java.lang.reflect.Type; +import java.util.Collection; +import java.util.List; +import java.util.Optional; +import java.util.function.Function; +import java.util.stream.Collectors; + +import com.google.common.collect.Range; +import com.google.gson.JsonArray; +import com.google.gson.JsonElement; +import com.google.gson.JsonObject; +import com.google.gson.JsonPrimitive; +import com.google.gson.JsonSerializationContext; +import com.google.gson.JsonSerializer; + +import gov.usgs.earthquake.nshmp.calc.Site; +import gov.usgs.earthquake.nshmp.calc.Vs30; +import gov.usgs.earthquake.nshmp.gmm.GmmInput; +import gov.usgs.earthquake.nshmp.gmm.GmmInput.Field; +import gov.usgs.earthquake.nshmp.util.Maths; + +@SuppressWarnings("javadoc") +public final class Util { + + public static <E extends Enum<E>> List<String> enumsToNameList( + Collection<E> values) { + return enumsToStringList(values, Enum::name); + } + + public static <E extends Enum<E>> List<String> enumsToStringList( + Collection<E> values, + Function<E, String> function) { + return values.stream().map(function).collect(Collectors.toList()); + } + + public static final class EnumSerializer<E extends Enum<E>> implements JsonSerializer<E> { + + @Override + public JsonElement serialize(E src, Type type, JsonSerializationContext context) { + + String value = (src instanceof Vs30) ? src.name().substring(3) : src.name(); + int displayOrder = (src instanceof Edition) ? ((Edition) src).displayOrder : src.ordinal(); + + JsonObject jObj = new JsonObject(); + jObj.addProperty("id", src.ordinal()); + jObj.addProperty("value", value); + if (src instanceof Edition) { + jObj.addProperty("version", ((Edition) src).version()); + } + jObj.addProperty("display", src.toString()); + jObj.addProperty("displayorder", displayOrder); + + if (src instanceof Region) { + Region region = (Region) src; + jObj.addProperty("minlatitude", region.minlatitude); + jObj.addProperty("maxlatitude", region.maxlatitude); + jObj.addProperty("minlongitude", region.minlongitude); + jObj.addProperty("maxlongitude", region.maxlongitude); + + jObj.addProperty("uiminlatitude", region.uiminlatitude); + jObj.addProperty("uimaxlatitude", region.uimaxlatitude); + jObj.addProperty("uiminlongitude", region.uiminlongitude); + jObj.addProperty("uimaxlongitude", region.uimaxlongitude); + } + + if (src instanceof Constrained) { + Constrained cSrc = (Constrained) src; + jObj.add("supports", context.serialize(cSrc.constraints())); + } + + return jObj; + } + } + + public static final class SiteSerializer implements JsonSerializer<Site> { + + @Override + public JsonElement serialize(Site site, Type typeOfSrc, JsonSerializationContext context) { + JsonObject loc = new JsonObject(); + + loc.addProperty("latitude", Maths.round(site.location.lat(), 3)); + loc.addProperty("longitude", Maths.round(site.location.lon(), 3)); + + JsonObject json = new JsonObject(); + json.add("location", loc); + json.addProperty("vs30", site.vs30); + json.addProperty("vsInfered", site.vsInferred); + json.addProperty("z1p0", Double.isNaN(site.z1p0) ? null : site.z1p0); + json.addProperty("z2p5", Double.isNaN(site.z2p5) ? null : site.z2p5); + + return json; + } + + } + + /* Constrain all doubles to 8 decimal places */ + public static final class DoubleSerializer implements JsonSerializer<Double> { + @Override + public JsonElement serialize(Double d, Type type, JsonSerializationContext context) { + double dOut = Double.valueOf(String.format("%.8g", d)); + return new JsonPrimitive(dOut); + } + } + + /* Serialize param type enum as lowercase */ + public static class ParamTypeSerializer implements JsonSerializer<ParamType> { + @Override + public JsonElement serialize(ParamType paramType, Type type, JsonSerializationContext context) { + return new JsonPrimitive(paramType.name().toLowerCase()); + } + } + + /* Convert NaN to null */ + public static final class NaNSerializer implements JsonSerializer<Double> { + @Override + public JsonElement serialize(Double d, Type type, JsonSerializationContext context) { + return Double.isNaN(d) ? null : new JsonPrimitive(d); + } + } + + public static final class ConstraintsSerializer implements JsonSerializer<GmmInput.Constraints> { + @Override + public JsonElement serialize( + GmmInput.Constraints constraints, + Type type, + JsonSerializationContext context) { + JsonArray json = new JsonArray(); + + for (Field field : Field.values()) { + Optional<?> opt = constraints.get(field); + if (opt.isPresent()) { + Range<?> value = (Range<?>) opt.get(); + Constraint constraint = new Constraint( + field.id, + value.lowerEndpoint(), + value.upperEndpoint()); + json.add(context.serialize(constraint)); + } + } + + return json; + } + } + + private static class Constraint { + final String id; + final Object min; + final Object max; + + Constraint(String id, Object min, Object max) { + this.id = id; + this.min = min; + this.max = max; + } + } + +} diff --git a/src/gov/usgs/earthquake/nshmp/www/meta/Versions.java b/src/gov/usgs/earthquake/nshmp/www/meta/Versions.java new file mode 100644 index 000000000..dea9da6c7 --- /dev/null +++ b/src/gov/usgs/earthquake/nshmp/www/meta/Versions.java @@ -0,0 +1,55 @@ +package gov.usgs.earthquake.nshmp.www.meta; + +import com.google.common.collect.ImmutableMap; + +import java.io.InputStream; +import java.util.Map; +import java.util.Properties; + +import gov.usgs.earthquake.nshmp.HazardCalc; + +/* + * Application and model version data. References are string-based as opposed to + * enum-based (e.g. Edition) to avoid circular references in enum + * initializations. + */ +class Versions { + + + static final String NSHMP_HAZ_VERSION = HazardCalc.VERSION; + static final String NSHMP_HAZ_WS_VERSION; + private static final Map<String, String> MODEL_VERSIONS; + private static final String UNKNOWN = "unknown"; + + static { + String nshmpHazWsVersion = UNKNOWN; + ImmutableMap.Builder<String, String> modelMap = ImmutableMap.builder(); + + /* Always runs from a war (possibly unpacked). */ + try (InputStream in = Metadata.class.getResourceAsStream("/service.properties")){ + Properties props = new Properties(); + props.load(in); + in.close(); + + for (String key : props.stringPropertyNames()) { + String value = props.getProperty(key); + /* Web-services version. */ + if (key.equals("app.version")) { + nshmpHazWsVersion = value; + } + /* Model versions. */ + modelMap.put(key, value); + } + } catch (Exception e) { + /* Do nothing; probably running outside standard build. */ + } + + NSHMP_HAZ_WS_VERSION = nshmpHazWsVersion; + MODEL_VERSIONS = modelMap.build(); + } + + static String modelVersion(String id) { + return MODEL_VERSIONS.getOrDefault(id + ".version", UNKNOWN); + } + +} diff --git a/src/gov/usgs/earthquake/nshmp/www/meta/package-info.java b/src/gov/usgs/earthquake/nshmp/www/meta/package-info.java new file mode 100644 index 000000000..891b3a5f7 --- /dev/null +++ b/src/gov/usgs/earthquake/nshmp/www/meta/package-info.java @@ -0,0 +1,9 @@ +/** + * Web-service metadata support classes. + * + * <p>Classes in this package largely provide support for web services used on + * the public facing USGS website. Services that are not public facing may use a + * simpler metadata structure defined within the service class itself (e.g. + * {@link gov.usgs.earthquake.nshmp.www.SpectraService}. + */ +package gov.usgs.earthquake.nshmp.www.meta; diff --git a/webapp/META-INF/MANIFEST.MF b/webapp/META-INF/MANIFEST.MF new file mode 100644 index 000000000..2f4b56835 --- /dev/null +++ b/webapp/META-INF/MANIFEST.MF @@ -0,0 +1 @@ +Manifest-Version: 1.0 diff --git a/webapp/WEB-INF/web-aws.xml b/webapp/WEB-INF/web-aws.xml new file mode 100644 index 000000000..013efe42e --- /dev/null +++ b/webapp/WEB-INF/web-aws.xml @@ -0,0 +1,65 @@ +<?xml version="1.0" encoding="UTF-8"?> +<web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xmlns="http://java.sun.com/xml/ns/javaee" xmlns:web="http://java.sun.com/xml/ns/javaee" + xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd" + id="WebApp_ID" version="3.0"> + + <display-name>nshmp-haz-ws</display-name> + + <welcome-file-list> + <welcome-file>index.html</welcome-file> + </welcome-file-list> + + <servlet> + <servlet-name>default</servlet-name> + <init-param> + <param-name>listings</param-name> + <param-value>true</param-value> + </init-param> + </servlet> + + <filter> + <filter-name>CorsFilter</filter-name> + <filter-class>org.apache.catalina.filters.CorsFilter</filter-class> + <init-param> + <param-name>cors.allowed.origins</param-name> + <param-value>*</param-value> + </init-param> + <init-param> + <param-name>cors.allowed.methods</param-name> + <param-value>GET,POST,HEAD,OPTIONS,PUT</param-value> + </init-param> + <init-param> + <param-name>cors.allowed.headers</param-name> + <param-value>Content-Type,X-Requested-With,accept,Origin,Access-Control-Request-Method,Access-Control-Request-Headers</param-value> + </init-param> + <init-param> + <param-name>cors.exposed.headers</param-name> + <param-value>Access-Control-Allow-Origin,Access-Control-Allow-Credentials</param-value> + <init-param> + <!-- need to disable credentials for wildcard allow-origin. --> + <param-name>cors.support.credentials</param-name> + <param-value>false</param-value> + </init-param> + <param-name>cors.preflight.maxage</param-name> + <param-value>10</param-value> + </init-param> + </filter> + <filter-mapping> + <filter-name>CorsFilter</filter-name> + <url-pattern>/*</url-pattern> + </filter-mapping> + <filter> + <filter-name>ExpiresFilter</filter-name> + <filter-class>org.apache.catalina.filters.ExpiresFilter</filter-class> + <init-param> + <param-name>ExpiresDefault</param-name> + <param-value>access plus 15 minutes</param-value> + </init-param> + </filter> + <filter-mapping> + <filter-name>ExpiresFilter</filter-name> + <url-pattern>/*</url-pattern> + <dispatcher>REQUEST</dispatcher> + </filter-mapping> +</web-app> diff --git a/webapp/WEB-INF/web.xml b/webapp/WEB-INF/web.xml new file mode 100644 index 000000000..bdbcc24ba --- /dev/null +++ b/webapp/WEB-INF/web.xml @@ -0,0 +1,49 @@ +<?xml version="1.0" encoding="UTF-8"?> +<web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xmlns="http://java.sun.com/xml/ns/javaee" xmlns:web="http://java.sun.com/xml/ns/javaee" + xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd" + id="WebApp_ID" version="3.0"> + + <display-name>nshmp-haz-ws</display-name> + + <welcome-file-list> + <welcome-file>index.html</welcome-file> + </welcome-file-list> + + <servlet> + <servlet-name>default</servlet-name> + <init-param> + <param-name>listings</param-name> + <param-value>true</param-value> + </init-param> + </servlet> + + <filter> + <filter-name>CorsFilter</filter-name> + <filter-class>org.apache.catalina.filters.CorsFilter</filter-class> + <init-param> + <!-- need to disable credentials for wildcard allow-origin. --> + <param-name>cors.support.credentials</param-name> + <param-value>false</param-value> + </init-param> + </filter> + <filter-mapping> + <filter-name>CorsFilter</filter-name> + <url-pattern>/*</url-pattern> + </filter-mapping> + + <filter> + <filter-name>ExpiresFilter</filter-name> + <filter-class>org.apache.catalina.filters.ExpiresFilter</filter-class> + <init-param> + <param-name>ExpiresDefault</param-name> + <param-value>access plus 15 minutes</param-value> + </init-param> + </filter> + + <filter-mapping> + <filter-name>ExpiresFilter</filter-name> + <url-pattern>/*</url-pattern> + <dispatcher>REQUEST</dispatcher> + </filter-mapping> +</web-app> \ No newline at end of file diff --git a/webapp/apps/config.js b/webapp/apps/config.js new file mode 100644 index 000000000..34f97f9b9 --- /dev/null +++ b/webapp/apps/config.js @@ -0,0 +1,7 @@ + +export const CONFIG = { + server: { + static: "https://dev01-earthquake.cr.usgs.gov", + dynamic: "" + } +} diff --git a/webapp/apps/css/D3MapView.css b/webapp/apps/css/D3MapView.css new file mode 100644 index 000000000..d5ac97f16 --- /dev/null +++ b/webapp/apps/css/D3MapView.css @@ -0,0 +1,97 @@ +/* +#################################################### +# +# D3MapView CSS +# +# +##################################################### +*/ + + +/* D3MapView */ +.D3MapView{ + position: relative; + height: 100%; +} + +/* Main map Bootstrap panel */ +.D3MapView>.panel{ + position: absolute; + top: 0; + right: 0; + bottom: 0; + left: 0; + margin: 0; +} + +/* Map: Bootstrap panel body */ +.D3MapView .map{ + height: 55%; + padding: 0; + background-color: #f5f5f5; +} + +/* Control panel: Bootstrap panel footer */ +.D3MapView .control-panel{ + position: absolute; + top: 55%; + right: 0; + bottom: 0; + left: 0; + padding: 0; + overflow: scroll; + background-color: white; +} + +/* All panels */ +.D3MapView .panel{ + margin: 0; +} + +/* Buttons in forms */ +.D3MapView .form-group .panel .btn{ + text-align: left; + border: none; +} + + +.D3MapView .col-xs-4{ + height: 100%; +} + +.D3MapView #test-site-form{ + height: 100%; + width: 100%; +} + +.D3MapView #test-site-form .panel{ + height: 90%; + overflow-x: scroll; + overflow-y: scroll; +} + +.D3MapView .btn-group-vertical{ + width: 100%; +} + + +.D3MapView .form-group{ + vertical-align: top; +} + +.D3MapView .form-control-panel{ + margin: 0; +} + + +.D3MapView form{ + position: absolute; + width: 100%; + height: 100%; + +} + + + + + diff --git a/webapp/apps/css/D3View.css b/webapp/apps/css/D3View.css new file mode 100644 index 000000000..5d6c80710 --- /dev/null +++ b/webapp/apps/css/D3View.css @@ -0,0 +1,310 @@ +/** +* CSS for D3View class +* +*/ + +/* D3 View */ +.D3View { + position: relative; +} + +/* Plot panel header */ +.D3View .panel .panel-heading { + background-color: #f7f7f7; + font-family: 'HelveticaNeue-Light',sans-serif; + position: relative; +} + +.D3View .panel-title { + font-size: 1.25em; +} + +.D3View .plot-title { + display: inline-block; + min-width: 30%; +} + +/* Plot panel body */ +.D3View .panel-body { + padding: 0; + line-height: 1.5; +} + +.D3View .panel-outer > .panel-body + .panel-body { + border-top: 1px solid #ddd; + position: relative; +} + +/* Plot panel footer */ +.D3View .panel .panel-footer { + background-color: #f7f7f7; + font-size: 1em; + position: relative; +} + +.D3View .footer-btn-toolbar { + margin: 0 1.25em 0 0; +} + +.D3View .footer-btn-group { + padding: 0 1%; +} + +.D3View .footer-button input { + height: 0; +} + +.D3View .panel-footer .dropdown-header { + font-weight: 400; +} + +.D3View .panel-footer label { + font-weight: initial; + font-size: 0.75em; +} + +/* Plot panel body data table */ +.D3View .panel-table td { + font-size: 0.75em; + vertical-align: initial; +} + +.D3View .panel-table th { + font-size: 1em; + font-weight: 550; + vertical-align: initial; + width: 50%; +} + +.D3View .panel-table tr tr td { + padding: 2px 5px; +} + +.D3View .panel-table .data-table-title { + font-size: 1.25em; + font-weight: 800; +} + +.D3View .panel-table { + overflow-x: scroll; + overflow-y: scroll; +} + +.D3View .panel-table .table { + margin-bottom: 2em; +} + +/* Panel glyphicons */ +.D3View .icon { + cursor: pointer; + font-size: 1em; + font-weight: 100; + line-height: 100%; + position: absolute; + right: 1em; + top: 50%; + transform: translateY(-50%); + z-index: 1000; +} + +.D3View .d3-tooltip { + line-height: 1.5; +} + +@media only screen and (max-width: 992px) { + .D3View .panel-footer label { + font-size: 0.45em; + } + + .D3View .plot-title { + font-size: 0.75em; + } + + .D3View .glyphicon { + font-size: 0.75em; + } + + .D3View .panel-heading { + padding: 5px 15px; + } + + .D3View .panel-footer { + padding: 5px 15px; + } +} + +@media only screen and (max-width: 768px) { + .D3View .panel-footer label { + font-size: 0.3em; + } + + .D3View .plot-title { + font-size: 0.5em; + } + + .D3View .panel-heading { + padding: 2.5px 7.5px; + } + + .D3View .panel-footer { + padding: 2.5px 7.5px; + } +} + +@media only screen and (min-width: 1450px) { + .D3View.col-xl-12 { + width: 100%; + } + .D3View.col-xl-11 { + width: 91.66666667%; + } + .D3View.col-xl-10 { + width: 83.33333333%; + } + .D3View.col-xl-9 { + width: 75%; + } + .D3View.col-xl-8 { + width: 66.66666667%; + } + .D3View.col-xl-7 { + width: 58.33333333%; + } + .D3View.col-xl-6 { + width: 50%; + } + .D3View.col-xl-5 { + width: 41.66666667%; + } + .D3View.col-xl-4 { + width: 33.33333333%; + } + .D3View.col-xl-3 { + width: 25%; + } + .D3View.col-xl-2 { + width: 16.66666667%; + } + .D3View.col-xl-1 { + width: 8.33333333%; + } + + .D3View.col-xl-offset-12 { + margin-left: 100%; + } + .D3View.col-xl-offset-11 { + margin-left: 91.66666667%; + } + .D3View.col-xl-offset-10 { + margin-left: 83.33333333%; + } + .D3View.col-xl-offset-9 { + margin-left: 75%; + } + .D3View.col-xl-offset-8 { + margin-left: 66.66666667%; + } + .D3View.col-xl-offset-7 { + margin-left: 58.33333333%; + } + .D3View.col-xl-offset-6 { + margin-left: 50%; + } + .D3View.col-xl-offset-5 { + margin-left: 41.66666667%; + } + .D3View.col-xl-offset-4 { + margin-left: 33.33333333%; + } + .D3View.col-xl-offset-3 { + margin-left: 25%; + } + .D3View.col-xl-offset-2 { + margin-left: 16.66666667%; + } + .D3View.col-xl-offset-1 { + margin-left: 8.33333333%; + } + .D3View.col-xl-offset-0 { + margin-left: 0; + } +} + +@media only screen and (min-width: 1700px) { + .D3View.col-xxl-12 { + width: 100%; + } + .D3View.col-xxl-11 { + width: 91.66666667%; + } + .D3View.col-xxl-10 { + width: 83.33333333%; + } + .D3View.col-xxl-9 { + width: 75%; + } + .D3View.col-xxl-8 { + width: 66.66666667%; + } + .D3View.col-xxl-7 { + width: 58.33333333%; + } + .D3View.col-xxl-6 { + width: 50%; + } + .D3View.col-xxl-5 { + width: 41.66666667%; + } + .D3View.col-xxl-4 { + width: 33.33333333%; + } + .D3View.col-xxl-3 { + width: 25%; + } + .D3View.col-xxl-2 { + width: 16.66666667%; + } + .D3View.col-xxl-1 { + width: 8.33333333%; + } + + .D3View.col-xxl-offset-12 { + margin-left: 100%; + } + .D3View.col-xxl-offset-11 { + margin-left: 91.66666667%; + } + .D3View.col-xxl-offset-10 { + margin-left: 83.33333333%; + } + .D3View.col-xxl-offset-9 { + margin-left: 75%; + } + .D3View.col-xxl-offset-8 { + margin-left: 66.66666667%; + } + .D3View.col-xxl-offset-7 { + margin-left: 58.33333333%; + } + .D3View.col-xxl-offset-6 { + margin-left: 50%; + } + .D3View.col-xxl-offset-5 { + margin-left: 41.66666667%; + } + .D3View.col-xxl-offset-4 { + margin-left: 33.33333333%; + } + .D3View.col-xxl-offset-3 { + margin-left: 25%; + } + .D3View.col-xxl-offset-2 { + margin-left: 16.66666667%; + } + .D3View.col-xxl-offset-1 { + margin-left: 8.33333333%; + } + .D3View.col-xxl-offset-0 { + margin-left: 0; + } +} diff --git a/webapp/apps/css/Gmm.css b/webapp/apps/css/Gmm.css new file mode 100644 index 000000000..725a3408c --- /dev/null +++ b/webapp/apps/css/Gmm.css @@ -0,0 +1,176 @@ +/* +#################################################### +# +# CSS for the Spectra and GmmDistance webpage +# +# +##################################################### +*/ + + +.D3View .fault-form .row{ + margin: 0; +} + +.D3View label{ + margin: 0; + font-weight: 700; +} + +.fault-form{ + position: absolute; + top: 0; + right: 0; + bottom: 0; + left: 65%; + border-left: 1px solid #ddd; + padding: 1em 0.5em 0.5em 0.5em; + height: 100%; + overflow: auto; + font-size: 0.80vw; +} + +.D3View .fault-form .form-control{ + height: 2vw; + font-size: 0.75em; + line-height: 1.5; + padding: 0 0.5em; +} + +.panel-lower .fault-form .input-group-addon{ + height: 2vw; + font-size: 0.75em; + line-height: 1; +} + +.panel-lower .fault-form [class*="col-"] { + padding: 0.2em; +} + +.D3View .fault-form .form-group{ + margin: 0; + padding: 0 0.25em; +} + +.D3View .fault-form .slider-form{ + margin-bottom: 0.50em; +} + +.slider{ + margin-top: 0.45em; + -webkit-appearance: none; + width: 100%; + height: 0.8em; + border-radius: 0.5em; + background: #eee; + border: 1px solid #ccc; +} + + +.slider:focus{ + outline: transparent; +} + +.slider::-webkit-slider-thumb{ + -webkit-appearance: none; + appearance: none; + height: 1em; + width: 1em; + border-radius: 50%; + background: #337ab7; + border-color: #2e6da4; + cursor: pointer; +} + +.slider::-webkit-slider-thumb:active{ + height: 1.35em; + width: 1.35em; +} + +.slider::-ms-fill-lower{ + background-color: #337ab7 +} + + +optgroup { + color: SteelBlue; +} + + +#gmm-sorter { + float: right; +} + + + +#addata { + float: right; + /*margin-top: 20px;*/ +} + + + +.form-group-sm .form-control { + height: 24px; + padding: 2px 4px; +} + +.input-group-sm .input-group-addon { + height: 24px; + padding: 2px 4px; +} + +#control [class*="col-sm-"], +#control [class*='col-xs-'] { + padding-left: 2px; + padding-right: 2px; +} + +.form-horizontal .form-group-sm .units { + text-align: left; + padding-top: 4px; +} + +.secondary-input { + font-weight: normal; +} + +.disabled { + color: #aaa; +} + +.btn-group { + padding-top: 1px; +} + + +/* plot */ + +/*text { + font: 10px sans-serif; +} +*/ + +.axis-label { + font-size: 1.2em; + font-weight: 500; + text-anchor: middle; +} + + +.axis path, +.axis line { + fill: none; + stroke: #000; + shape-rendering: crispEdges; +} + +.legend line, +.lines { + fill: none; + stroke-width: 2.5px; + stroke-linejoin: round; + stroke-linecap: round; +} + + diff --git a/webapp/apps/css/MetadataPrint.css b/webapp/apps/css/MetadataPrint.css new file mode 100644 index 000000000..b93cf8de1 --- /dev/null +++ b/webapp/apps/css/MetadataPrint.css @@ -0,0 +1,22 @@ +/* +* D3SaveMetadata printing css. +*/ + +@media print { + html { + margin: 0 !important; + padding: 0 !important; + } + + body { + margin: 0.25in !important; + padding: 0 !important; + } + + header, footer { + margin: 0; + padding: 0; + display: none !important; + } + +} diff --git a/webapp/apps/css/PrintFigure.css b/webapp/apps/css/PrintFigure.css new file mode 100644 index 000000000..620c14e33 --- /dev/null +++ b/webapp/apps/css/PrintFigure.css @@ -0,0 +1,38 @@ +/* +* Styling for printing in PDF or to printer +*/ + +body { + margin: 0; + padding: 0; +} + +@media print { + html { + height: 11in !important; + width: 8.5in !important; + margin: 0 !important; + padding: 0 !important; + transform: rotate(90deg); + -webkit-transform: rotate(90deg); + -moz-transform:rotate(90deg); + filter:progid:DXImageTransform.Microsoft.BasicImage(rotation=3); + } + + body { + margin: 0 !important; + padding: 0 !important; + } + + img { + margin: 0; + padding: 0; + } + + header, footer { + margin: 0; + padding: 0; + display: none !important; + } + +} diff --git a/webapp/apps/css/dashboard.css b/webapp/apps/css/dashboard.css new file mode 100644 index 000000000..876fe91dc --- /dev/null +++ b/webapp/apps/css/dashboard.css @@ -0,0 +1,31 @@ + + +#container { + position: fixed; + top: 0; + right:0; + bottom:0; + left:0; + margin: 2em 0 3.5em 0; + padding: 20px; + overflow: auto; + display: flex; +} + +#dash { + width: 100%; + margin: auto; +} + +.panel .panel-footer { + background-color: #f7f7f7; + font-family: 'HelveticaNeue-Light',sans-serif; +} + +.panel-body { + padding: 0; +} + +.panel:hover { + cursor: pointer; +} diff --git a/webapp/apps/css/header.css b/webapp/apps/css/header.css new file mode 100644 index 000000000..7fc1943a6 --- /dev/null +++ b/webapp/apps/css/header.css @@ -0,0 +1,20 @@ + +#header { + background: black url('../img/usgs_logo.png') 0px/84px no-repeat; + background-position: 3px 3px; + position: fixed; + z-index: 100; + top: 0; + left: 0; + width: 100%; + height: 30px; + padding-right: 8px; + font-size: 18px; + font-weight: 300; + color: white; + text-align: right; + line-height: 30px; + box-sizing: border-box; + box-shadow: 0px 0px 3px 2px #666666; + -webkit-font-smoothing: subpixel-antialiased; +} diff --git a/webapp/apps/css/location.css b/webapp/apps/css/location.css new file mode 100644 index 000000000..9d95fc90a --- /dev/null +++ b/webapp/apps/css/location.css @@ -0,0 +1,143 @@ +/* +#################################################### +# +# Location CSS +# +# +##################################################### +*/ + + +/*###################################################*/ +/**/ +/*................... Main Content ..................*/ + +#content{ + position: fixed; + top: 0; + left: 0; + bottom: 0; + right: 0; + margin: 2em 0 3.5em 0; +} +/*-------------- End: Main Content -------------------*/ +/**/ +/*###################################################*/ + + + +/* +#testsite-menu{ + width: 100%; +} + +#menu-text{ + float: left; +} + +.caret{ + float:right; +} + + + +#testsite>li{ + padding-left: 10px; +} + +*/ + + +.list-group-item{ + font-size: 12px; + padding: 5px 10px; +} + +#region{ + width: 100%; + font-size: 12px; +} + + +#testsite{ + height: 250px; + overflow-x: scroll; + padding: 0; +} + +#testsite{ + width: 100%; +} + +#testsite>label{ + font-size: 11px; +} + + +#map-checkbox{ + position: absolute; + bottom: 20px; + padding: 10px 0; +} + + +#inputs, +.form-horizontal{ + height: 100%; +} + + +/*#################################################*/ +/**/ +/*.................... Plots ......................*/ + + +/*............... Plot Panel Container ............*/ +#content>.map-panel{ + position: absolute; + padding: 20px 0 20px 20px; + top:0; + right: 300px; + bottom: 0; + left:0; +} +/*-------------------------------------------------*/ + +/*............... Plot Panel Container ............*/ +#content>.control-panel{ + position: absolute; + padding: 20px 20px 20px 0; + top:0; + right: 0; + bottom: 0; + width: 300px; +} +/*-------------------------------------------------*/ + +#map{ + margin: 0 auto; + padding: 0; +} + +.panel, +.panel-body{ + height: 100%; +} + +#content>.map-panel>.panel{ + border-top-right-radius: 0px; + border-bottom-right-radius: 0px; +} + +#content>.control-panel>.panel{ + border-top-left-radius: 0px; + border-bottom-left-radius: 0px; + background-color: #f7f7f7; +} + +/*------------- End: Plots ------------------------*/ +/**/ +/*#################################################*/ + + + diff --git a/webapp/apps/css/model-compare.css b/webapp/apps/css/model-compare.css new file mode 100644 index 000000000..cba383ac3 --- /dev/null +++ b/webapp/apps/css/model-compare.css @@ -0,0 +1,28 @@ +/* +#################################################### +# +# CSS for the model-compare webpage +# +# +##################################################### +*/ + + + + +/*#################################################*/ +/**/ +/*............... Lat/Lon Bounds ..................*/ + +#lat-bounds, +#lon-bounds{ + color: black; + opacity: 0.8; +} +/*------------- End: Lat/Lon Bounds ---------------*/ +/**/ +/*#################################################*/ + + + + diff --git a/webapp/apps/css/model-explorer.css b/webapp/apps/css/model-explorer.css new file mode 100644 index 000000000..3b7071a34 --- /dev/null +++ b/webapp/apps/css/model-explorer.css @@ -0,0 +1,27 @@ +/* +#################################################### +# +# CSS for the model-explorer webpage +# +# +##################################################### +*/ + + + +/*#################################################*/ +/**/ +/*............... Lat/Lon Bounds ..................*/ + +#lat-bounds, +#lon-bounds{ + color: black; + opacity: 0.8; +} +/*------------- End: Lat/Lon Bounds ---------------*/ +/**/ +/*#################################################*/ + + + + diff --git a/webapp/apps/css/services.css b/webapp/apps/css/services.css new file mode 100644 index 000000000..fa7b14fb8 --- /dev/null +++ b/webapp/apps/css/services.css @@ -0,0 +1,91 @@ +/* CSS for services.html and Services.js */ + + +/* Service panel heading */ +.panel .panel-heading>* { + margin-bottom: 0; + margin-top: 0; +} + +/* Lists inside service panels */ +.service .list-group-item { + border: none; + padding: 0.25em 1em; +} + +/* Containers */ +.services { + overflow: scroll; +} + +.content { + margin-top: 2.5em; +} + +/* Service nav menu */ +.service-menu { + padding-left: 3em; + padding-top: 2em; +} + +.service-menu-list>li>a { + color: #767676;; + font-weight: 400; + line-height: 1.25; + padding: 0.25em; +} + +.service-menu .back-to-top { + padding-top: 1.25em; +} + +.service-menu .back-to-top>a { + color: #999; +} + +/* Service panel */ +.anchor { + padding-top: 2em; +} +.service { + padding-top: 2.5em; +} + +.service:last-of-type { + padding-bottom: 5em; +} + +.service .service-div>* { + margin-top: 0; +} + +.service .service-div { + padding: 0.5em 0; +} + +/* Service panel footer */ +.panel-footer { + background-color: white; +} + +.examples { + color: #959595; + font-size: 12px; + font-weight: 700; + letter-spacing: 1px; + padding-bottom: 0.5em; + text-transform: uppercase; +} + +/* URL formats */ +.format-url { + padding-bottom: 6px; + word-break: break-all; + word-wrap: break-word; +} + +.service-link { + padding-bottom: 6px; + word-break: break-all; + word-wrap: break-word; +} diff --git a/webapp/apps/css/styles.css b/webapp/apps/css/styles.css new file mode 100644 index 000000000..7459906a8 --- /dev/null +++ b/webapp/apps/css/styles.css @@ -0,0 +1,77 @@ +body { + margin: 36px 12px 12px 12px; +} + +/* +h1, h2, h3 { + font-weight: 500; +} +*/ + +#header { + background: black url('../img/usgs_logo.png') 0px/84px no-repeat; + background-position: 3px 3px; + position: fixed; + z-index: 100; + top: 0; + left: 0; + width: 100%; + height: 30px; + padding-right: 8px; + font-size: 18px; + font-weight: 300; + color: white; + text-align: right; + line-height: 30px; + box-sizing: border-box; + box-shadow: 0px 0px 3px 2px #666666; + -webkit-font-smoothing: subpixel-antialiased; +} + +#status, #model1, #model2 { + float: right; + display: inline-block; + padding: 6px 12px; + margin-bottom: 12px; + clear: right; +} + +.service { + width: 640px; + margin-bottom: 24px; + border-bottom: 1px solid #ddd; +} + +.service:last-of-type { + border: 0px; +} + +.serviceExample { + position: relative; + padding: 45px 15px 0px; + margin: 20px 32px 20px 10px; + border: 1px solid #e5e5e5; + border-radius: 4px; + -webkit-box-shadow: inset 0 3px 6px rgba(0, 0, 0, .05); + box-shadow: inset 0 3px 6px rgba(0, 0, 0, .05) +} + +.serviceExample:after { + position: absolute; + top: 15px; + left: 15px; + font-size: 12px; + font-weight: 700; + color: #959595; + text-transform: uppercase; + letter-spacing: 1px; + content: "Examples"; + box-sizing: border-box; +} + +.serviceLink { + word-wrap: break-word; + word-break: break-all; + padding-bottom: 6px; +} + diff --git a/webapp/apps/css/template.css b/webapp/apps/css/template.css new file mode 100644 index 000000000..92fb3174e --- /dev/null +++ b/webapp/apps/css/template.css @@ -0,0 +1,610 @@ +/* +###################################################### +# +# Main CSS for webapps with a control panel: +# - spectra-plot +# - model-explorer +# +###################################################### +*/ + + +/* Test Site View Class */ + +.test-site-view.in { + display: flex !important; +} + +.test-site-view .modal-body { + padding: 0 15px; + height: 200px; +} + +.test-site-view .modal-dialog { + margin: auto; + padding: 1em; +} + +@media (min-width: 1200px) { + .test-site-view .modal-lg { + width: 1100px; + } +} + +@media (max-width: 768px) { + .test-site-view .modal-dialog { + width: 100%; + } + + .test-site-view #site-list label { + font-size: 0.5em; + } +} + +@media (min-height: 500px) { + .test-site-view .modal-body { + padding: 0 15px; + height: 300px; + } +} + +@media (min-height: 700px) { + .test-site-view .modal-body { + padding: 0 15px; + height: 500px; + } +} + +@media (min-height: 900px) { + .test-site-view .modal-body { + padding: 0 15px; + height: 700px; + } +} + +.test-site-view #map-body { + bottom: 0; + left: 0; + position: absolute; + top: 0; +} + +.test-site-view #site-list-body { + bottom: 0; + overflow: scroll; + position: absolute; + right: 0; + top: 0; +} + +.test-site-view #site-list-body .form-group { + margin: 0; +} + +.test-site-view #site-list { + width: 100%; +} + +/*###################################################*/ +/**/ +/*.................... Main Body ....................*/ + +html, body{ + width: 100%; + height:100%; + -webkit-font-smoothing: subpixel-antialiased; +} +/*------------------ End: Main Body -----------------*/ +/**/ +/*###################################################*/ + + +.vertical-center { + top: 50% !important; + transform: translate(0, -50%) !important; +} + + +/*###################################################*/ +/**/ +/*..................... Header ......................*/ + +/*..................... Header ......................*/ +#header{ + background: black url("../img/usgs_logo.png") 0px/84px no-repeat; + background-position: 3px 3px; + position: fixed; + color: white; + z-index: 100; + top: 0; + left: 0; + right: 0; + height: 1.6em; + line-height: 1.6em; + text-align: right; + padding-right: 8px; + font-weight: 300; + font-size: 18px; + box-sizing: border-box; + box-shadow: 0px 0px 3px 2px #999; +} +/*---------------------------------------------------*/ + + +/*................. Header Title ....................*/ +#header .title{ + padding-right: 8px; +} +/*---------------------------------------------------*/ + + +/*................. Header Menu .....................*/ +#header #header-menu{ + float:right; +} +/*---------------------------------------------------*/ + + +/*........... Header Glyphicon Hover.................*/ +#header .glyphicon-menu-hamburger:hover{ + cursor:pointer; +} +/*---------------------------------------------------*/ + + +/*............. Header Glyphicon ....................*/ +#header .glyphicon{ + top: 3px; +} +/*---------------------------------------------------*/ + +/*-------------- End: Header ------------------------*/ +/**/ +/*###################################################*/ + + + + +/*###################################################*/ +/**/ +/* .................... Footer ..................... */ + +.Footer{ + position: absolute; + left: 0; + right: 0; + bottom: 0; + background-color: #f7f7f7; + border-top: 1px solid #bbb; + box-shadow: 0px 0px 3px 1px #ccc; + z-index: 101; + height: 3.5em; +} + +.Footer .settings-btn{ + padding-left: 10px; + cursor: pointer; +} + +.Footer .footer-btns { + position: absolute; + left: 8em; + right: 8em; + top: 50%; + transform: translateY(-50%); +} + +.Footer .footer-icons { + position: absolute; + left: calc(100% - 8em); + top: 50%; + transform: translateY(-50%); +} + +.Footer .code-info-icon { + cursor: pointer; + font-size: 2.0em; + vertical-align: middle; + margin: 0 0.5em; +} + +.Footer .github-icon { + cursor: pointer; + width: 2.0em; + margin: 0 0.5em; +} + +.Footer .code-info-icon:focus { + outline: none; +} + +.Footer .code-info-collapse { + bottom: 100%; + left: 400px; + position: absolute; + right: 0; +} + +.Footer .well { + background-color: white; + margin: 10px 2px 3px 2px; + padding: 10px; +} + +.Footer .code-info-icon.disabled { + color: grey; + cursor: not-allowed; + opacity: 0.65; +} + +/*-------------- End: Footer ----------------------- */ +/**/ +/*###################################################*/ + + + + +/*###################################################*/ +/**/ +/*.................. Control Panel ..................*/ + +/*.................. Control Panel ..................*/ +#control { + background-color: #f7f7f7; + position: fixed; + top: 0; + left: 0; + bottom: 0; + margin: 2em 0 3.5em 0; + padding:20px; + width: 400px; + overflow: auto; + border-right: 1px solid #ddd; +} + +#control .btn-group .btn+.btn { + margin-left: 0; +} + +/*.................... Forms .......................*/ +#control .form-horizontal .form-group { + margin-right: -5px; + margin-left: -5px; + margin-bottom: 6px; + padding: 0.25em; +} + + +#control option:disabled { + color:#999; +} + +#control option { + color: black; +} + + +label { + font-weight: 500; +} + +label.control-group { + font-weight: 700; +} + + +label.control-spacer { + margin-top: 8px; +} + +#control .form-inline label small { + font-weight: normal; +} + +#control .form-horizontal .form-control { + font-size: 12px; +} + +#control .form-horizontal .form-group-sm .control-label { + padding-top: 3px; +} + +#control .control-panel-slider { + margin-top: 0.45em; + -webkit-appearance: none; + appearance: none; + height: 0.8em; + border-radius: 0.5em; + background: #eee; + border: 1px solid #ccc; +} + +#control .control-panel-slider:disabled { + cursor: not-allowed; + opacity: 0.5; +} + +#control .control-panel-slider:focus { + outline: transparent; +} + +#control .control-panel-slider::-webkit-slider-thumb { + -webkit-appearance: none; + appearance: none; + height: 1em; + width: 1em; + border-radius: 50%; + background: #337ab7; + border-color: #2e6da4; + cursor: pointer; +} + +#control .control-panel-slider::-webkit-slider-thumb:active { + height: 1.35em; + width: 1.35em; +} + +#control .control-panel-slider::-ms-fill-lower { + background-color: #337ab7 +} + +#control .btn-group>.btn.focus { + background-color: initial; +} + +#control .btn-group>.btn.focus.active { + background-color: #d4d4d4; +} +/*----------- End: Control Panel -------------------*/ +/**/ +/*###################################################*/ + + + + +/*###################################################*/ +/**/ +/*................... Main Content ..................*/ + +#content{ + position: fixed; + top: 0; + left: 0; + bottom: 0; + right: 0; + margin: 2em 0 3.5em 400px; + overflow: auto; + padding: 20px 0; +} +/*-------------- End: Main Content -------------------*/ +/**/ +/*###################################################*/ + + + +/*#########################################################*/ +/**/ +/*................... Setting Menu .......................*/ + + +/*............. Screen Overlay ......................*/ +.Settings .settings-overlay{ + position: absolute; + top: 0; + right: 0; + bottom: 0; + left: 0; + background-color: black; + opacity: 0.20; + z-index: 1000; +} +/*---------------------------------------------------*/ + +/*............. Loader Box to Put Spinner ...........*/ +.Settings .settings-panel{ + position: absolute; + top: 50%; + left: 50%; + transform: translateX(-50%) translateY(-50%); + height: 50%; + width: 50%; + box-sizing: border-box; + box-shadow: 0 3px 6px rgba(0,0,0,0.16), 0 3px 6px rgba(0,0,0,0.23); + z-index: 1001; + background-color: white; +} +/*---------------------------------------------------*/ + +.Settings .settings-panel .panel-heading{ + height: 2.5em; +} + +.Settings .settings-panel .panel-footer{ + height: 3.5em; + padding: 8px; +} + +.Settings .settings-panel .panel-body{ + height: calc(100% - 6em); + padding: 15px; +} + + +/*-------------- End: Setting Menu -----------------------*/ +/**/ +/*########################################################*/ + + + + +/*###################################################*/ +/**/ +/*................... Spinner .......................*/ + +.test-site-modal.in { + display: flex !important; +} + +.test-site-modal .modal-dialog { + display: flex; + flex-direction: column; + justify-content: center; +} + +.Spinner { + display: flex !important; +} + +.Spinner .modal-dialog { + margin: auto; + width: auto; +} + +.Spinner .spinner-text { + font-size: 1.5em; + padding: 1em 0 0 0; + margin: 0; +} + +.Spinner .modal-content { + height: auto; + width: 12em; +} + +.Spinner .modal-content { + text-align: center; +} + +.Spinner .modal-footer { + text-align: center; +} + +.Spinner .loading-spinner { + margin: 0.25em auto; + border: 0.7em solid #E0E0E0; + border-radius: 50%; + border-top: 0.7em solid #337ab7; + width: 4em; + height: 4em; + -webkit-animation: spin 2s linear infinite; + animation: spin 2s linear infinite; +} + +@-webkit-keyframes spin { + 0% { -webkit-transform: rotate(0deg); } + 100% { -webkit-transform: rotate(360deg); } +} + +@keyframes spin { + 0% { transform: rotate(0deg); } + 100% { transform: rotate(360deg); } +} +/*-------------- End: Spinner -----------------------*/ +/**/ +/*###################################################*/ + + + + + +/*###################################################*/ +/**/ +/*................... Axes Buttons ..................*/ + + +.axes-btns>.form-group{ + margin: 0 2%; + font-size: 12px; +} + +.axes-btns>.form-group>.btn-group>.btn{ + width: 55px; + margin: 5px 0; + text-align: center; +} + +/*-------------- End: Axes Buttons ------------------*/ +/**/ +/*###################################################*/ + + + + +/*#################################################*/ +/**/ +/*.................... Plots ......................*/ + + + +/*............... Plot Panel Container ............*/ +#content>.plot-panel{ + position: relative; + height: 100%; + padding:20px; +} +/*-------------------------------------------------*/ + + +/*............... Plot Panel ......................*/ +#content>.plot-panel>.panel{ + position: relative; + height: 100%; +} +/*-------------------------------------------------*/ + + +/*............... Plot Panel Header ...............*/ +#content>.plot-panel>.panel>.panel-heading{ + background-color: #f7f7f7; + font-family: 'HelveticaNeue-Light',sans-serif; + font-size: 1.25em; +} +/*-------------------------------------------------*/ + + +/*............... Plot Panel Content ...............*/ +#content>.plot-panel>.panel>.panel-content{ + position: relative; + height: 100%; + margin: 0 auto; +} +#content>.plot-panel>.panel>.panel-body{ + position: relative; + height: 100%; + margin: 0 auto; + padding: 0; +} +/*-------------------------------------------------*/ + + +/*............... Plot Panel Footer ...............*/ +#content>.plot-panel>.panel>.panel-footer{ + background-color: #f7f7f7; + position: absolute; + right: 0; + bottom: 0; + left: 0; + padding: 5px 0; +} +/*-------------------------------------------------*/ + + +/*............... Plot Resize ....................*/ +#content>.plot-panel>.panel>.panel-heading>.plot-resize{ + float: right; + font-size: .85em; + font-weight: 100; + opacity: .7; +} + +#content>.plot-panel>.panel>.panel-heading>.plot-resize:hover{ + cursor: pointer; +} +/*------------------------------------------------*/ + + +/*------------- End: Plots ------------------------*/ +/**/ +/*#################################################*/ + + diff --git a/webapp/apps/css/test-sites.css b/webapp/apps/css/test-sites.css new file mode 100644 index 000000000..c1e397865 --- /dev/null +++ b/webapp/apps/css/test-sites.css @@ -0,0 +1,13 @@ + + + + +#testsites{ + position: fixed; + top: 0; + right: 0; + bottom: 0; + left: 0; + margin: 2em 0 3.5em 0; + overflow: auto; +} diff --git a/webapp/apps/css/util.css b/webapp/apps/css/util.css new file mode 100644 index 000000000..22ed20171 --- /dev/null +++ b/webapp/apps/css/util.css @@ -0,0 +1,29 @@ +/* +#################################################### +# +# Utilities CSS +# +# +##################################################### +*/ + + +/*###################################################*/ +/**/ +/*................... Main Content ..................*/ + +#content{ + position: fixed; + top: 0; + left: 0; + bottom: 0; + right: 0; + margin: 2em 0 3.5em 0; + padding: 20px; + overflow: auto; +} +/*-------------- End: Main Content -------------------*/ +/**/ +/*###################################################*/ + + diff --git a/webapp/apps/dynamic-compare.html b/webapp/apps/dynamic-compare.html new file mode 100644 index 000000000..c911feabd --- /dev/null +++ b/webapp/apps/dynamic-compare.html @@ -0,0 +1,173 @@ +<!doctype html> +<html lang="en"> + +<!-- Head --> +<head> + <meta name="viewport" content="width=device-width, initial-scale=1.0"> + <meta charset="UTF-8"> + + <!-- CSS files --> + <link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css"> + <link rel="stylesheet" href="https://unpkg.com/leaflet@1.3.1/dist/leaflet.css" /> + + <link rel="stylesheet" href="css/template.css" type="text/css"> + <link rel="stylesheet" href="css/D3View.css" type="text/css"> + <!-- End: CSS files --> +</head> +<!-- End: Head --> + +<!-- Body --> +<body> + + <!-- The control panel --> + <div class='hidden' id="control" > + <form id="inputs"> + <div class="form-horizontal"> + + <!-- First model select menu --> + <div class="form-group"> + <label class="control-spacer control-group" + for="fist-model"> Model: </label> + <select class="form-control model" id="first-model"> + </select> + </div> + <!-- End: First model select menu --> + + <!-- Second model select menu --> + <div class="form-group"> + <label class="control-spacer control-group" + for="second-model"> Second Model: </label> + <select class="form-control model" id="second-model"> + </select> + </div> + <!-- End: Second model select menu --> + + <!-- Latitude input --> + <div class="form-group form-group-sm form-inline" id="lat-form"> + <label class="control-spacer control-group" + for="lat"> Latitude: + </label> <br> + <div class="input-group"> + <input id="lat" class="form-control" + type="text" name="latitude" /> + <div class="input-group-addon">°</div> + </div> + </div> + <!-- End: Latitude input --> + + <!-- Logitude input --> + <div class="form-group form-group-sm form-inline" id="lon-form"> + <label class="control-spacer control-group" + for="lon"> Longitude: + </label> <br> + <div class="input-group"> + <input id="lon" class="form-control" + type="text" name="longitude" /> + <div class="input-group-addon">°</div> + </div> + </div> + <!-- End: Logitude input --> + + <!-- Choose location on map --> + <div class='form-group form-group-sm'> + <button class="btn btn-default" id='test-site-picker' type='button'> + Choose a test site + </button> + </div> + <!-- End: Choose location on map --> + + <!-- IMT select menu --> + <div class="form-group form-group-sm"> + <label class="control-spacer control-group" for="imt"> + Intensity Measure Type: + </label> + <select class="form-control" id="imt"> + </select> + </div> + <!-- End: IMT select menu --> + + <!-- Vs30 select menu --> + <div class="form-group form-group-sm"> + <label class="control-spacer control-group" for="vs30"> + V<sub>s</sub>30: + </label> + <select class="form-control" id="vs30" name="vs30"> + </select> + </div> + <!-- End: Vs30 select menu --> + + <!-- Return period input --> + <div class="form-group form-group-sm form-inline" id="period-form"> + <label class="control-spacer control-group" + for="return-period"> Return Period: + </label> <br> + <div class="input-group"> + <input id="return-period" class="form-control" + type="number" step="20" /> + </div> + </div> + <!-- End: Return period input --> + + <!-- Return period buttons --> + <div class='btn-group btn-group-xs btn-group-justified' + id='return-period-btns' data-toggle='buttons' role='group'> + + <label class='btn btn-default' id='return-period-2475' + for='return-period-input-2475'> + <input type='radio' id='return-period-input-2475' value='2475'/> + 2% in 50 years <br> <small> (2,475 years) </small> + </label> + + <label class='btn btn-default' id='return-period-975' + for='return-period-input-975'> + <input type='radio' id='return-period-input-975' value='975'/> + 5% in 50 years <br> <small> (975 years) </small> + </label> + + <label class='btn btn-default' id='return-period-475' + for='return-period-input-475'> + <input type='radio' id='return-period-input-475' value='475'/> + 10% in 50 years <br> <small> (475 years) </small> + </label> + </div> + <!-- End: Return period buttons --> + + </div> + </form> + </div> + <!-- End: The control panel --> + + + <!-- Plots --> + <div id="content"> + </div> + <!-- End: Plots --> + + + <!-- JavaScript --> + + <!-- jQuery --> + <script src="https://ajax.googleapis.com/ajax/libs/jquery/3.2.1/jquery.min.js"> + </script> + + <!-- Bootstrap --> + <script src="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/js/bootstrap.min.js"> + </script> + + <!-- Application Specific JavaScript --> + <script src="https://d3js.org/d3.v4.min.js" charset="utf-8"></script> + <script src="https://d3js.org/topojson.v2.min.js"></script> + <script src="https://unpkg.com/leaflet@1.3.1/dist/leaflet.js"></script> + + <script type='module'> + import { CONFIG } from './config.js'; + import { DynamicCompare } from './js/DynamicCompare.js'; + + new DynamicCompare(CONFIG); + </script> + <!-- End: JavaScript --> + +</body> +<!-- End: Body --> + +</html> diff --git a/webapp/apps/exceedance-explorer.html b/webapp/apps/exceedance-explorer.html new file mode 100644 index 000000000..49f4f6422 --- /dev/null +++ b/webapp/apps/exceedance-explorer.html @@ -0,0 +1,166 @@ +<!doctype html> + +<html lang="en"> + + <head> + <meta name="viewport" content="width=device-width, initial-scale=1.0"> + <meta charset="UTF-8"> + + <!-- Bootstrap --> + <link rel="stylesheet" + href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css"> + + <!-- CSS Files --> + <link rel="stylesheet" href="css/template.css" type="text/css"> + <link rel="stylesheet" href="css/D3View.css" type="text/css"> + </head> + + + <body> + + <!-- Control panel --> + <div id="control" class="hidden"> + + <form id="inputs"> + <div class="form-horizontal"> + + <!-- Median input --> + <div class="form-group form-group-sm" id="median-form"> + <label class="control-spacer control-group" for="median"> + Median (g): + </label> + <div class="input-group col-xs-6"> + <input + id="median" + class="form-control" + type="number" + min="0" + max="10" + step="0.5" + name="median" /> + </div> + </div> + <!-- End: Median input --> + + <!-- Sigma input --> + <div class="form-group form-group-sm" id="sigma-form"> + <label class="control-spacer control-group" for="sigma"> + Sigma (natural log units): + </label> + <div class="input-group col-xs-6"> + <input + id="sigma" + class="form-control" + type="number" + min="0" + max="1" + step="0.10" + name="sigma" /> + </div> + </div> + <!-- End: Sigma input --> + + <!-- Rate input --> + <div class="form-group form-group-sm" id="rate-form"> + <label class="control-spacer control-group" for="rate"> + Annual Rate: + </label> + <div class="input-group col-xs-6"> + <input + id="rate" + class="form-control" + type="number" + min="0" + max="1" + step="0.10" + name="rate" /> + </div> + </div> + <!-- End: Rate input --> + + <!-- Truncation checkbox --> + <div class="form-group"> + <div class="input-group"> + <label for="truncation" class="control-label control-spacer"> + <input type="checkbox" id="truncation"> Truncation + </label> + </div> + </div> + <!-- End: Truncation checkbox --> + + <!-- Truncation level input --> + <div class="form-group form-group-sm" id="trunc-level-form"> + <label class="control-spacer control-group" for="truncation-level"> + Truncation Level (n): + </label> + <div class="input-group col-xs-6"> + <input + id="truncation-level" + class="form-control" + type="number" + min="0" + max="5" + step="1" + name="truncation-level" /> + </div> + </div> + <!-- End: Truncation level input --> + + <!-- Buttons --> + <div class="form-group" style="padding-top: 1em"> + <div class="btn-group" style="width: 100%"> + <button + type="button" + class="btn btn-primary col-xs-3" + id="add-plot"> + Add + </button> + + <button + type="button" + class="btn btn-warning col-xs-6" + id="remove-plot"> + Remove Selected + </button> + + <button + type="button" + class="btn btn-danger col-xs-3" + id="clear-plot"> + Clear + </button> + </div> + </div> + + </div> + </form> + + </div> + + <!-- Plot --> + <div id="content"></div> + + <!-- jQuery --> + <script + src="https://ajax.googleapis.com/ajax/libs/jquery/3.2.1/jquery.min.js"> + </script> + + <!-- Bootstrap --> + <script + src="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/js/bootstrap.min.js"> + </script> + + <!-- D3 --> + <script src="https://d3js.org/d3.v4.min.js"> </script> + + <!-- Application Specific JavaScript --> + <script type='module'> + import { CONFIG } from './config.js'; + import { ExceedanceExplorer } from './js/ExceedanceExplorer.js'; + + new ExceedanceExplorer(CONFIG); + </script> + + </body> + +</html> diff --git a/webapp/apps/geo-deagg.html b/webapp/apps/geo-deagg.html new file mode 100644 index 000000000..2679744c7 --- /dev/null +++ b/webapp/apps/geo-deagg.html @@ -0,0 +1,151 @@ +<!doctype html> +<html lang="en"> + +<head> + <meta name="viewport" content="width=device-width, initial-scale=1.0"> + <meta charset="UTF-8"> + + <link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css"> + <link rel="stylesheet" href="https://unpkg.com/leaflet@1.3.1/dist/leaflet.css" /> + <link rel="stylesheet" href="css/template.css" type="text/css"> + <link rel="stylesheet" href="css/D3View.css" type="text/css"> +</head> + +<body> + + <!-- Control panel --> + <div class='hidden' id="control" > + <form id="inputs"> + <div class="form-horizontal"> + + <!-- Edition select menu --> + <div class="form-group"> + <label class="control-spacer control-group" for="edition"> + Edition: + </label> + <select class="form-control" id="edition" name="edition"> + </select> + </div> + + <!-- Region select menu --> + <div class="form-group"> + <label class="control-spacer control-group" for="region"> + Region: + </label> + <select class="form-control" id="region" name="region" autofocus> + </select> + </div> + + <!-- Latitude input --> + <div class="form-group form-group-sm form-inline" id="lat-form"> + <label class="control-spacer control-group" + for="lat"> Latitude: <small id="lat-bounds"></small> + </label> <br> + <div class="input-group"> + <input id="lat" class="form-control" type="text" name="latitude" /> + <div class="input-group-addon">°</div> + </div> + </div> + + <!-- Longitude input --> + <div class="form-group form-group-sm form-inline" id="lon-form"> + <label class="control-spacer control-group" + for="lon"> Longitude: <small id="lon-bounds"></small> + </label> <br> + <div class="input-group"> + <input id="lon" class="form-control" type="text" name="longitude" /> + <div class="input-group-addon">°</div> + </div> + </div> + + <!-- Choose location on map --> + <div class='form-group form-group-sm'> + <button class="btn btn-default" id='test-site-picker' type='button'> + Choose a test site + </button> + </div> + <!-- End: Choose location on map --> + + <!-- Itensity measure type select menu --> + <div class="form-group form-group-sm"> + <label class="control-spacer control-group" for="imt"> + Intensity Measure Type: + </label> + <select class="form-control" id="imt" name="imt"> + </select> + </div> + + <!-- Vs30 select menu --> + <div class="form-group form-group-sm"> + <label class="control-spacer control-group" for="vs30"> + V<sub>s</sub>30: + </label> + <select class="form-control" id="vs30" name="vs30"> + </select> + </div> + + <!-- Return period input --> + <div class="form-group form-group-sm form-inline" id="period-form"> + <label class="control-spacer control-group" + for="return-period"> Return Period: + </label> <br> + <div class="input-group"> + <input id="return-period" class="form-control" + type="number" name="returnperiod" /> + </div> + </div> + <!-- Return period buttons --> + <div class='btn-group btn-group-xs btn-group-justified' + id='return-period-btns' data-toggle='buttons' role='group'> + + <label class='btn btn-default' id='return-period-2475' + for='return-period-input-2475'> + <input type='radio' id='return-period-input-2475' value='2475'/> + 2% in 50 years <br> <small> (2,475 years) </small> + </label> + + <label class='btn btn-default' id='return-period-975' + for='return-period-input-975'> + <input type='radio' id='return-period-input-975' value='975'/> + 5% in 50 years <br> <small> (975 years) </small> + </label> + + <label class='btn btn-default' id='return-period-475' + for='return-period-input-475'> + <input type='radio' id='return-period-input-475' value='475'/> + 10% in 50 years <br> <small> (475 years) </small> + </label> + </div> + + </div> + </form> + </div> + + <!-- Plots --> + <div id="content"> + </div> + + <!-- jQuery --> + <script src="https://ajax.googleapis.com/ajax/libs/jquery/3.2.1/jquery.min.js"></script> + + <!-- Bootstrap --> + <script src="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/js/bootstrap.min.js"></script> + + <!-- D3 --> + <script src="https://d3js.org/d3.v4.min.js" charset="utf-8"></script> + <script src="https://d3js.org/topojson.v1.min.js"></script> + <script src="https://d3js.org/d3-geo-projection.v2.min.js"></script> + <script src="https://unpkg.com/leaflet@1.3.1/dist/leaflet.js"></script> + + <!-- Application specific JavaScript --> + <script type='module'> + import { CONFIG } from './config.js'; + import GeoDeagg from './js/GeoDeagg.js'; + + new GeoDeagg(CONFIG); + </script> + +</body> + +</html> + diff --git a/webapp/apps/gmm-distance.html b/webapp/apps/gmm-distance.html new file mode 100644 index 000000000..c20870bd8 --- /dev/null +++ b/webapp/apps/gmm-distance.html @@ -0,0 +1,253 @@ + + +<!doctype html> + + +<html lang="en"> + +<!-- ......................... Head ........................................ --> +<head> + + <meta name="viewport" content="width=device-width, initial-scale=1.0"> + <meta charset="UTF-8"> + + <!-- Bootstrap --> + <link rel="stylesheet" + href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.6/css/bootstrap.min.css"> + + <!-- CSS Files --> + <link rel="stylesheet" href="css/template.css" type="text/css"> + <link rel="stylesheet" href="css/D3View.css" type="text/css"> + <link rel="stylesheet" href="css/Gmm.css" type="text/css"> + +</head> +<!-- ......................... End: Head ................................... --> + + + +<!-- ........................... Body ...................................... --> +<body> + + + <!-- ...................... Control Panel ................................ --> + <div class='hidden' id="control"> + <label for="gmms" class="control-group">Ground Motion Models:</label> + + <div id="gmm-sorter" class="btn-group btn-group-xs" data-toggle="buttons"> + <label class="btn btn-default gmm-group active" + data-toggle="tooltip" + data-container="body" + title="Sort by Group" + for="gmm-sort-group"> + <input type="radio" + value="group" + name="gmm-sort" + id="gmm-sort-group" + aria-label="Sort by Group" + checked="true" /> + <span class="glyphicon glyphicon-list" + aria-hidden="true" /> + </label> + <label class="btn btn-default gmm-alpha" + data-toggle="tooltip" + data-container="body" + title="Sort Alphabetically" + for="gmm-sort-alpha"> + <input type="radio" + value="alpha" + name="gmm-sort" + id="gmm-sort-alpha" + aria-label="Sort Alphabetically" /> + <span class="glyphicon glyphicon-sort-by-alphabet" + aria-hidden="true" /> + </input> + </label> + </div> + + <form id="inputs"> + <select class="form-control input-sm" + name="gmm" + id="gmms" + size="16" + multiple="true" + autofocus> + </select> + + <div class="form-horizontal"> + + + <!-- ..................... IMT Menu ................................. --> + <div class="form-group form-group-sm"> + <label class="control-spacer control-group" + for="imt"> Intensity Measure Type: </label> + <select class="form-control" id="imt" name="imt"> + </select> + </div> + <!-- ................ End: Imt Menu ................................ --> + + <label class="control-spacer control-group"> + Event Parameters: + </label> + <div class="form-group form-group-sm show-grid"> + <label class="col-sm-2 control-label" for="Mw"> + M<sub>W</sub> + </label> + <div class="col-sm-3"> + <input type="text" class="form-control" id="Mw" name="Mw"> + </div> + </div> + + <label class="control-spacer control-group"> + Source Geometry: + </label> + <div class="form-group form-group-sm"> + <label class="col-sm-2 control-label" for="zTop"> + Z<sub>TOP</sub> + </label> + <div class="col-sm-3"> + <div class="input-group input-group-sm"> + <input type="text" class="form-control" + id="zTop" name="zTop"> + <span class="input-group-addon">km</span> + </div> + </div> + <label class="col-sm-1 control-label" for="dip"> + Dip + </label> + <div class="col-sm-3"> + <div class="input-group input-group-sm"> + <input type="number" + class="form-control" + id="dip" + name="dip" + min="0" + max="90"> + <span class="input-group-addon">°</span> + </div> + </div> + </div> + <div class="form-group form-group-sm"> + <label class="col-sm-2 control-label" for="width"> + Width + </label> + <div class="col-sm-3"> + <div class="input-group input-group-sm"> + <input type="text" class="form-control" + id="width" name="width"> + <span class="input-group-addon">km</span> + </div> + </div> + </div> + + <label class="control-spacer control-group"> + Site & Basin: + </label> + <div class="form-group form-group-sm"> + <label class="col-sm-2 control-label" for="vs30"> + V<sub>S</sub>30 + </label> + <div class="col-sm-3"> + <div class="input-group input-group-sm"> + <input type="text" class="form-control" id="vs30" name="vs30"> + <span class="input-group-addon"><sup>m</sup>â„<sub>s</sub></span> + </div> + </div> + <div class="col-sm-6 col-sm-offset-1"> + <div id="vs-infer" + class="btn-group btn-group-xs disabled" + data-toggle="buttons"> + <label class="btn btn-default" + title="Vs30 Measured" + for="vs-measured"> + Measured + <input type="radio" + value="measured" + id="vs-measured" + name="vsInf"> + </label> + <label class="btn btn-default active" + title="Vs30 Inferred" + for="vs-inferred"> + Inferred + <input type="radio" + value="inferred" + id="vs-inferred" + name="vsInf" + checked="true"> + </label> + </div> + </div> + </div> + <div class="form-group form-group-sm"> + <label class="col-sm-2 control-label" for="z1p0"> + Z<sub>1.0</sub> + </label> + <div class="col-sm-3"> + <div class="input-group input-group-sm"> + <input type="text" class="form-control" + placeholder="default" + id="z1p0" + name="z1p0"> + <span class="input-group-addon">km</span> + </div> + </div> + <label class="col-sm-1 control-label" for="z2p5"> + Z<sub>2.5</sub> + </label> + <div class="col-sm-3"> + <div class="input-group input-group-sm"> + <input type="text" + class="form-control" + placeholder="default" + id="z2p5" + name="z2p5"> + <span class="input-group-addon">km</span> + </div> + </div> + </div> + + </div> + </form> + + </div> + <!-- .................... End: Control Panel ............................. --> + + + + <!-- .......................... Plots .................................... --> + <div id="content"> + + </div> + <!-- ..................... End: Plots .................................... --> + + + + <!-- ..................... Import JavaScript ............................. --> + + <!-- jQuery --> + <script + src="https://ajax.googleapis.com/ajax/libs/jquery/3.2.1/jquery.min.js"> + </script> + + <!-- Bootstrap --> + <script + src="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/js/bootstrap.min.js"> + </script> + + <!-- Application Specific JavaScript --> + <script src="https://d3js.org/d3.v4.min.js" charset="utf-8"></script> + + <script type='module'> + import { CONFIG } from './config.js'; + import { GmmDistance } from './js/GmmDistance.js'; + + new GmmDistance(CONFIG); + </script> + <!-- ..................... End: Import JavaScript ........................ --> + + + +</body> +<!-- ......................... End: Body ................................... --> + +</html> diff --git a/webapp/apps/hw-fw.html b/webapp/apps/hw-fw.html new file mode 100644 index 000000000..93e47cfff --- /dev/null +++ b/webapp/apps/hw-fw.html @@ -0,0 +1,280 @@ +<!doctype html> + +<html lang="en"> + +<!-- ......................... Head ........................................ --> +<head> + + <meta name="viewport" content="width=device-width, initial-scale=1.0"> + <meta charset="UTF-8"> + + <!-- Bootstrap --> + <link rel="stylesheet" + href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.6/css/bootstrap.min.css"> + + <!-- CSS Files --> + <link rel="stylesheet" href="css/template.css" type="text/css"> + <link rel="stylesheet" href="css/D3View.css" type="text/css"> + <link rel="stylesheet" href="css/Gmm.css" type="text/css"> + +</head> +<!-- ......................... End: Head ................................... --> + + + +<!-- ........................... Body ...................................... --> +<body> + + + <!-- ...................... Control Panel ................................ --> + <div class='hidden' id="control"> + + <label for="gmms" class="control-group">Ground Motion Models:</label> + + <div id="gmm-sorter" class="btn-group btn-group-xs" data-toggle="buttons"> + <label class="btn btn-default gmm-group active" + data-toggle="tooltip" + data-container="body" + title="Sort by Group" + for="gmm-sort-group"> + <input type="radio" + value="group" + name="gmm-sort" + id="gmm-sort-group" + aria-label="Sort by Group" + checked="true" /> + <span class="glyphicon glyphicon-list" + aria-hidden="true" /> + </label> + <label class="btn btn-default gmm-alpha" + data-toggle="tooltip" + data-container="body" + title="Sort Alphabetically" + for="gmm-sort-alpha"> + <input type="radio" + value="alpha" + name="gmm-sort" + id="gmm-sort-alpha" + aria-label="Sort Alphabetically" /> + <span class="glyphicon glyphicon-sort-by-alphabet" + aria-hidden="true" /> + </input> + </label> + </div> + + <form id="inputs"> + <select class="form-control input-sm" + name="gmm" + id="gmms" + size="16" + multiple="true" + autofocus> + </select> + + <div class="form-horizontal"> + + + <!-- ..................... IMT Menu ................................. --> + <div class="form-group form-group-sm"> + <label class="control-spacer control-group" + for="imt"> Intensity Measure Type: </label> + <select class="form-control" id="imt" name="imt"> + </select> + </div> + <!-- ................ End: Imt Menu ................................ --> + + <label class="control-spacer control-group"> + Event Parameters: + </label> + <div class="form-group form-group-sm show-grid"> + <label class="col-sm-2 control-label" for="Mw"> + M<sub>W</sub> + </label> + <div class="col-sm-3"> + <input type="text" class="form-control" id="Mw" name="Mw"> + </div> + </div> + + <!-- + <label class="control-spacer control-group"> + Source Geometry: + </label> + <div class="form-group form-group-sm"> + <label class="col-sm-2 control-label" for="zTop"> + Z<sub>TOP</sub> + </label> + <div class="col-sm-3"> + <div class="input-group input-group-sm"> + <input type="text" class="form-control" + id="zTop" name="zTop" readonly> + <span class="input-group-addon">km</span> + </div> + </div> + <label class="col-sm-1 control-label" for="dip"> + Dip + </label> + <div class="col-sm-3"> + <div class="input-group input-group-sm"> + <input type="number" + class="form-control" + id="dip" + name="dip" + min="0" + max="90" + readonly> + <span class="input-group-addon">°</span> + </div> + </div> + </div> + <div class="form-group form-group-sm"> + <label class="col-sm-2 control-label" for="width"> + Width + </label> + <div class="col-sm-3"> + <div class="input-group input-group-sm"> + <input type="text" class="form-control" + id="width" name="width" readonly> + <span class="input-group-addon">km</span> + </div> + </div> + </div> + + <label class="control-spacer control-group"> + Path Parameters: + </label> + <div class="form-group form-group-sm"> + <label class="col-sm-2 control-label" for="rMin"> + R<sub>Min</sub> + </label> + <div class="col-sm-3"> + <div class="input-group input-group-sm"> + <input type="text" class="form-control" + id="rMin" name="rMin" readonly> + <span class="input-group-addon">km</span> + </div> + </div> + <label class="col-sm-1 control-label" for="rMax"> + R<sub>Max</sub> + </label> + <div class="col-sm-3"> + <div class="input-group input-group-sm"> + <input type="text" class="form-control" + id="rMax" name="rMax" readonly> + <span class="input-group-addon">km</span> + </div> + </div> + </div> + --> + + <label class="control-spacer control-group"> + Site & Basin: + </label> + <div class="form-group form-group-sm"> + <label class="col-sm-2 control-label" for="vs30"> + V<sub>S</sub>30 + </label> + <div class="col-sm-3"> + <div class="input-group input-group-sm"> + <input type="text" class="form-control" id="vs30" name="vs30"> + <span class="input-group-addon"><sup>m</sup>â„<sub>s</sub></span> + </div> + </div> + <div class="col-sm-6 col-sm-offset-1"> + <div id="vs-infer" + class="btn-group btn-group-xs disabled" + data-toggle="buttons"> + <label class="btn btn-default" + title="Vs30 Measured" + for="vs-measured"> + Measured + <input type="radio" + value="measured" + id="vs-measured" + name="vsInf"> + </label> + <label class="btn btn-default active" + title="Vs30 Inferred" + for="vs-inferred"> + Inferred + <input type="radio" + value="inferred" + id="vs-inferred" + name="vsInf" + checked="true"> + </label> + </div> + </div> + </div> + <div class="form-group form-group-sm"> + <label class="col-sm-2 control-label" for="z1p0"> + Z<sub>1.0</sub> + </label> + <div class="col-sm-3"> + <div class="input-group input-group-sm"> + <input type="text" class="form-control" + placeholder="default" + id="z1p0" + name="z1p0"> + <span class="input-group-addon">km</span> + </div> + </div> + <label class="col-sm-1 control-label" for="z2p5"> + Z<sub>2.5</sub> + </label> + <div class="col-sm-3"> + <div class="input-group input-group-sm"> + <input type="text" + class="form-control" + placeholder="default" + id="z2p5" + name="z2p5"> + <span class="input-group-addon">km</span> + </div> + </div> + </div> + + </div> + </form> + + </div> + <!-- .................... End: Control Panel ............................. --> + + + + <!-- .......................... Plots .................................... --> + <div id="content"> + + </div> + <!-- ..................... End: Plots .................................... --> + + + + <!-- ..................... Import JavaScript ............................. --> + + <!-- jQuery --> + <script + src="https://ajax.googleapis.com/ajax/libs/jquery/3.2.1/jquery.min.js"> + </script> + + <!-- Bootstrap --> + <script + src="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/js/bootstrap.min.js"> + </script> + + <!-- Application Specific JavaScript --> + <script src="https://d3js.org/d3.v4.min.js" charset="utf-8"></script> + + <script type='module'> + import { CONFIG } from './config.js'; + import { HwFw } from './js/HwFw.js'; + + new HwFw(CONFIG); + </script> + <!-- ..................... End: Import JavaScript ........................ --> + + + +</body> +<!-- ......................... End: Body ................................... --> + +</html> diff --git a/webapp/apps/img/github.svg b/webapp/apps/img/github.svg new file mode 100644 index 000000000..1438915a8 --- /dev/null +++ b/webapp/apps/img/github.svg @@ -0,0 +1,2 @@ +<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 496 512"><path d="M165.9 397.4c0 2-2.3 3.6-5.2 3.6-3.3.3-5.6-1.3-5.6-3.6 0-2 2.3-3.6 5.2-3.6 3-.3 5.6 1.3 5.6 3.6zm-31.1-4.5c-.7 2 1.3 4.3 4.3 4.9 2.6 1 5.6 0 6.2-2s-1.3-4.3-4.3-5.2c-2.6-.7-5.5.3-6.2 2.3zm44.2-1.7c-2.9.7-4.9 2.6-4.6 4.9.3 2 2.9 3.3 5.9 2.6 2.9-.7 4.9-2.6 4.6-4.6-.3-1.9-3-3.2-5.9-2.9zM244.8 8C106.1 8 0 113.3 0 252c0 110.9 69.8 205.8 169.5 239.2 12.8 2.3 17.3-5.6 17.3-12.1 0-6.2-.3-40.4-.3-61.4 0 0-70 15-84.7-29.8 0 0-11.4-29.1-27.8-36.6 0 0-22.9-15.7 1.6-15.4 0 0 24.9 2 38.6 25.8 21.9 38.6 58.6 27.5 72.9 20.9 2.3-16 8.8-27.1 16-33.7-55.9-6.2-112.3-14.3-112.3-110.5 0-27.5 7.6-41.3 23.6-58.9-2.6-6.5-11.1-33.3 2.6-67.9 20.9-6.5 69 27 69 27 20-5.6 41.5-8.5 62.8-8.5s42.8 2.9 62.8 8.5c0 0 48.1-33.6 69-27 13.7 34.7 5.2 61.4 2.6 67.9 16 17.7 25.8 31.5 25.8 58.9 0 96.5-58.9 104.2-114.8 110.5 9.2 7.9 17 22.9 17 46.4 0 33.7-.3 75.4-.3 83.6 0 6.5 4.6 14.4 17.3 12.1C428.2 457.8 496 362.9 496 252 496 113.3 383.5 8 244.8 8zM97.2 352.9c-1.3 1-1 3.3.7 5.2 1.6 1.6 3.9 2.3 5.2 1 1.3-1 1-3.3-.7-5.2-1.6-1.6-3.9-2.3-5.2-1zm-10.8-8.1c-.7 1.3.3 2.9 2.3 3.9 1.6 1 3.6.7 4.3-.7.7-1.3-.3-2.9-2.3-3.9-2-.6-3.6-.3-4.3.7zm32.4 35.6c-1.6 1.3-1 4.3 1.3 6.2 2.3 2.3 5.2 2.6 6.5 1 1.3-1.3.7-4.3-1.3-6.2-2.2-2.3-5.2-2.6-6.5-1zm-11.4-14.7c-1.6 1-1.6 3.6 0 5.9 1.6 2.3 4.3 3.3 5.6 2.3 1.6-1.3 1.6-3.9 0-6.2-1.4-2.3-4-3.3-5.6-2z"/></svg> + diff --git a/webapp/apps/img/servicesIcon.png b/webapp/apps/img/servicesIcon.png new file mode 100644 index 0000000000000000000000000000000000000000..630125724688ec2f3d8509f811dfdc2c17c337d5 GIT binary patch literal 152531 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 NMnXZn{I$`?{{vu=^xXge literal 0 HcmV?d00001 diff --git a/webapp/apps/img/usgs_logo 2.png b/webapp/apps/img/usgs_logo 2.png new file mode 100644 index 0000000000000000000000000000000000000000..00df1785f1e2627c9d18bd403162bbd8d7e02c9b GIT binary patch literal 5379 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 literal 0 HcmV?d00001 diff --git a/webapp/apps/img/usgs_logo.png b/webapp/apps/img/usgs_logo.png new file mode 100644 index 0000000000000000000000000000000000000000..ef1acfc8b56a21193dffde59f930213c5bc87fe0 GIT binary patch literal 7503 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 literal 0 HcmV?d00001 diff --git a/webapp/apps/js/Dashboard.js b/webapp/apps/js/Dashboard.js new file mode 100644 index 000000000..3c09ec9dd --- /dev/null +++ b/webapp/apps/js/Dashboard.js @@ -0,0 +1,70 @@ +'use strict'; + +import Footer from './lib/Footer.js'; +import Header from './lib/Header.js'; +import Settings from './lib/Settings.js'; + +/** +* @fileoverview Class for index.html, this class +* creates the header and footer for the index page. +* +* @class Dashboard +* @author bclayton@usgs.gov +*/ +export default class Dashboard { + + /** + * @param {Object} config - config.json object, output from + * Config.getConfig() + */ + constructor(config) { + /** @type {Footer} */ + this.footer = new Footer().removeButtons().removeInfoIcon(); + /** @type {Settings} */ + //this.settings = new Settings(footer.settingsBtnEl); + /** @type {Header} */ + this.header = new Header(); + this.header.setTitle("Dashboard"); + + /** @ type {Array{Object}} */ + this.webapps = [ + { + label: 'Response Spectra', + href: 'apps/spectra-plot.html', + }, { + label: 'Services', + href: 'apps/services.html', + } + ]; + + this.createDashboard(); + } + + /** + * @method createDashboard + * + * Create the panels for the dashboard + */ + createDashboard() { + let elD3 = d3.select('body') + .append('div') + .attr('id', 'container') + .append('div') + .attr('id', 'dash'); + + elD3.selectAll('div') + .data(this.webapps) + .enter() + .append('div') + .attr('class', 'col-sm-offset-4 col-sm-4 col-sm-offset-4') + .on('click', (d, i) => { window.location = d.href; }) + .append('div') + .attr('class', 'panel panel-default') + .append('div') + .attr('class', 'panel-heading') + .append('h2') + .attr('class', 'panel-title') + .text((d, i) => {return d.label;}); + } + +} diff --git a/webapp/apps/js/DashboardDev.js b/webapp/apps/js/DashboardDev.js new file mode 100644 index 000000000..0c47f2800 --- /dev/null +++ b/webapp/apps/js/DashboardDev.js @@ -0,0 +1,91 @@ +'use strict'; + +import Footer from './lib/Footer.js'; +import Header from './lib/Header.js'; +import Settings from './lib/Settings.js'; + +/** +* @fileoverview Class for index.html, this class +* creates the header and footer for the index page. +* +* @class Dashboard +* @author bclayton@usgs.gov +*/ +export default class Dashboard { + + /** + * @param {Object} config - config.json object, output from + * Config.getConfig() + */ + constructor(config) { + /** @type {Footer} */ + this.footer = new Footer().removeButtons().removeInfoIcon(); + /** @type {Settings} */ + //this.settings = new Settings(footer.settingsBtnEl); + /** @type {Header} */ + this.header = new Header(); + this.header.setTitle("Dev Dashboard"); + + /** @ type {Array{Object}} */ + this.webapps = [ + { + label: 'Dynamic Model Comparison', + href: '../apps/dynamic-compare.html', + }, { + label: 'Response Spectra', + href: '../apps/spectra-plot.html', + }, { + label: 'Model Comparison', + href: '../apps/model-compare.html', + }, { + label: 'Ground Motion Vs. Distance', + href: '../apps/gmm-distance.html', + }, { + label: 'Model Explorer', + href: '../apps/model-explorer.html', + }, { + label: 'Hanging Wall Effects', + href: '../apps/hw-fw.html', + }, { + label: 'Geographic Deaggregation', + href: '../apps/geo-deagg.html', + }, { + label: 'Exceedance Explorer', + href: '../apps/exceedance-explorer.html', + }, { + label: 'Services', + href: '../apps/services.html', + } + ]; + + this.createDashboard(); + } + + /** + * @method createDashboard + * + * Create the panels for the dashboard + */ + createDashboard() { + let elD3 = d3.select('body') + .append('div') + .attr('id', 'container') + .append('div') + .attr('id', 'dash'); + + elD3.selectAll('div') + .data(this.webapps) + .enter() + .append('div') + .attr('class', 'col-sm-offset-1 col-sm-4 col-sm-offset-1') + .on('click', (d, i) => { window.location = d.href; }) + .append('div') + .attr('class', 'panel panel-default') + .append('div') + .attr('class', 'panel-heading') + .append('h2') + .attr('class', 'panel-title') + .text((d, i) => {return d.label;}); + } + +} diff --git a/webapp/apps/js/DynamicCompare.js b/webapp/apps/js/DynamicCompare.js new file mode 100644 index 000000000..472fb256c --- /dev/null +++ b/webapp/apps/js/DynamicCompare.js @@ -0,0 +1,1481 @@ + +import { D3LineData } from './d3/data/D3LineData.js'; +import { D3LineLegendOptions } from './d3/options/D3LineLegendOptions.js'; +import { D3LineOptions } from './d3/options/D3LineOptions.js'; +import { D3LinePlot } from './d3/D3LinePlot.js'; +import { D3LineSeriesData } from './d3/data/D3LineSeriesData.js'; +import { D3LineSubView } from './d3/view/D3LineSubView.js'; +import { D3LineSubViewOptions } from './d3/options/D3LineSubViewOptions.js'; +import { D3LineView } from './d3/view/D3LineView.js'; +import { D3LineViewOptions } from './d3/options/D3LineViewOptions.js'; +import { D3TextOptions } from './d3/options/D3TextOptions.js'; + +import { Hazard } from './lib/HazardNew.js'; +import { HazardServiceResponse } from './response/HazardServiceResponse.js'; + +import Constraints from './lib/Constraints.js'; +import LeafletTestSitePicker from './lib/LeafletTestSitePicker.js'; +import NshmpError from './error/NshmpError.js'; +import { Preconditions } from './error/Preconditions.js'; +import Tools from './lib/Tools.js'; + +/** + * @fileoverview Class for the dynamic compare webpage, dynamic-compare.html. + * + * This class contains two plot panels with the following plots: + * first panel: + * - Model comparison of ground motion Vs. annual frequency of exceedence + * - Percent difference between the models + * second panel: + * - Response spectrum of the models + * - Percent difference of the response spectrum + * + * The class first class out to the source model webservice, + * nshmp-haz-ws/source/models, to get the usage and build the + * following menus: + * - Model + * - Second Model + * - IMT + * - Vs30 + * + * The IMT and Vs30 menus are created by the common supported values + * between the two selected models. + * + * Bootstrap tooltips are created and updated for the latitude, longitude, + * and return period inputs. + * + * The inputs, latitude, longitude, and return period, are monitored. If + * a bad or out of range value is entered the update button will + * remain disabled. Once all inputs are correctly entered the update + * button or enter can be pressed to render the results. + * + * The return period allowable minimum and maximum values are updated + * based on the choosen models such that the response spectrum + * is defined for the entire bounds for both models. + * + * The results are rendered using the D3 package. + * + * @class DynamicCompare + * @author Brandon Clayton + */ +export class DynamicCompare extends Hazard { + + /** @param {!Config} config - The config file */ + constructor(config) { + let webApp = 'DynamicCompare'; + let webServiceUrl = '/nshmp-haz-ws/haz'; + super(webApp, webServiceUrl, config); + this.header.setTitle('Dynamic Compare'); + + this.options = { + defaultImt: 'PGA', + defaultReturnPeriod: 2475, + defaultVs30: 760, + }; + + /** @type {HTMLElement} */ + this.contentEl = document.querySelector('#content'); + + /** @type {HTMLElement} */ + this.firstModelEl = document.querySelector('#first-model'); + + /** @type {HTMLElement} */ + this.secondModelEl = document.querySelector('#second-model'); + + /** @type {HTMLElement} */ + this.modelsEl = document.querySelector('.model'); + + /** @type {HTMLElement} */ + this.testSitePickerBtnEl = document.querySelector('#test-site-picker'); + + /** @type {Object[]} */ + this.comparableModels = undefined; + + /* Get webservice usage */ + this.getUsage(); + + /** X-axis domain for spectra plots - @type {Array<Number} */ + this.spectraXDomain = [0.001, 10.0]; + + /* Default titles */ + this.hazardComponentPlotTitle = 'Hazard Component Curves'; + this.hazardPlotTitle = 'Hazard Curves'; + this.spectraComponentPlotTitle = 'Response Spectrum Component Curves'; + this.spectraPlotTitle = 'Response Spectrum'; + + /* Spectra plot setup */ + this.spectraView = this.setupSpectraView(); + this.spectraLinePlot = new D3LinePlot(this.spectraView); + this.spectraLinePlot.plotZeroRefLine(this.spectraView.lowerSubView); + + /* Hazard curve plot setup */ + this.hazardView = this.setupHazardView(); + this.hazardLinePlot = new D3LinePlot(this.hazardView); + this.hazardLinePlot.plotZeroRefLine(this.hazardView.lowerSubView); + + /* Spectra component curves setup */ + this.spectraComponentView = this.setupSpectraComponentView(); + this.spectraComponentLinePlot = new D3LinePlot(this.spectraComponentView); + + /* Hazard component curves setup */ + this.hazardComponentView = this.setupHazardComponentView(); + this.hazardComponentLinePlot = new D3LinePlot(this.hazardComponentView); + + /* @type {LeafletTestSitePicker} */ + this.testSitePicker = new LeafletTestSitePicker( + this.latEl, + this.lonEl, + this.testSitePickerBtnEl); + + this.imtHandler = () => {}; + this.returnPeriodEventHandler = () => {}; + this.vs30EventHandler = () => {}; + + this.addEventListener(); + } + + /** Add event listeners */ + addEventListener() { + /* Check latitude values on change */ + $(this.latEl).on('input', (event) => { + this.onCoordinate(event); + }); + + /* Check longitude values on change */ + $(this.lonEl).on('input', (event) => { + this.onCoordinate(event); + }); + + /* Listen for input changes */ + this.footer.onInput(this.inputsEl, this.footerOptions); + + /* Update menus when first model changes */ + this.firstModelEl.addEventListener('change', () => { + this.onFirstModelChange(); + }); + + /** Update menus on second model change */ + this.secondModelEl.addEventListener('change', () => { + this.onModelChange(); + }); + + /** Check query on test site load */ + this.testSitePicker.on('testSiteLoad', (event) => { + this.checkQuery(); + }); + + /* Bring Leaflet map up when clicked */ + $(this.testSitePickerBtnEl).on('click', (event) => { + let model = Tools.stringToParameter( + this.parameters.models, + this.firstModelEl.value); + + this.testSitePicker.plotMap(model.region); + }); + } + + /** + * Add an input tooltip for latitude, longitude, and return period + * using Constraints.addTooltip. + */ + addInputTooltip() { + let model = Tools.stringToParameter( + this.parameters.models, + this.firstModelEl.value); + + let region = this.parameters.region.values.find((region) => { + return region.value == model.region; + }); + + Constraints.addTooltip( + this.latEl, + region.minlatitude, + region.maxlatitude); + + Constraints.addTooltip( + this.lonEl, + region.minlongitude, + region.maxlongitude); + + let periodValues = this.parameters.returnPeriod.values; + Constraints.addTooltip( + this.returnPeriodEl, + periodValues.minimum, + periodValues.maximum); + } + + /** + * Add text with the ground motion difference. + * + * @param {Array<HazardServiceResponse>} hazardResponses The hazard responses + * @param {D3LineSubView} subView The sub view + * @returns {SVGElement} The text element + */ + addGroundMotionDifferenceText(hazardResponses, subView) { + Preconditions.checkArgumentArrayInstanceOf( + hazardResponses, + HazardServiceResponse); + + Preconditions.checkArgumentInstanceOf(subView, D3LineSubView); + + let timeHorizon = this.returnPeriodEl.value; + let returnPeriod = 1 / timeHorizon; + let percentDifference = this.getGroundMotionDifference(hazardResponses); + + let xMax = this.hazardLinePlot.getXDomain(subView)[1]; + let text = `${timeHorizon} years, % diff = ${percentDifference}`; + + let textOptions = D3TextOptions.builder() + .dy(10) + .fontSize(18) + .textAnchor('end') + .build(); + + let textEl = this.hazardLinePlot.addText( + subView, + xMax, + returnPeriod, + text, + textOptions); + + return textEl; + } + + /** + * Process usage response from nshmp-haz-ws/source/models and set menus. + */ + buildInputs() { + this.spinner.off(); + $(this.controlPanelEl).removeClass('hidden'); + this.setComparableModels(); + + this.setFirstModelMenu(); + this.setSecondModelMenu(); + this.setParameterMenu(this.imtEl, this.options.defaultImt); + this.setParameterMenu(this.vs30El, this.options.defaultVs30); + this.setDefaultReturnPeriod(); + this.addInputTooltip(); + } + + /** + * Check the current hash part of the URL for parameters, if they + * exist plot the results. + */ + checkQuery() { + let url = window.location.hash.substring(1); + let urlObject = Tools.urlQueryStringToObject(url); + + /* Make sure all pramameters are present in URL */ + if (!urlObject.hasOwnProperty('model') || + !urlObject.hasOwnProperty('latitude') || + !urlObject.hasOwnProperty('longitude') || + !urlObject.hasOwnProperty('imt') || + !urlObject.hasOwnProperty('returnperiod') || + !urlObject.hasOwnProperty('vs30')) return false; + + /* Update values for the menus */ + this.firstModelEl.value = urlObject.model[0]; + $(this.firstModelEl).trigger('change'); + this.secondModelEl.value = urlObject.model[1]; + this.latEl.value = urlObject.latitude; + this.lonEl.value = urlObject.longitude; + this.imtEl.value = urlObject.imt; + this.vs30El.value = urlObject.vs30; + this.returnPeriodEl.value = urlObject.returnperiod; + + /* Trigger events to update tooltips */ + $(this.latEl).trigger('input'); + $(this.lonEl).trigger('input'); + this.onReturnPeriodInput(); + this.addInputTooltip(); + + /* Get and plot results */ + $(this.inputsEl).trigger('change'); + let keypress = jQuery.Event('keypress'); + keypress.which = 13; + keypress.keyCode = 13; + $(document).trigger(keypress); + } + + /** + * Clear all plots + */ + clearPlots() { + this.hazardComponentLinePlot.clearAll(); + this.hazardComponentView.setTitle(this.hazardComponentPlotTitle); + + this.hazardLinePlot.clearAll(); + this.hazardView.setTitle(this.hazardPlotTitle); + this.hazardLinePlot.plotZeroRefLine(this.hazardView.lowerSubView); + + this.spectraComponentLinePlot.clearAll(); + this.spectraComponentView.setTitle(this.spectraComponentPlotTitle); + + this.spectraLinePlot.clearAll(); + this.spectraView.setTitle(this.spectraPlotTitle); + this.spectraLinePlot.plotZeroRefLine(this.spectraView.lowerSubView); + } + + /** + * Calculate the ground motion difference. + * + * @param {Array<HazardServiceResponse>} hazardResponses The responses + */ + getGroundMotionDifference(hazardResponses) { + Preconditions.checkArgumentArrayInstanceOf( + hazardResponses, + HazardServiceResponse); + + let spectrum = []; + let imt = this.imtEl.value; + + let timeHorizon = this.returnPeriodEl.value; + let returnPeriod = 1 / timeHorizon; + + for (let hazardResponse of hazardResponses) { + let response = hazardResponse.getResponse(imt); + spectrum.push(response.calculateResponseSpectrum('Total', returnPeriod)); + } + + return Tools.percentDifference(spectrum[0], spectrum[1]); + } + + /** + * Return the Y limit for the hazard curves. + * + * @param {Array<HazardServiceResponse>} hazardResponses The hazard responses + * @param {String} imt The IMT + * @returns {Array<Number>} The Y limit + */ + getHazardCurvesYLimit(hazardResponses, imt) { + Preconditions.checkArgumentArrayInstanceOf( + hazardResponses, + HazardServiceResponse); + + Preconditions.checkArgumentString(imt); + + let yValues = []; + + for (let hazardResponse of hazardResponses) { + let response = hazardResponse.getResponse(imt); + + for (let data of response.data) { + yValues.push(data.yValues); + } + } + + yValues = d3.merge(yValues).filter((y) => { return y > this.Y_MIN_CUTOFF; }); + + let min = d3.min(yValues); + let max = d3.max(yValues); + + return [ min, max ]; + } + + /** + * Get the metadata, associated with the hazard plots, + * about the selected parameters in the control panel. + * + * @return {Map<String, Array<String>>} The metadata Map + */ + getMetadataHazard() { + let models = [ + $(':selected', this.firstModelEl).text(), + $(':selected', this.secondModelEl).text(), + ]; + + let metadata = new Map(); + metadata.set('Model:', models) + metadata.set('Latitude:', [this.latEl.value]); + metadata.set('Longitude:', [this.lonEl.value]); + metadata.set('Intensity Measure Type:', [$(':selected', this.imtEl).text()]); + metadata.set('V<sub>s</sub>30:', [$(':selected', this.vs30El).text()]); + + return metadata; + } + + /** + * Get the metadata, associated with the response spectra plots, + * about the selected parameters in the control panel. + * + * @return {Map<String, Array<String>>} The metadata Map + */ + getMetadataSpectra() { + let models = [ + $(':selected', this.firstModelEl).text(), + $(':selected', this.secondModelEl).text(), + ]; + + let metadata = new Map(); + metadata.set('Model:', models) + metadata.set('Latitude:', [this.latEl.value]); + metadata.set('Longitude:', [this.lonEl.value]); + metadata.set('V<sub>s</sub>30:', [$(':selected', this.vs30El).text()]); + metadata.set('Return Period (years):', [this.returnPeriodEl.value]); + + return metadata; + } + + /** + * Calculate the percent difference of the models. + * + * @param {D3LineSubView} subView The sub view for the line data + * @param {D3LineData} lineData The hazard line data + * @param {Array<Number>} xLimit The X limit for the data + * @returns {D3LineData} The percent difference line data + */ + getModelDifferenceData(subView, lineData, xLimit) { + Preconditions.checkArgumentInstanceOf(subView, D3LineSubView); + Preconditions.checkArgumentInstanceOf(lineData, D3LineData); + Preconditions.checkArgumentArrayOf(xLimit, 'number'); + + let firstModelSeries = lineData.series.find((series) => { + return series.lineOptions.id == this.firstModelEl.value; + }); + + let secondModelSeries = lineData.series.find((series) => { + return series.lineOptions.id == this.secondModelEl.value; + }); + + let firstModelData = D3LineSeriesData.intersectionX( + firstModelSeries, + secondModelSeries); + + let secondModelData = D3LineSeriesData.intersectionX( + secondModelSeries, + firstModelSeries); + + let firstModelYValues = firstModelData.map((xyPair) => { + return xyPair.y; + }); + + let secondModelYValues = secondModelData.map((xyPair) => { + return xyPair.y; + }); + + let xValues = firstModelData.map((xyPair) => { + return xyPair.x; + }); + + let yValues = Tools.percentDifferenceArray( + firstModelYValues, + secondModelYValues); + + let maxVal = d3.max(yValues, (/** @type {Number} */ y) => { + return Math.abs(y); + }); + + let yLimit = [-maxVal, maxVal]; + + let selectedFirstModel = $(':selected', this.firstModelEl).text(); + let selectedSecondModel = $(':selected', this.secondModelEl).text(); + + let label = selectedFirstModel + ' Vs. ' + selectedSecondModel; + + let lineOptions = D3LineOptions.builder() + .label(label) + .id('plot-difference') + .build(); + + let diffData = D3LineData.builder() + .subView(subView) + .data(xValues, yValues, lineOptions) + .xLimit(xLimit) + .yLimit(yLimit) + .build(); + + return diffData; + } + + /** + * Return the Y limit for the response spectrum. + * + * @param {Array<HazardServiceResponse>} hazardResponses The hazard responses + * @returns {Array<Number>} The Y limit + */ + getResponseSpectraYLimit(hazardResponses) { + Preconditions.checkArgumentArrayInstanceOf( + hazardResponses, + HazardServiceResponse); + + let minReturnPeriod = 1 / this.parameters.returnPeriod.values.maximum; + let maxReturnPeriod = 1 / this.parameters.returnPeriod.values.minimum; + + let spectras = []; + + for (let hazardResponse of hazardResponses) { + let spectraMin = hazardResponse.toResponseSpectrum(minReturnPeriod); + let spectraMax = hazardResponse.toResponseSpectrum(maxReturnPeriod); + + spectras.push(spectraMin); + spectras.push(spectraMax); + } + + let yValues = []; + + for (let spectra of spectras) { + for (let data of spectra.data) { + yValues.push(data.yValues); + } + } + + yValues = d3.merge(yValues).filter((y) => { return y > this.Y_MIN_CUTOFF; }); + + let maxSpectraGm = d3.max(yValues); + let minSpectraGm = d3.min(yValues); + + maxSpectraGm = isNaN(maxSpectraGm) ? 1.0 : maxSpectraGm; + minSpectraGm = isNaN(minSpectraGm) ? 1e-4 : minSpectraGm; + + return [minSpectraGm, maxSpectraGm]; + } + + /** + * On longitude or latitude input, check that the coordinate values + * input are good values. + * + * @param {Event} event - The event that triggered the input. + */ + onCoordinate(event) { + let model = Tools.stringToParameter( + this.parameters.models, + this.firstModelEl.value); + + let region = Tools.stringToParameter(this.parameters.region, model.region); + + this.checkCoordinates(event.target, region); + } + + /** + * Update menus and update the second model + */ + onFirstModelChange() { + this.setSecondModelMenu(); + this.onModelChange(); + this.latEl.value = null; + this.lonEl.value = null; + } + + /** + * Handler to update the hazard plots on IMT change. + * + * @param {String} firstModel The first model value + * @param {String} secondModel The second model value + * @param {Array<HazardServiceResponse>} hazardResponses The hazard responses + */ + onIMTChange(firstModel, secondModel, hazardResponses) { + Preconditions.checkArgumentString(firstModel); + Preconditions.checkArgumentString(secondModel); + Preconditions.checkArgumentArrayInstanceOf( + hazardResponses, + HazardServiceResponse); + + this.serializeUrls(); + + if (firstModel != this.firstModelEl.value || + secondModel != this.secondModelEl.value) { + return; + } + + this.plotHazardCurves(hazardResponses); + this.plotHazardComponentCurves(hazardResponses); + } + + /** + * Update menus on model change + */ + onModelChange() { + this.clearPlots(); + this.setParameterMenu(this.imtEl, this.options.defaultImt); + this.setParameterMenu(this.vs30El, this.options.defaultVs30); + this.addInputTooltip(); + } + + /** + * Handler to update the plots when the return period is changed. + * + * @param {String} firstModel The first model value + * @param {String} secondModel The second model value + * @param {Array<HazardServiceResponse>} hazardResponses The hazard responses + */ + onReturnPeriodChange(firstModel, secondModel, hazardResponses) { + Preconditions.checkArgumentString(firstModel); + Preconditions.checkArgumentString(secondModel); + Preconditions.checkArgumentArrayInstanceOf( + hazardResponses, + HazardServiceResponse); + + if (firstModel != this.firstModelEl.value || + secondModel != this.secondModelEl.value) { + return; + } + + this.serializeUrls(); + this.plotResponseSpectrum(hazardResponses); + this.plotResponseSpectrumComponents(hazardResponses); + + this.plotHazardCurves(hazardResponses); + this.plotHazardComponentCurves(hazardResponses); + } + + /** + * Handler for the return period line being dragged on the hazard curve plot. + * + * @param {Array<HazardServiceResponse>} hazardResponses The hazard responses + * @param {D3LineSubView} subView The sub view + * @param {Number} returnPeriod The return period + * @param {SVGElement} textEl The return period difference text element + */ + onReturnPeriodDrag(hazardResponses, subView, textEl, returnPeriod) { + Preconditions.checkArgumentArrayInstanceOf( + hazardResponses, + HazardServiceResponse); + + Preconditions.checkArgumentInstanceOf(subView, D3LineSubView); + Preconditions.checkArgumentInstanceOfSVGElement(textEl); + Preconditions.checkArgumentNumber(returnPeriod); + + let timeHorizon = 1 / returnPeriod; + timeHorizon = Number(timeHorizon.toFixed(2)); + this.returnPeriodEl.value = timeHorizon; + + this.checkReturnPeriodButtons(); + + this.plotResponseSpectrum(hazardResponses); + this.plotResponseSpectrumComponents(hazardResponses); + this.updateGroundMotionDifferenceText(hazardResponses, subView, textEl); + + this.serializeUrls(); + } + + /** + * Handler to update the plots on vs30 change. + * + * @param {String} firstModel The first model value + * @param {String} secondModel The second model value + * @param {Array<HazardServiceResponse>} hazardResponses The hazard responses + * @param {Number} vs30Value The vs30 + */ + onVs30Change(firstModel, secondModel, hazardResponses, vs30) { + Preconditions.checkArgumentString(firstModel); + Preconditions.checkArgumentString(secondModel); + Preconditions.checkArgumentString(vs30); + Preconditions.checkArgumentArrayInstanceOf( + hazardResponses, + HazardServiceResponse); + + if (vs30 != this.vs30El.value || + firstModel != this.firstModelEl.value || + secondModel != this.secondModelEl.value) { + this.clearPlots(); + } else { + this.serializeUrls(); + this.plotResponseSpectrum(hazardResponses); + this.plotResponseSpectrumComponents(hazardResponses); + + this.plotHazardCurves(hazardResponses); + this.plotHazardComponentCurves(hazardResponses); + } + } + + /** + * Plot the hazard comonent curves. + * + * @param {Array<HazardServiceResponse>} hazardResponses The hazard responses + */ + plotHazardComponentCurves(hazardResponses) { + Preconditions.checkArgumentArrayInstanceOf( + hazardResponses, + HazardServiceResponse); + + let subView = this.hazardComponentView.upperSubView; + + this.hazardComponentLinePlot.clear(subView); + + let yLimit = this.getHazardCurvesYLimit(hazardResponses, this.imtEl.value); + + let lineData = []; + let lineStyles = [ '-', '--' ]; + + for (let index in hazardResponses) { + let hazardResponse = hazardResponses[index]; + + let lineStyle = lineStyles[index]; + + let dataBuilder = D3LineData.builder() + .subView(subView) + .removeSmallValues(this.Y_MIN_CUTOFF) + .yLimit(yLimit); + + let response = hazardResponse.getResponse(this.imtEl.value); + let xValues = response.metadata.xValues; + + let model = response.metadata.model; + + for (let componentData of response.getDataComponents()) { + let yValues = componentData.yValues; + + let lineOptions = D3LineOptions.builder() + .color(Tools.hazardComponentToColor(componentData.component)) + .id(`${model.value}-${componentData.component}`) + .label(`${model.year} ${model.region}: ${componentData.component}`) + .lineStyle(lineStyle) + .build(); + + dataBuilder.data(xValues, yValues, lineOptions); + } + + lineData.push(dataBuilder.build()); + } + + let hazardComponentData = D3LineData.of(...lineData); + + this.hazardComponentLinePlot.plot(hazardComponentData); + + let imt = $(':selected', this.imtEl).text(); + let vs30 = $(':selected', this.vs30El).text(); + + let model = Tools.stringToParameter( + this.parameters.models, + this.firstModelEl.value); + + let siteTitle = this.testSitePicker.getTestSiteTitle(model.region); + let title = `Hazard Component Curves: ${siteTitle}, ${imt}, ${vs30}`; + + this.hazardComponentView.setTitle(title); + + let metadata = this.getMetadataHazard(); + this.hazardComponentView.setMetadata(metadata); + this.hazardComponentView.createMetadataTable(); + + this.hazardComponentView.setSaveData(hazardComponentData); + this.hazardComponentView.createDataTable(hazardComponentData); + } + + /** + * Plot the total hazard curve. + * + * @param {Array<HazardServiceResponse>} hazardResponses The hazard responses + */ + plotHazardCurves(hazardResponses) { + Preconditions.checkArgumentArrayInstanceOf( + hazardResponses, + HazardServiceResponse); + + let subView = this.hazardView.upperSubView; + + this.hazardLinePlot.clear(subView); + + let metadata = this.getMetadataHazard(); + + let yLimit = this.getHazardCurvesYLimit(hazardResponses, this.imtEl.value); + + let dataBuilder = D3LineData.builder() + .subView(subView) + .removeSmallValues(this.Y_MIN_CUTOFF) + .yLimit(yLimit); + + for (let hazardResponse of hazardResponses) { + let response = hazardResponse.getResponse(this.imtEl.value); + let totalHazardData = response.getDataComponent('Total'); + + let xValues = response.metadata.xValues; + let yValues = totalHazardData.yValues; + + let model = response.metadata.model; + + let lineOptions = D3LineOptions.builder() + .id(model.value) + .label(model.display) + .build(); + + dataBuilder.data(xValues, yValues, lineOptions); + } + + let hazardData = dataBuilder.build(); + + this.hazardLinePlot.plot(hazardData); + + let timeHorizon = this.returnPeriodEl.value; + let returnPeriod = 1 / timeHorizon; + + let returnPeriodPlotEl = this.hazardLinePlot.plotHorizontalRefLine( + subView, + returnPeriod); + + let textEl = this.addGroundMotionDifferenceText(hazardResponses, subView); + + let returnPeriodValues = this.parameters.returnPeriod.values; + + let yLimitDrag = [ + 1 / returnPeriodValues.maximum, + 1 / returnPeriodValues.minimum ]; + + this.hazardLinePlot.makeDraggableInY( + subView, + returnPeriodPlotEl, + yLimitDrag, + (returnPeriod) => { + this.onReturnPeriodDrag( + hazardResponses, + subView, + textEl, + returnPeriod); + }); + + let imt = $(':selected', this.imtEl).text(); + let vs30 = $(':selected', this.vs30El).text(); + + let model = Tools.stringToParameter( + this.parameters.models, + this.firstModelEl.value); + + let siteTitle = this.testSitePicker.getTestSiteTitle(model.region); + let title = `Hazard Curves: ${siteTitle}, ${imt}, ${vs30}`; + + this.hazardView.setTitle(title); + + let hazardDiffData = this.plotHazardCurveDifference(hazardData); + + this.hazardView.setMetadata(metadata); + this.hazardView.createMetadataTable(); + + this.hazardView.setSaveData(hazardData, hazardDiffData); + this.hazardView.createDataTable(hazardData, hazardDiffData); + } + + /** + * Plot the total hazard curve difference. + * + * @param {D3LineData} hazardData The hazard data + */ + plotHazardCurveDifference(hazardData) { + let subView = this.hazardView.lowerSubView; + + this.hazardLinePlot.clear(subView); + + let xLimit = hazardData.getXLimit(); + + let hazardDiffData = this.getModelDifferenceData(subView, hazardData, xLimit); + + this.hazardLinePlot.plot(hazardDiffData); + this.hazardLinePlot.plotZeroRefLine(subView); + + return hazardDiffData; + } + + /** + * Plot the response spectrum calculated from the total hazard component. + * + * @param {Array<HazardServiceResponse>} hazardResponses The hazard responses + */ + plotResponseSpectrum(hazardResponses) { + Preconditions.checkArgumentArrayInstanceOf( + hazardResponses, + HazardServiceResponse); + + let subView = this.spectraView.upperSubView; + + this.spectraLinePlot.clear(subView); + + let returnPeriod = 1 / this.returnPeriodEl.value; + + let dataBuilder = D3LineData.builder() + .subView(subView); + + let pgaBuilder = D3LineData.builder() + .subView(subView); + + for (let hazardResponse of hazardResponses) { + let spectra = hazardResponse.calculateResponseSpectrum('Total', returnPeriod); + let xValues = spectra[0]; + let yValues = spectra[1]; + + let model = hazardResponse.response[0].metadata.model; + + let iPGA = xValues.indexOf(Tools.imtToValue('PGA')); + let pgaX = xValues.splice(iPGA, 1); + let pgaY = yValues.splice(iPGA, 1); + + let pgaOptions = D3LineOptions.builder() + .id(model.value) + .label(model.display) + .lineStyle('none') + .markerStyle('s') + .showInLegend(false) + .build(); + + pgaBuilder.data(pgaX, pgaY, pgaOptions, [ 'PGA' ]); + + let lineOptions = D3LineOptions.builder() + .id(model.value) + .label(model.display) + .build(); + + dataBuilder.data(xValues, yValues, lineOptions); + } + + let xLimit = this.spectraXDomain; + let yLimit = this.getResponseSpectraYLimit(hazardResponses); + + let spectraLineData = dataBuilder + .xLimit(xLimit) + .yLimit(yLimit) + .build(); + + let spectraPGAData = pgaBuilder + .xLimit(xLimit) + .yLimit(yLimit) + .build(); + + this.spectraLinePlot.plot(spectraPGAData); + this.spectraLinePlot.plot(spectraLineData); + + let model = Tools.stringToParameter( + this.parameters.models, + this.firstModelEl.value); + + let vs30 = $(':selected', this.vs30El).text(); + let siteTitle = this.testSitePicker.getTestSiteTitle(model.region); + + let title = `Response Spectrum at ${this.returnPeriodEl.value}` + + ` years, ${siteTitle}, ${vs30}`; + + this.spectraView.setTitle(title); + + let metadata = this.getMetadataSpectra(); + this.spectraView.setMetadata(metadata); + this.spectraView.createMetadataTable(); + + let spectraDiffData = this.plotResponseSpectrumDifference(spectraLineData); + + let spectraData = spectraPGAData.concat(spectraLineData); + this.spectraView.createDataTable(spectraData, spectraDiffData); + this.spectraView.setSaveData(spectraData, spectraDiffData); + } + + /** + * Plot the response spectrum component curves. + * + * @param {Array<HazardServiceResponse>} hazardResponses The hazard responses + */ + plotResponseSpectrumComponents(hazardResponses) { + Preconditions.checkArgumentArrayInstanceOf( + hazardResponses, + HazardServiceResponse); + + let subView = this.spectraComponentView.upperSubView; + + this.spectraComponentLinePlot.clear(subView); + + let returnPeriod = 1 / this.returnPeriodEl.value; + + let lineStyles = [ '-', '--' ]; + let markerStyles = [ 's', '*' ] + let lineData = []; + let pgaData = []; + + let xLimit = this.spectraXDomain; + let yLimit = this.getResponseSpectraYLimit(hazardResponses); + + for (let index in hazardResponses) { + let hazardResponse = hazardResponses[index]; + + let lineStyle = lineStyles[index]; + let markerStyle = markerStyles[index]; + + let spectra = hazardResponse.toResponseSpectrum(returnPeriod); + + let dataBuilder = D3LineData.builder() + .subView(subView) + .xLimit(xLimit) + .yLimit(yLimit); + + let pgaBuilder = D3LineData.builder() + .subView(subView) + .xLimit(xLimit) + .yLimit(yLimit); + + let model = hazardResponse.response[0].metadata.model; + + for (let componentData of spectra.getDataComponents()) { + let xValues = componentData.xValues; + let yValues = componentData.yValues; + + let iPGA = xValues.indexOf(Tools.imtToValue('PGA')); + let pgaX = xValues.splice(iPGA, 1); + let pgaY = yValues.splice(iPGA, 1); + + let pgaOptions = D3LineOptions.builder() + .color(Tools.hazardComponentToColor(componentData.component)) + .id(`${model.value}-${componentData.component}`) + .label(`${model.year} ${model.region}: ${componentData.component}`) + .lineStyle('none') + .markerStyle(markerStyle) + .showInLegend(false) + .build(); + + pgaBuilder.data(pgaX, pgaY, pgaOptions, [ 'PGA' ]); + + let lineOptions = D3LineOptions.builder() + .color(Tools.hazardComponentToColor(componentData.component)) + .id(`${model.value}-${componentData.component}`) + .label(`${model.year} ${model.region}: ${componentData.component}`) + .lineStyle(lineStyle) + .build(); + + dataBuilder.data(xValues, yValues, lineOptions); + } + + lineData.push(dataBuilder.build()); + pgaData.push(pgaBuilder.build()); + } + + let spectraComponentLineData = D3LineData.of(...lineData); + let spectraComponentPGAData = D3LineData.of(...pgaData); + + this.spectraComponentLinePlot.plot(spectraComponentPGAData); + this.spectraComponentLinePlot.plot(spectraComponentLineData); + + let model = Tools.stringToParameter( + this.parameters.models, + this.firstModelEl.value); + + let vs30 = $(':selected', this.vs30El).text(); + let siteTitle = this.testSitePicker.getTestSiteTitle(model.region); + + let title = `Response Spectrum at ${this.returnPeriodEl.value}` + + ` years, ${siteTitle}, ${vs30}`; + + this.spectraComponentView.setTitle(title); + + let metadata = this.getMetadataSpectra(); + this.spectraComponentView.setMetadata(metadata); + this.spectraComponentView.createMetadataTable(); + + let spectraData = spectraComponentPGAData.concat(spectraComponentLineData); + this.spectraComponentView.createDataTable(spectraData); + this.spectraComponentView.setSaveData(spectraData); + } + + /** + * Plot the response spectrum percent difference. + * + * @param {D3LineData} spectraData The spectra data + */ + plotResponseSpectrumDifference(spectraData) { + let subView = this.spectraView.lowerSubView; + + this.spectraLinePlot.clear(subView); + + let spectraDiffData = this.getModelDifferenceData( + subView, + spectraData, + this.spectraXDomain); + + this.spectraLinePlot.plot(spectraDiffData); + this.spectraLinePlot.plotZeroRefLine(subView); + + return spectraDiffData; + } + + /** + * @override + * Get URLs to query. + */ + serializeUrls() { + let urls = []; + let inputs = $(this.inputsEl).serialize(); + let windowUrl = ''; + + for (let modelEl of [this.firstModelEl, this.secondModelEl]) { + let model = Tools.stringToParameter( + this.parameters.models, + modelEl.value); + + urls.push(`${this.dynamicUrl}?model=${model.value}&${inputs}`); + + windowUrl += `&model=${model.value}`; + } + + windowUrl += `&${inputs}&imt=${this.imtEl.value}` + + `&returnperiod=${this.returnPeriodEl.value}`; + + window.location.hash = windowUrl.substring(1); + return urls; + } + + /** + * Given the models in nshmp-haz-ws/source/models find only models + * that can be compared, ones that have that same region. + */ + setComparableModels() { + this.comparableModels = this.parameters.models.values.filter((model) => { + let regions = this.parameters.models.values.filter((modelCheck) => { + return model.region == modelCheck.region; + }); + return regions.length > 1; + }); + } + + /** + * Set the first model select menu with only comparable models. + * See setComparableModels(). + */ + setFirstModelMenu() { + Tools.setSelectMenu(this.firstModelEl, this.comparableModels); + this.firstModelEl.value = this.comparableModels[0].value; + } + + /** + * Set select menus with supported values of the selected models. + * + * @param {HTMLElement} el - The dom element of the select menu to set. + */ + setParameterMenu(el, defaultValue) { + let firstModel = Tools.stringToParameter( + this.parameters.models, + this.firstModelEl.value); + + let secondModel = Tools.stringToParameter( + this.parameters.models, + this.secondModelEl.value); + + let supports = []; + supports.push(firstModel.supports[el.id]); + supports.push(secondModel.supports[el.id]); + + let supportedValues = Tools.supportedParameters( + this.parameters[el.id ], + supports); + + Tools.setSelectMenu(el, supportedValues); + + let hasDefaultValue = supportedValues.some((val) => { + return defaultValue == val.value; + }); + + if (hasDefaultValue) el.value = defaultValue; + } + + /** + * Set the second model select menu with only comparable models to + * the first selected model. + */ + setSecondModelMenu() { + let selectedModel = Tools.stringToParameter( + this.parameters.models, + this.firstModelEl.value); + + let comparableModels = this.comparableModels.filter((model) => { + return selectedModel.region == model.region && + selectedModel != model; + }); + + Tools.setSelectMenu(this.secondModelEl, comparableModels); + } + + /** + * Build the view for the hazard component curves plot + * + * @returns {D3LineView} The spectra line view + */ + setupHazardComponentView() { + /* Upper sub view options: hazard plot */ + let upperSubViewOptions = D3LineSubViewOptions.upperBuilder() + .dragLineSnapTo(1e-10) + .filename('dynamic-compare-hazard-components') + .label('Hazard Component Curves') + .lineLabel('Model') + .xLabel('Ground Motion (g)') + .xAxisScale('log') + .yAxisScale('log') + .yLabel('Annual Frequency of Exceedence') + .yValueToExponent(true) + .build(); + + /* Plot view options */ + let viewOptions = D3LineViewOptions.builder() + .titleFontSize(14) + .viewSize('min') + .build(); + + /* Build the view */ + let view = D3LineView.builder() + .containerEl(this.contentEl) + .viewOptions(viewOptions) + .upperSubViewOptions(upperSubViewOptions) + .build(); + + view.setTitle(this.hazardComponentPlotTitle); + + return view; + } + + /** + * Build the view for the hazard curve plot with % difference + * + * @returns {D3LineView} The spectra line view + */ + setupHazardView() { + /* Lower sub view options: % difference plot */ + let lowerSubViewOptions = D3LineSubViewOptions.lowerBuilder() + .defaultYLimit([ -1.0, 1.0 ]) + .filename('dynamic-compare-hazard-difference') + .label('Hazard Curves Percent Difference') + .lineLabel('Model') + .showLegend(false) + .xLabel('Ground Motion (g)') + .yLabel('% Difference') + .build(); + + /* Upper sub view legend options: hazard plot */ + let upperLegendOptions = D3LineLegendOptions.upperBuilder() + .location('bottom-left') + .build(); + + /* Upper sub view options: hazard plot */ + let upperSubViewOptions = D3LineSubViewOptions.upperBuilder() + .dragLineSnapTo(1e-8) + .filename('dynamic-compare-hazard') + .label('Hazard Curves') + .lineLabel('Model') + .legendOptions(upperLegendOptions) + .xLabel('Ground Motion (g)') + .yAxisScale('log') + .yLabel('Annual Frequency of Exceedence') + .yValueToExponent(true) + .build(); + + /* Plot view options */ + let viewOptions = D3LineViewOptions.builder() + .syncXAxisScale(true, 'log') + .syncYAxisScale(false) + .titleFontSize(14) + .viewSize('min') + .build(); + + /* Build the view */ + let view = D3LineView.builder() + .addLowerSubView(true) + .containerEl(this.contentEl) + .viewOptions(viewOptions) + .lowerSubViewOptions(lowerSubViewOptions) + .upperSubViewOptions(upperSubViewOptions) + .build(); + + view.setTitle(this.hazardPlotTitle); + + return view; + } + + /** + * Build the view for the spectra plot with % difference + * + * @returns {D3LineView} The spectra line view + */ + setupSpectraComponentView() { + /* Upper sub view options: spectra plot */ + let upperSubViewOptions = D3LineSubViewOptions.upperBuilder() + .defaultXLimit(this.spectraXDomain) + .filename('dynamic-compare-spectra-components') + .label('Response Spectrum Component Curves') + .lineLabel('Model') + .xAxisScale('log') + .yAxisScale('log') + .xLabel('Spectral Period (s)') + .yLabel('Ground Motion (g)') + .build(); + + /* Plot view options */ + let viewOptions = D3LineViewOptions.builder() + .titleFontSize(14) + .viewSize('min') + .build(); + + /* Build the view */ + let view = D3LineView.builder() + .containerEl(this.contentEl) + .viewOptions(viewOptions) + .upperSubViewOptions(upperSubViewOptions) + .build(); + + view.setTitle(this.spectraComponentPlotTitle); + + return view; + } + + /** + * Build the view for the spectra plot with % difference + * + * @returns {D3LineView} The spectra line view + */ + setupSpectraView() { + /* Lower sub view options: % difference plot */ + let lowerSubViewOptions = D3LineSubViewOptions.lowerBuilder() + .defaultXLimit(this.spectraXDomain) + .defaultYLimit([ -1.0, 1.0 ]) + .filename('dynamic-compare-spectra-difference') + .label('Response Spectrum Percent Difference') + .lineLabel('Model') + .showLegend(false) + .xLabel('Period (s)') + .yLabel('% Difference') + .build(); + + /* Upper sub view legend options: hazard plot */ + let upperLegendOptions = D3LineLegendOptions.upperBuilder() + .location('bottom-left') + .build(); + + /* Upper sub view options: spectra plot */ + let upperSubViewOptions = D3LineSubViewOptions.upperBuilder() + .defaultXLimit(this.spectraXDomain) + .filename('dynamic-compare-spectra') + .label('Response Spectrum') + .legendOptions(upperLegendOptions) + .lineLabel('Model') + .xLabel('Spectral Period (s)') + .yAxisScale('log') + .yLabel('Ground Motion (g)') + .build(); + + /* Plot view options */ + let viewOptions = D3LineViewOptions.builder() + .syncXAxisScale(true, 'log') + .syncYAxisScale(false) + .titleFontSize(14) + .viewSize('min') + .build(); + + /* Build the view */ + let view = D3LineView.builder() + .addLowerSubView(true) + .containerEl(this.contentEl) + .viewOptions(viewOptions) + .lowerSubViewOptions(lowerSubViewOptions) + .upperSubViewOptions(upperSubViewOptions) + .build(); + + view.setTitle(this.spectraPlotTitle); + + return view; + } + + /** + * Update the ground motion difference text. + * + * @param {Array<HazardServiceResponse>} hazardResponses The responses + * @param {D3LineSubView} subView The sub view + * @param {SVGElement} textEl The SVG text element to update + */ + updateGroundMotionDifferenceText(hazardResponses, subView, textEl) { + Preconditions.checkArgumentArrayInstanceOf( + hazardResponses, + HazardServiceResponse); + + Preconditions.checkArgumentInstanceOf(subView, D3LineSubView); + Preconditions.checkArgumentInstanceOfSVGElement(textEl); + + let timeHorizon = this.returnPeriodEl.value; + let returnPeriod = 1 / timeHorizon; + let percentDifference = this.getGroundMotionDifference(hazardResponses); + let text = `${timeHorizon} years, % diff = ${percentDifference}`; + + let xMax = this.hazardLinePlot.getXDomain(subView)[1]; + this.hazardLinePlot.moveText(subView, xMax, returnPeriod, textEl); + this.hazardLinePlot.updateText(textEl, text); + } + + /** + * Call the hazard web service for each model and plot the resuls. + */ + updatePlot() { + let urls = this.serializeUrls(); + + let jsonCall = Tools.getJSONs(urls); + this.spinner.on(jsonCall.reject, 'Calculating'); + + Promise.all(jsonCall.promises).then((results) => { + this.spinner.off(); + + Tools.checkResponses(results); + + let hazardResponses = results.map((result) => { + return new HazardServiceResponse(result); + }); + + /* Set footer metadata */ + this.footer.setWebServiceMetadata(hazardResponses[0]); + + /* Update tooltips for input */ + this.addInputTooltip(); + + /* Plot response spectrum */ + this.plotResponseSpectrum(hazardResponses); + + /* Plot hazard curves */ + this.plotHazardCurves(hazardResponses); + + /* Plot spectra component curves */ + this.plotResponseSpectrumComponents(hazardResponses); + + /* Plot hazard component curves */ + this.plotHazardComponentCurves(hazardResponses); + + let firstModel = this.firstModelEl.value; + let secondModel = this.secondModelEl.value; + let vs30 = this.vs30El.value; + + this.updatePlotIMT(firstModel, secondModel, hazardResponses); + this.updatePlotReturnPeriod(firstModel, secondModel, hazardResponses); + this.updatePlotVs30(firstModel, secondModel, hazardResponses, vs30); + + /* Get raw data */ + this.footer.onRawDataBtn(urls); + }).catch((errorMessage) => { + this.spinner.off(); + NshmpError.throwError(errorMessage); + }); + } + + /** Update IMT event handler */ + updatePlotIMT(firstModel, secondModel, hazardResponses) { + let imtHandler = () => { + this.onIMTChange(firstModel, secondModel, hazardResponses) + }; + + this.imtEl.removeEventListener('change', this.imtHandler); + this.imtEl.addEventListener('change', imtHandler); + this.imtHandler = imtHandler; + } + + /** Update return period event handler */ + updatePlotReturnPeriod(firstModel, secondModel, hazardResponses) { + let returnPeriodEventHandler = () => { + this.onReturnPeriodChange(firstModel, secondModel, hazardResponses); + }; + + this.returnPeriodEl.removeEventListener( + 'returnPeriodChange', + this.returnPeriodEventHandler) + + this.returnPeriodEl.addEventListener( + 'returnPeriodChange', + returnPeriodEventHandler); + + this.returnPeriodEventHandler = returnPeriodEventHandler; + } + + /** Update vs30 event handler */ + updatePlotVs30(firstModel, secondModel, hazardResponses, vs30) { + let vs30EventHandler = () => { + this.onVs30Change(firstModel, secondModel, hazardResponses, vs30); + }; + + this.vs30El.removeEventListener('change', this.vs30EventHandler); + this.vs30El.addEventListener('change', vs30EventHandler); + this.vs30EventHandler = vs30EventHandler; + } + +} diff --git a/webapp/apps/js/ExceedanceExplorer.js b/webapp/apps/js/ExceedanceExplorer.js new file mode 100644 index 000000000..9258e046a --- /dev/null +++ b/webapp/apps/js/ExceedanceExplorer.js @@ -0,0 +1,416 @@ + +import Footer from './lib/Footer.js'; +import Header from './lib/Header.js'; +import Constraints from './lib/Constraints.js'; + +import { D3LineSubViewOptions } from './d3/options/D3LineSubViewOptions.js'; +import { D3LineView } from './d3/view/D3LineView.js'; +import { D3LinePlot } from './d3/D3LinePlot.js'; +import { D3XYPair } from './d3/data/D3XYPair.js'; +import { D3LineData } from './d3/data/D3LineData.js'; +import { D3LineOptions } from './d3/options/D3LineOptions.js'; +import { D3LineLegendOptions } from './d3/options/D3LineLegendOptions.js'; +import { Maths } from './calc/Maths.js'; +import { ExceedanceModel } from './calc/ExceedanceModel.js'; +import { Preconditions } from './error/Preconditions.js'; +import { UncertaintyModel } from './calc/UncertaintyModel.js'; + +/** + * @fileoverview Class for exceedance-explorer.html + * + * @class ExceedanceExplorer + */ +export class ExceedanceExplorer { + + constructor() { + /* Add footer */ + this.footer = new Footer() + .removeButtons() + .removeInfoIcon(); + + /* Add header */ + this.header = new Header(); + + /* Set HTML elements */ + this.formEl = document.querySelector('#inputs'); + this.controlPanelEl = document.querySelector('#control'); + this.medianEl = document.querySelector('#median'); + this.sigmaEl = document.querySelector('#sigma'); + this.truncationEl = document.querySelector('#truncation'); + this.truncationLevelEl = document.querySelector('#truncation-level'); + this.rateEl = document.querySelector('#rate'); + this.addPlotBtnEl = document.querySelector('#add-plot'); + this.clearPlotBtnEl = document.querySelector('#clear-plot'); + this.containerEl = document.querySelector('#content'); + this.removePlotBtnEl = document.querySelector('#remove-plot'); + + this.removePlotBtnEl.disabled = true; + this.clearPlotBtnEl.disabled = true; + + /* Default values */ + this.defaults = { + median: 1, + sigma: 0.5, + truncation: true, + truncationLevel: 3, + rate: 1, + xMin: 0.0001, + xMax: 10.0, + xPoints: 100 + }; + + const formEls = [ + this.medianEl, + this.rateEl, + this.sigmaEl, + this.truncationLevelEl + ]; + + this.addInputTooltips(formEls); + this.onInputCheck(formEls); + this.setDefaults(); + this.eventListeners(); + this.checkInputs(); + this.controlPanelEl.classList.remove('hidden'); + + this.plotView = this.createView(); + this.plotView.setTitle('Exceedance'); + + /* Create plot */ + this.plot = new D3LinePlot(this.plotView); + this.exceedanceData = this.plot.upperLineData; + + this.sequence = this.createSequence(); + this.plotSelection(); + + this.medianValues = []; + this.sigmaValues = []; + this.rateValues = []; + this.truncationValues = []; + this.truncationLevelValues = []; + } + + /** + * Add form input tooltips. + * + * @param {HTMLElement[]} formEls + */ + addInputTooltips(formEls) { + Preconditions.checkArgumentArrayInstanceOf(formEls, Element); + + for (let el of formEls) { + Constraints.addTooltip(el, el.getAttribute('min'), el.getAttribute('max')); + } + } + + /** + * Add a new plot + */ + addPlot() { + this.clearPlotBtnEl.disabled = false; + + let model = new UncertaintyModel( + this.mean(), + this.sigma(), + this.truncationLevel() === 'N/A' ? 0 : this.truncationLevel()); + + let sequence = []; + let label = `μ=${this.median()}, σ=${this.sigma()},` + + ` rate=${this.rate()}, n=${this.truncationLevel()}`; + + if (this.truncationEl.checked) { + sequence = ExceedanceModel.truncationUpperOnlySequence(model, this.sequence); + } else { + sequence = ExceedanceModel.truncationOffSequence(model, this.sequence); + } + + let xValues = sequence.map(xy => Math.exp(xy.x)); + let yValues = sequence.map(xy => Maths.round(xy.y * this.rate() , 4)); + + let dataBuilder = this.getDataBuilder(); + + let lineOptions = D3LineOptions.builder() + .label(label) + .markerSize(3) + .lineWidth(1.25) + .build(); + + let data = dataBuilder + .data(xValues, yValues, lineOptions) + .removeSmallValues(1e-14) + .build(); + + this.exceedanceData = data; + + this.removePlotBtnEl.disabled = true; + this.plot.clearAll(); + this.plot.plot(data); + + this.updateValues(); + this.setPlotData(data); + } + + /** + * Check for any form input errors + */ + checkInputs() { + const hasError = this.formEl.querySelectorAll('.has-error').length > 0; + this.addPlotBtnEl.disabled = hasError; + } + + /** + * Clear all plots + */ + clearPlot() { + this.plot.clearAll(); + this.removePlotBtnEl.disabled = true; + this.exceedanceData = D3LineData.builder() + .subView(this.exceedanceData.subView) + .build(); + + this.medianValues = []; + this.sigmaValues = []; + this.rateValues = []; + this.truncationValues = []; + this.truncationLevelValues = []; + + this.clearPlotBtnEl.disabled = true; + } + + /** + * Create the XY sequence to plot + */ + createSequence() { + const xMin = this.defaults.xMin; + const xMax = this.defaults.xMax; + const xPoints = this.defaults.xPoints + + return d3.ticks(Math.log(xMin), Math.log(xMax), xPoints).map((x) => { + return new D3XYPair(x, 0); + }); + } + + /** + * Create the D3LineView + */ + createView() { + const legendOptions = D3LineLegendOptions.upperBuilder() + .location('bottom-left') + .build(); + + const upperSubViewOptions = D3LineSubViewOptions.upperBuilder() + .filename('exceedance') + .label('Exceedance Models') + .legendOptions(legendOptions) + .lineLabel('Exceedance Model') + .xValueToExponent(true) + .xAxisScale('log') + .xLabel('Ground Motion (g)') + .yLabel('Annual Frequency of Exceedance') + .build(); + + return D3LineView.builder() + .containerEl(this.containerEl) + .upperSubViewOptions(upperSubViewOptions) + .build(); + } + + /** + * Add event listeners + */ + eventListeners() { + this.truncationEl.addEventListener('change', () => { + this.truncationLevelEl.disabled = !this.truncationEl.checked; + }); + + this.formEl.addEventListener('change', () => { + this.checkInputs(); + }); + + this.formEl.addEventListener('input', () => { + this.checkInputs(); + }); + + this.addPlotBtnEl.addEventListener('click', () => { + this.addPlot(); + }); + + this.clearPlotBtnEl.addEventListener('click', () => { + this.clearPlot(); + }); + } + + /** + * Return a D3LineData.Builder for plotting of the previous data + * before adding a new plot + */ + getDataBuilder() { + let dataBuilder = D3LineData.builder() + .subView(this.plot.view.upperSubView); + + for (let series of this.exceedanceData.series) { + let lineOptions = D3LineOptions.builder() + .label(series.lineOptions.label) + .markerSize(series.lineOptions.markerSize) + .lineWidth(series.lineOptions.lineWidth) + .build(); + + dataBuilder.data(series.xValues, series.yValues, lineOptions); + } + + return dataBuilder; + } + + /** + * Add form input checks. + * + * @param {HTMLElement[]} formEls + */ + onInputCheck(formEls) { + Preconditions.checkArgumentArrayInstanceOf(formEls, Element); + + for (let el of formEls) { + Constraints.onInput(el, el.getAttribute('min'), el.getAttribute('max')); + } + } + + /** + * Return the mean value, ln(median) + */ + mean() { + return Math.log(this.median()); + } + + /** + * Return the median value + */ + median() { + return Number(this.medianEl.value); + } + + metadata() { + const metadata = new Map(); + + metadata.set('Median (g)', this.medianValues); + metadata.set('Sigma (natural log units)', this.sigmaValues); + metadata.set('Truncation', this.truncationValues); + metadata.set('Truncation Level (n)', this.truncationLevelValues); + + return metadata; + } + + /** + * Remove selected plot if remove selected button is clicked. + */ + plotSelection() { + let removePlotEvent = () => {}; + + this.plot.onPlotSelection(this.exceedanceData, (selectedData) => { + this.removePlotBtnEl.disabled = false; + + let plotEvent = () => { + let index = this.exceedanceData.series.findIndex(series => { + return series.lineOptions.id === selectedData.lineOptions.id; + }); + + this.exceedanceData.series.splice(index, 1); + + this.plot.clearAll(); + this.removePlotBtnEl.disabled = true; + this.removeValues(index); + this.setPlotData(this.exceedanceData); + + if(this.exceedanceData.series.length == 0) { + this.clearPlotBtnEl.disabled = true; + return; + } + + this.plot.plot(this.exceedanceData); + }; + + this.removePlotBtnEl.removeEventListener('click', removePlotEvent); + this.removePlotBtnEl.addEventListener('click', plotEvent); + + removePlotEvent = plotEvent; + }); + } + + /** + * Return the rate value + */ + rate() { + return Number(this.rateEl.value); + } + + /** + * Remove values + */ + removeValues(index) { + Preconditions.checkArgumentInteger(index); + + this.medianValues.splice(index, 1); + this.sigmaValues.splice(index, 1); + this.rateValues.splice(index, 1); + this.truncationValues.splice(index, 1); + this.truncationLevelValues.splice(index, 1); + } + + /** + * Set the default form values + */ + setDefaults() { + this.medianEl.value = this.defaults.median; + this.sigmaEl.value = this.defaults.sigma; + this.truncationLevelEl.value = this.defaults.truncationLevel; + this.truncationEl.setAttribute('checked', !this.defaults.truncation); + this.rateEl.value = this.defaults.rate; + this.truncationLevelEl.disabled = !this.truncationEl.checked; + } + + /** + * Set the plot data and metadata + * @param {D3LineData} data + */ + setPlotData(data) { + Preconditions.checkArgumentInstanceOf(data, D3LineData); + + this.plotView.setSaveData(data); + this.plotView.createDataTable(data); + this.plotView.setMetadata(this.metadata()); + this.plotView.createMetadataTable(); + } + + /** + * Return the sigma value + */ + sigma() { + return Number(this.sigmaEl.value); + } + + /** + * Return the truncation value + */ + truncation() { + return this.truncationEl.checked ? 'On' : 'Off'; + } + + /** + * Return the truncation level value + */ + truncationLevel() { + return this.truncationEl.checked ? + Number(this.truncationLevelEl.value) : 'N/A'; + } + + /** + * Update values + */ + updateValues() { + this.medianValues.push(this.median()); + this.sigmaValues.push(this.sigma()); + this.rateValues.push(this.rate()); + this.truncationValues.push(this.truncation()); + this.truncationLevelValues.push(this.truncationLevel()); + } + +} diff --git a/webapp/apps/js/GeoDeagg.js b/webapp/apps/js/GeoDeagg.js new file mode 100644 index 000000000..fffcec65a --- /dev/null +++ b/webapp/apps/js/GeoDeagg.js @@ -0,0 +1,599 @@ +'use strict'; + +import Constraints from './lib/Constraints.js'; +import D3GeoDeagg from './lib/D3GeoDeagg.js'; +import Footer from './lib/Footer.js'; +import Header from './lib/Header.js'; +import LeafletTestSitePicker from './lib/LeafletTestSitePicker.js'; +import NshmpError from './error/NshmpError.js'; +import Spinner from './lib/Spinner.js'; +import Tools from './lib/Tools.js'; + +/** +* @class GeoDeagg +* +* @fileoverview Class for the geographic deaggregation webpage, geo-deagg.html. +* The class first calls out to the deagg webservice, nshmp-haz-ws/deagg, to +* get the usage and builds the following menus: +* - Edition +* - Region +* - IMT +* - Vs30 +* The IMT and Vs30 menus are created by the common supported values in +* the edition and region. +* Bootstrap tooltips are created and updated for the latitude, longitude, +* and return period inputs. +* The inputs, latitude, longitude, and return period, are monitored. If +* a bad or out of range value, given the coorresponding min/max values in +* the usage, is entered the update button will remain disabled. Once +* all inputs are correctly entered the update button or enter can be +* pressed to render the results. +* Once the update button or enter is pressed the results are rendered +* using the D3GeoDeagg class. +* For the geographic deaggregation, only the single sources for the total +* component are graphed. +* +* @author bclayton@usgs.gov (Brandon Clayton) +*/ +export default class GeoDeagg { + + /** + * @param {!Config} config - The config file. + */ + constructor(config) { + /** @type {Config} */ + this.config = config; + /** @type {FooterOptions} */ + this.footerOptions = { + rawBtnDisable: true, + updateBtnDisable: true, + }; + /** @type {Footer} */ + this.footer = new Footer(); + this.footer.setOptions(this.footerOptions); + /** @type {Header} */ + this.header = new Header().setTitle('Geographic Deaggregation'); + /** @type {Spinner} */ + this.spinner = new Spinner(); + + /** + * @typedef {Object} GeoDeaggOptions - Geographic deagg options + * @property {String} defaultEdition - Default selected edition. + * Values: 'E2014' || 'E2008' || 'E2007' + * Default: 'E2014' + * @property {Number} defaultReturnPeriod - Default selected return + * return period. + * Values: 475 || 975 || 2475 + * Default: 2475 + */ + this.options = { + defaultEdition: 'E2014', + defaultReturnPeriod: 2475, + }; + + /** @type {String} */ + this.webServiceUrl = this.config.server.dynamic + '/nshmp-haz-ws/deagg'; + + /** @type {HTMLElement} */ + this.contentEl = document.querySelector('#content'); + /** @type {HTMLElement} */ + this.controlPanelEl = document.querySelector('#control'); + /** @type {HTMLElement} */ + this.editionEl = document.querySelector('#edition'); + /** @type {HTMLElement} */ + this.imtEl = document.querySelector('#imt'); + /** @type {HTMLElement} */ + this.inputsEl = document.querySelector('#inputs'); + /** @type {HTMLElement} */ + this.latEl = document.querySelector('#lat'); + /** @type {HTMLElement} */ + this.lonEl = document.querySelector('#lon'); + /** @type {HTMLElement} */ + this.regionEl = document.querySelector('#region'); + /** @type {HTMLElement} */ + this.returnPeriodEl = document.querySelector('#return-period'); + /** @type {HTMLElement} */ + this.returnPeriodBtnsEl = document.querySelector('#return-period-btns'); + /** @type {HTMLElement} */ + this.testSitePickerBtnEl = document.querySelector('#test-site-picker'); + /** @type {HTMLElement} */ + this.vs30El = document.querySelector('#vs30'); + + /** @type {D3GeoDeagg} */ + this.plot = this.plotSetup(); + + /** @type {Object} - Deagg usage */ + this.parameters = undefined; + // Get deagg usage + this.getUsage(); + + // Check latitude values on input + $(this.latEl).on('input', (event) => { + this.checkCoordinates(event.target); + }); + + // Check longitude values on input + $(this.lonEl).on('input', (event) => { + this.checkCoordinates(event.target); + }); + + // Update plot when update button pressed + $(this.footer.updateBtnEl).click((event) => { + $(this.footer.rawBtnEl).off(); + this.footerOptions.rawBtnDisable = false; + this.footer.setOptions(this.footerOptions); + this.updatePlot(); + }); + + // Listen for all control panel changes + this.onInputChange(); + + /* @type {LeafletTestSitePicker} */ + this.testSitePicker = new LeafletTestSitePicker( + this.latEl, + this.lonEl, + this.testSitePickerBtnEl); + + /* Bring Leaflet map up when clicked */ + $(this.testSitePickerBtnEl).on('click', (event) => { + this.testSitePicker.plotMap(this.regionEl.value); + }); + } + + /** + * @method addInputTooltip + * + * Add an input tooltip for latitude, logitude, and return period using + * Contraints.addTooltip. + */ + addInputTooltip() { + let region = Tools.stringToParameter( + this.parameters.region, this.regionEl.value); + + Constraints.addTooltip( + this.latEl, region.minlatitude, region.maxlatitude); + + Constraints.addTooltip( + this.lonEl, region.minlongitude, region.maxlongitude); + + let period = this.parameters.returnPeriod; + Constraints.addTooltip( + this.returnPeriodEl, period.values.minimum, period.values.maximum); + } + + /** + * @method buildInputs + * + * Process usage response and set select menus + * @param {!Object} usage - JSON usage response from deagg web service. + */ + buildInputs(usage) { + this.spinner.off(); + let parameters = usage.parameters; + this.parameters = parameters; + + // Set edition menu + Tools.setSelectMenu(this.editionEl, parameters.edition.values); + this.editionEl.value = this.options.defaultEdition; + + // Set menus + this.setRegionMenu(); + this.setImtMenu(); + this.setVs30Menu(); + this.setDefaultReturnPeriod(); + this.addInputTooltip(); + + $(this.controlPanelEl).removeClass('hidden'); + + // Update return period on change + this.onReturnPeriodChange(); + // Update menus when edition is changed + this.onEditionChange(); + // Update menus when region is changed + this.onRegionChange(); + // Check URL for parameters + this.testSitePicker.on('testSiteLoad', (event) => { + this.checkQuery(); + }); + } + + /** + * @method checkCoordinates + * + * Check the input values of latitude or longitude using Contraints.check + * method. If values is out of range or bad, the plot cannot be updated. + * @param {HTMLElement} el - Latitude or longitude element. + */ + checkCoordinates(el) { + let region = Tools.stringToParameter( + this.parameters.region, this.regionEl.value); + + let min = el.id == 'lat' ? region.minlatitude : + region.minlongitude; + + let max = el.id == 'lat' ? region.maxlatitude : + region.maxlongitude; + + Constraints.check(el, min, max); + } + + /** + * @method checkReturnPeriod + * + * Check the return period input value using the Contraints.check method. + * If the value is out of range or bad, the plot cannot be updated. + */ + checkReturnPeriod() { + let period = this.parameters.returnPeriod; + Constraints.check( + this.returnPeriodEl, period.values.minimum, period.values.maximum); + } + + /** + * @method checkQuery + * + * Check the hash of the URL string to see if parameters exists. If + * there are paramaters present, set the menus to match the values and + * plot the results. + */ + checkQuery() { + let url = window.location.hash.substring(1); + let urlObject = Tools.urlQueryStringToObject(url); + + // Make sure all pramameters are present in URL + if (!urlObject.hasOwnProperty('edition') || + !urlObject.hasOwnProperty('region') || + !urlObject.hasOwnProperty('latitude') || + !urlObject.hasOwnProperty('longitude') || + !urlObject.hasOwnProperty('imt') || + !urlObject.hasOwnProperty('vs30') || + !urlObject.hasOwnProperty('returnperiod')) return; + + // Set edition value and trigger a change to update the other menus + let edition = urlObject.edition; + this.editionEl.value = edition; + $(this.editionEl).trigger('change'); + delete urlObject.edition; + + // Set regions value and trigger a change to update the other menus + let region = urlObject.region; + this.regionEl.value = region; + $(this.regionEl).trigger('change'); + delete urlObject.region; + + // Update all other values + for (let key in urlObject) { + $('[name="' + key + '"]').val(urlObject[key]); + } + + this.checkCoordinates(this.latEl); + this.checkCoordinates(this.lonEl); + + // Trigger input event on return period + $(this.returnPeriodEl).trigger('input'); + + // Trigger an enter key + $(this.inputsEl).trigger('change'); + let keypress = jQuery.Event('keypress'); + keypress.which = 13; + keypress.keyCode = 13; + $(document).trigger(keypress); + } + + /** + * Get current chosen parameters. + * @return Map<String, Array<String>> The metadata Map + */ + getMetadata() { + let metadata = new Map(); + metadata.set('Edition:', [$(this.editionEl).find(':selected').text()]); + metadata.set('Region:', [$(this.regionEl).find(':selected').text()]); + metadata.set('Latitude (°):', [this.latEl.value]); + metadata.set('Longitude (°):', [this.lonEl.value]); + metadata.set('Intensity Measure Type:', [$(this.imtEl).find(':selected').text()]); + metadata.set('V<sub>s</sub>30:', [$(this.vs30El).find(':selected').text()]); + metadata.set('Return Period (years):', [this.returnPeriodEl.value + ' years']); + + return metadata; + } + + /** + * @method getUsage + * + * Call deagg web service and get the JSON usage. Once usage is received, + * build the inputs. + */ + getUsage() { + let jsonCall = Tools.getJSON(this.webServiceUrl); + this.spinner.on(jsonCall.reject); + + jsonCall.promise.then((usage) => { + NshmpError.checkResponse(usage); + this.buildInputs(usage); + }).catch((errorMessage) => { + this.spinner.off(); + NshmpError.throwError(errorMessage); + }); + } + + /** + * @method onEditionChange + * + * Listen for the edition menu to change and update region, IMT, + * and Vs30 menus and update input tooltip for new values. + */ + onEditionChange() { + $(this.editionEl).on('change', (event) => { + this.setRegionMenu(); + this.setImtMenu(); + this.setVs30Menu(); + this.latEl.value = null; + this.lonEl.value = null; + this.addInputTooltip(); + }); + } + + /** + * @method onInputChange + * + * Listen for any menu or input to change in the control panel and check + * if any input box has bad values. If values are good allow the plot + * to be updated. + */ + onInputChange() { + $(this.inputsEl).on('change input', (event) => { + let hasError; + let val; + $(this.inputsEl).find('input').each((i, d) => { + val = parseFloat(d.value); + hasError = isNaN(val) ? true : $(d.parentNode).hasClass('has-error'); + return !hasError; + }); + this.footerOptions.updateBtnDisable = hasError; + this.footer.setOptions(this.footerOptions); + }); + } + + /** + * @method onRegionChange + * + * Listen for the region menu to change and update the IMT and Vs30 + * menus and update the input tooltips for new values. + */ + onRegionChange() { + $(this.regionEl).on('change', (event) => { + this.latEl.value = null; + this.lonEl.value = null; + this.addInputTooltip(); + this.setImtMenu(); + this.setVs30Menu(); + }); + } + /** + * @method onReturnPeriodChange + * + * Listen for a return period button to be clicked or the return period + * input to be changed. Update the value of the input with the new button + * value. If a value is input, check to see if the value matches + * any of the button values and make that button active. + */ + onReturnPeriodChange() { + // Update input with button value + $(this.returnPeriodBtnsEl).on('click', (event) => { + let el = $(event.target).closest('.btn').find('input'); + let val = el.val(); + this.returnPeriodEl.value = val; + }); + + // See if input value matches a button value + $(this.returnPeriodEl).on('input', (event) => { + this.checkReturnPeriod(); + + d3.select(this.returnPeriodBtnsEl) + .selectAll('label') + .classed('active', false) + .selectAll('input') + .select((d, i, els) => { + if (this.returnPeriodEl.value == els[i].value) { + return els[i].parentNode; + } + }) + .classed('active', true); + }); + } + + /** + * @method plotSetup + * + * Set specific plot options. + * @return {D3GeoDeagg} - New instance of the D3GeoDeagg class for plotting + * results. + */ + plotSetup() { + let viewOptions = {}; + let upperPlotOptions = { + marginBottom: 0, + marginLeft: 0, + marginRight: 0, + marginTop: 10, + }; + let lowerPlotOptions = {}; + + return new D3GeoDeagg( + this.contentEl, + viewOptions, + upperPlotOptions, + lowerPlotOptions) + .withPlotHeader() + .withPlotFooter(); + } + + /** + * @method serializeUrl + * + * Get all current values from the control panel and serialize using + * the name attribute. Update the hash part of the URL to the + * key value pairs. + * @return {String} - URL to call webservices. + */ + serializeUrl() { + let inputs = $(this.inputsEl).serialize(); + let url = this.webServiceUrl + '?' + inputs; + window.location.hash = inputs; + + return url; + } + + /** + * @method setDefaultReturnPeriod + * + * Set the default return period value and button. + */ + setDefaultReturnPeriod() { + d3.select(this.returnPeriodBtnsEl) + .selectAll('input') + .filter('input[value="' + this.options.defaultReturnPeriod + '"]') + .select((d, i, els) => { return els[0].parentNode }) + .classed('active', true); + this.returnPeriodEl.value = this.options.defaultReturnPeriod; + } + + /** + * @method setRegionMenu + * + * Set the region select menu given the supported regions from the + * selected edition. + */ + setRegionMenu() { + let selectedEdition = Tools.stringToParameter( + this.parameters.edition, this.editionEl.value); + + let supportedRegions = Tools.stringArrayToParameters( + this.parameters.region, selectedEdition.supports.region); + + Tools.setSelectMenu(this.regionEl, supportedRegions); + } + + /** + * @method setImtMenu + * + * Set the IMT select menu given the common supported IMT values from the + * selected edition and region. + */ + setImtMenu() { + let selectedEdition = Tools.stringToParameter( + this.parameters.edition, this.editionEl.value); + + let selectedRegion = Tools.stringToParameter( + this.parameters.region, this.regionEl.value); + + let supports = []; + supports.push(selectedEdition.supports.imt) + supports.push(selectedRegion.supports.imt); + + let supportedImts = Tools.supportedParameters( + this.parameters.imt, supports); + + Tools.setSelectMenu(this.imtEl, supportedImts); + } + + /** + * @method setVs30 + * + * Set the VS30 select menu given the common supported Vs30 values from the + * selected edition and region. + */ + setVs30Menu() { + let selectedEdition = Tools.stringToParameter( + this.parameters.edition, this.editionEl.value); + + let selectedRegion = Tools.stringToParameter( + this.parameters.region, this.regionEl.value); + + let supports = []; + supports.push(selectedEdition.supports.vs30) + supports.push(selectedRegion.supports.vs30); + + let supportedVs30 = Tools.supportedParameters( + this.parameters.vs30, supports); + + Tools.setSelectMenu(this.vs30El, supportedVs30); + } + + /** + * @method updatePlot + * + * Call the deagg web service and plot the single source total component + * values using the D3GeoDeagg plotting class. + */ + updatePlot() { + let url = this.serializeUrl(); + let metadata = this.getMetadata(); + + let jsonCall = Tools.getJSON(url); + this.spinner.on(jsonCall.reject, 'Calculating'); + + jsonCall.promise.then((response) => { + this.spinner.off(); + NshmpError.checkResponse(response, this.plot); + + this.footer.setMetadata(response.server); + + // Find total data component + let totalData = response.response[0].data.find((d, i) => { + return d.component == 'Total'; + }); + + // Find the single sources + let singleSources = totalData.sources.filter((d, i) => { + return d.type == 'SINGLE'; + }); + + let seriesData = []; + let seriesLabels = []; + let seriesIds = []; + + singleSources.forEach((d, i) => { + seriesData.push([[d.longitude, d.latitude, d.contribution]]); + seriesLabels.push(d.name); + seriesIds.push(d.name.replace(/ /g, '_')); + }); + + metadata.set('url', [window.location.href]); + metadata.set('date', [response.date]); + + let lat = response.response[0].metadata.latitude; + let lon = response.response[0].metadata.longitude; + // Where the map should rotate to + let rotate = [-lon, -lat, 0]; + + let siteTitle = this.testSitePicker + .getTestSiteTitle(this.regionEl.value); + let vs30 = $(':selected', this.vs30El).text(); + let imt = $(':selected', this.imtEl).text(); + let edition = $(':selected', this.editionEl).text(); + + let title = edition + ', ' + siteTitle + ', ' + imt + ', ' + vs30; + + this.plot.setPlotTitle(title) + .setSiteLocation({latitude: lat, longitude: lon}) + .setMetadata(metadata) + .setUpperData(seriesData) + .setUpperPlotFilename('geoDeagg') + .setUpperDataTableTitle('Deaggregation Contribution') + .setUpperPlotIds(seriesIds) + .setUpperPlotLabels(seriesLabels) + .plotData(this.plot.upperPanel, rotate); + + $(this.footer.rawBtnEl).off(); + $(this.footer.rawBtnEl).on('click', (event) => { + window.open(url); + }); + + }).catch((errorMessage) => { + this.spinner.off(); + NshmpError.throwError(errorMessage); + }); + } + +} diff --git a/webapp/apps/js/GmmDistance.js b/webapp/apps/js/GmmDistance.js new file mode 100644 index 000000000..f66886149 --- /dev/null +++ b/webapp/apps/js/GmmDistance.js @@ -0,0 +1,241 @@ + +import { D3LineData } from './d3/data/D3LineData.js'; +import { D3LineOptions } from './d3/options/D3LineOptions.js'; +import { D3LinePlot } from './d3/D3LinePlot.js'; +import { D3LineSubViewOptions } from './d3/options/D3LineSubViewOptions.js'; +import { D3LineView } from './d3/view/D3LineView.js'; +import { D3LineSubView } from './d3/view/D3LineSubView.js'; +import { D3LineLegendOptions } from './d3/options/D3LineLegendOptions.js'; + +import { Gmm } from './lib/Gmm.js'; +import NshmpError from './error/NshmpError.js'; +import Tools from './lib/Tools.js'; + +/** +* @class GmmDistance +* @extends Gmm +* +* @fileoverview Class for gmm-distance..html, ground motion Vs. +* distance web app. +* This class plots the results of nshmp-haz-ws/gmm/distance web service. +* This class will first call out to nshmp-haz-ws/gmm/distance web service +* to obtain the usage and create the control panel with the following: +* - Ground motions models +* - Intensity measure type +* - Magnitude +* - zTop +* - Dip +* - Width +* - Vs30 +* - Vs30 measured or inferred +* - Z1.0 +* - Z2.5 +* Once the control panel is set, it can be used to select desired +* parameters and plot ground motion vs. distance. +* Already defined DOM elements: +* - #gmms +* - .gmm-alpha +* - .gmm-group +* - #gmm-sorter +* - #inputs +* - #Mw +* - #vs30 +* - #z1p0 +* - #z2p5 +* +* @author bclayton@usgs.gov (Brandon Clayton) +*/ +export class GmmDistance extends Gmm { + + /** + * @param {HTMLElement} contentEl - Container element to put plots + */ + constructor(config) { + let webServiceUrl = '/nshmp-haz-ws/gmm/distance'; + let webApp = 'GmmDistance'; + super(webApp, webServiceUrl, config); + this.header.setTitle('Ground Motion Vs. Distance'); + + /** + * @type {{ + * rMaxDefault: {number} - Maximum distance, + * rMinDefault: {number} - Minimum distance, + * }} Object + */ + this.options = { + rMax: 300, + rMin: 0.1, + }; + + /** @type {number} */ + this.rMax = this.options.rMax; + /** @type {number} */ + this.rMin = this.options.rMin; + + /** @type {HTMLElement} */ + this.contentEl = document.querySelector('#content'); + /** @type {HTMLElement} */ + this.dipEl = document.querySelector('#dip'); + /** @type {HTMLElement} */ + this.imtEl = document.querySelector('#imt'); + /** @type {HTMLElement} */ + this.widthEl = document.querySelector('#width'); + /** @type {HTMLElement} */ + this.zTopEl = document.querySelector('#zTop'); + + this.gmmView = this.setupGMMView(); + this.gmmLinePlot = new D3LinePlot(this.gmmView); + + $(this.imtEl).change((event) => { this.imtOnChange(); }); + + this.getUsage(); + } + + /** + * Get current chosen parameters. + * @return {Map<String, Array<String>>} The metadata Map + */ + getMetadata() { + let gmms = this.getCurrentGmms(); + + let metadata = new Map(); + metadata.set('Ground Motion Model:', gmms); + metadata.set('Intensity Measure Type:', [$(this.imtEl).find(':selected').text()]); + metadata.set('M<sub>W</sub>:', [this.MwEl.value]); + metadata.set('Z<sub>Top</sub> (km):', [this.zTopEl.value]); + metadata.set('Dip (°):', [this.dipEl.value]); + metadata.set('Width (km):', [this.widthEl.value]); + metadata.set('Minimum Rupture Distance (km):', [this.rMin]); + metadata.set('Maximum Rupture Distance (km):', [this.rMax]); + metadata.set('V<sub>S</sub>30 (m/s):', [this.vs30El.value]); + metadata.set('Z<sub>1.0</sub> (km):', [this.z1p0El.value]); + metadata.set('Z<sub>2.5</sub> (km):', [this.z2p5El.value]); + + return metadata; + } + + /** + * Plot the ground motion vs. distance response. + * + * @param {Object} response + */ + plotGMM(response) { + this.gmmLinePlot.clearAll(); + let lineData = this.responseToLineData(response, this.gmmView.upperSubView); + this.gmmLinePlot.plot(lineData); + + let metadata = this.getMetadata(); + this.gmmView.setMetadata(metadata); + this.gmmView.createMetadataTable(); + + this.gmmView.setSaveData(lineData); + this.gmmView.createDataTable(lineData); + } + + /** + * Convert the response to line data. + * + * @param {Object} response The response + * @param {D3LineSubView} subView The sub view to plot the line data + */ + responseToLineData(response, subView) { + let dataBuilder = D3LineData.builder().subView(subView); + + for (let responseData of response.means.data) { + let lineOptions = D3LineOptions.builder() + .id(responseData.id) + .label(responseData.label) + .markerSize(4) + .build(); + + dataBuilder.data(responseData.data.xs, responseData.data.ys, lineOptions); + } + + return dataBuilder.build(); + } + + /** + * @override + * @method serializeGmmUrl + * + * Serialize all forms for ground motion web wervice and set + * set the hash of the window location to reflect the form values. + */ + serializeGmmUrl(){ + let controlInputs = $(this.inputsEl).serialize(); + let inputs = controlInputs + '&' + + '&rMin=' + this.rMin + + '&rMax=' + this.rMax; + let dynamic = this.config.server.dynamic; + let url = dynamic + this.webServiceUrl + '?' + inputs; + window.location.hash = inputs; + + return url; + } + + /** + * Setup the plot view + */ + setupGMMView() { + /* Upper sub view legend options */ + let legendOptions = D3LineLegendOptions.upperBuilder() + .location('bottom-left') + .build(); + + /* Upper sub view options: gmm vs distance */ + let upperSubViewOptions = D3LineSubViewOptions.upperBuilder() + .filename('gmm-distance') + .label('Ground Motion Vs. Distance') + .legendOptions(legendOptions) + .lineLabel('Ground Motion Model') + .xAxisScale('log') + .xLabel('Distance (km)') + .yAxisScale('log') + .yLabel('Median Ground Motion (g)') + .build(); + + let view = D3LineView.builder() + .containerEl(this.contentEl) + .upperSubViewOptions(upperSubViewOptions) + .build(); + + view.setTitle('Ground Motion Vs. Distance'); + + return view; + } + + /** + * Call the ground motion web service and plot the results + */ + updatePlot() { + let url = this.serializeGmmUrl(); + + // Call ground motion gmm/distance web service + let jsonCall = Tools.getJSON(url); + this.spinner.on(jsonCall.reject, 'Calculating'); + + jsonCall.promise.then((response) => { + this.spinner.off(); + NshmpError.checkResponse(response, this.plot); + + this.footer.setMetadata(response.server); + + let selectedImt = $(':selected', this.imtEl); + let selectedImtDisplay = selectedImt.text(); + + this.gmmView.setTitle(`Ground Motion Vs. Distance: ${selectedImtDisplay}`); + + this.plotGMM(response); + + $(this.footer.rawBtnEl).off() + $(this.footer.rawBtnEl).click((event) => { + window.open(url); + }); + }).catch((errorMessage) => { + this.spinner.off(); + this.gmmLinePlot.clearAll(); + NshmpError.throwError(errorMessage); + }); + } + +} diff --git a/webapp/apps/js/HwFw.js b/webapp/apps/js/HwFw.js new file mode 100644 index 000000000..60edfe7f9 --- /dev/null +++ b/webapp/apps/js/HwFw.js @@ -0,0 +1,568 @@ + +import { D3LineData } from './d3/data/D3LineData.js'; +import { D3LineOptions } from './d3/options/D3LineOptions.js'; +import { D3LinePlot } from './d3/D3LinePlot.js'; +import { D3LineSubViewOptions } from './d3/options/D3LineSubViewOptions.js'; +import { D3LineView } from './d3/view/D3LineView.js'; +import { D3LineSubView } from './d3/view/D3LineSubView.js'; +import { D3LineViewOptions } from './d3/options/D3LineViewOptions.js'; +import { D3SaveFigureOptions } from './d3/options/D3SaveFigureOptions.js'; + +import Constraints from './lib/Constraints.js'; +import { Gmm } from './lib/Gmm.js'; +import Tools from './lib/Tools.js'; +import NshmpError from './error/NshmpError.js'; + +/** +* @class HwFw +* @extends Gmm +* +* @fileoverview Class for hw-fw.html, hanging wall effects web app. +* This class plots the results of nshmp-haz-ws/gmm/hw-fw web service. +* This class will first call out to nshmp-haz-ws/gmm/hw-fw web service +* to obtain the usage and create the control panel with the following: +* - Ground motions models +* - Intensity measure type +* - Magnitude +* - Vs30 +* - Vs30 measured or inferred +* - Z1.0 +* - Z2.5 +* Once the control panel is set, it can be used to select desired +* parameters and plot ground motion vs. distance. +* A fault plane is shown underneath the ground motion vs. distance plot. +* To show hanging wall effects, three range sliders are shown next to the +* fault plane and control the fault plane's: +* - Dip (range: 0-90) +* - Width (range: 1-30km) +* - zTop (range: 0-10km) +* The fault plane is limited to having a fault bottom of 20km. +* Once the fault plane is changed with either of the sliders, the +* ground motions vs. distance plot is updated automatically. +* +* @author bclayton@usgs.gov (Brandon Clayton) +*/ +export class HwFw extends Gmm { + + /** + * @param {HTMLElement} contentEl - Container element to put plots + */ + constructor(config) { + let webServiceUrl = '/nshmp-haz-ws/gmm/hw-fw'; + let webApp = 'HwFw'; + super(webApp, webServiceUrl, config); + this.header.setTitle('Hanging Wall Effects'); + + /** + * @type {{ + * lowerPlotWidth: {number} - Lower plot width in percentage, + * minDip: {number} - Minimum dip allowed in degrees, + * minWidth: {number} - Minimum width allowed in km, + * minZTop: {number} - Minimum zTop allowed in km, + * maxDip: {number} - Maximum dip allowed in degrees, + * maxWidth: {number} - Maximum width allowed in km, + * maxZTop: {number} - Maximum zTop allowed in km, + * maxFaultBottom: {number} - Maximum fault bottom allowed in km, + * rMaxDefault: {number} - Maximum distance, + * rMinDefault: {number} - Minimum distance, + * stepDip: {number} - Step in dip in degrees, + * stepWidth: {number} - Step in width in km, + * stepZTop: {number} - step in zTop in km + * }} Object + */ + this.options = { + lowerPlotWidth: 0.65, + minDip: 10, + minWidth: 1, + minZTop: 0, + maxDip: 90, + maxWidth: 30, + maxZTop: 10, + maxFaultBottom: 20, + rMax: 70, + rMin: -20, + stepDip: 5, + stepWidth: 0.5, + stepZTop: 0.5, + }; + + /** @type {number} */ + this.rMax = this.options.rMax; + /** @type {number} */ + this.rMin = this.options.rMin; + + /** @type {HTMLElement} */ + this.contentEl = document.querySelector("#content"); + /** @type {HTMLElement} */ + this.dipEl = undefined; + /** @type {HTMLElement} */ + this.dipSliderEl = undefined; + /** @type {HTMLElement} */ + this.imtEl = document.querySelector('#imt'); + /** @type {HTMLElement} */ + this.widthEl = undefined; + /** @type {HTMLElement} */ + this.widthSliderEl = undefined; + /** @type {HTMLElement} */ + this.zTopEl = undefined; + /** @type {HTMLElement} */ + this.zTopSliderEl = undefined; + + this.xLimit = [ this.rMin, this.rMax ]; + + this.gmmView = this.setupGMMView(); + this.gmmLinePlot = new D3LinePlot(this.gmmView); + + this.faultXLimit = this.gmmView.lowerSubView.options.defaultXLimit; + this.faultYLimit = [ 0, this.options.maxFaultBottom ]; + + $(this.imtEl).change((event) => { this.imtOnChange(); }); + + this.faultSliders(); + + this.getUsage(this.setSliderValues); + } + + /** + * @method checkFaultExtent + * + * Check to see if the fault plane is or well be out of the + * defined fault bottom maximum + * @return {{ + * maxDip: {number} - Max dip allowed given other values, + * maxWidth: {number} - Max width allowed given other values, + * maxZTop: {number} - Max zTop allowed given other values, + * pastExtent: {Boolean} + * }} + */ + checkFaultExtent() { + let faultCheck = {}; + let dip = this.dip_val(); + let width = this.width_val(); + let zTop = this.zTop_val(); + let faultBottom = width * Math.sin(dip) + zTop; + let maxFaultBottom = this.options.maxFaultBottom; + faultCheck.maxDip = Math.asin((maxFaultBottom - zTop) / width); + faultCheck.maxDip = faultCheck.maxDip * 180.0 / Math.PI; + faultCheck.maxDip = isNaN(faultCheck.maxDip) ? 90 : faultCheck.maxDip; + faultCheck.maxWidth = (maxFaultBottom - zTop) / Math.sin(dip); + faultCheck.maxZTop = maxFaultBottom - width * Math.sin(dip); + faultCheck.pastExtent = faultBottom > maxFaultBottom ? true : false; + + return faultCheck; + } + + /** + * @method faultSliders + * + * Create range sliders for the fault plane plot + */ + faultSliders() { + let sliderInfo = [ + { + name: 'Dip', + sliderId: 'dip-slider', + valueId: 'dip', + min: this.options.minDip, + max: this.options.maxDip, + step: this.options.stepDip, + unit: '°', + },{ + name: 'Width', + sliderId: 'width-slider', + valueId: 'width', + min: this.options.minWidth, + max: this.options.maxWidth, + step: this.options.stepWidth, + unit: 'km', + },{ + name: 'zTop', + sliderId: 'zTop-slider', + valueId: 'zTop', + min: this.options.minZTop, + max: this.options.maxZTop, + step: this.options.stepZTop, + unit: 'km', + } + ]; + + let width = (1 - this.options.lowerPlotWidth) * 100; + d3.select(this.gmmView.lowerSubView.svg.svgEl) + .style('margin-right', width + '%'); + + let faultFormD3 = d3.select(this.gmmView.lowerSubView.subViewBodyEl) + .append('form') + .attr('class', 'form fault-form'); + + let divD3 = faultFormD3.selectAll('div') + .data(sliderInfo) + .enter() + .append('div') + .attr('class', 'slider-form'); + + divD3.append('label') + .attr('for', (d,i) => { return d.sliderId }) + .text((d,i) => { return d.name }); + + let formD3 = divD3.append('div') + .attr('class', 'row'); + + formD3.append('div') + .attr('class', 'col-sm-12 col-md-12 col-lg-8') + .html((d,i) => { + return '<input class="slider" id=' + d.sliderId + ' type="range"' + + ' min=' + d.min + ' max=' + d.max + + ' step=' + d.step + ' />' + }); + + formD3.append('div') + .attr('class', 'col-sm-12 col-md-6 col-lg-4') + .append('div') + .attr('class', 'input-group input-group-sm') + .html((d,i) => { + return '<input class="form-control input-sm slider-value"' + + ' id=' + d.valueId + ' type="number"' + + 'name="' + d.valueId + '"' + + ' min=' + d.min + ' max=' + d.max + ' step="' + d.step + '" >' + + '<span class="input-group-addon input-sm"> ' + d.unit + ' </span>'; + }); + + this.dipSliderEl = document.querySelector('#dip-slider'); + this.dipEl = document.querySelector('#dip'); + this.faultFormEl = document.querySelector('.fault-form'); + this.widthSliderEl = document.querySelector('#width-slider'); + this.widthEl = document.querySelector('#width'); + this.zTopSliderEl = document.querySelector('#zTop-slider'); + this.zTopEl = document.querySelector('#zTop'); + + // Update tooltips + Constraints.addTooltip( + this.dipEl, this.options.minDip, this.options.maxDip); + Constraints.addTooltip( + this.widthEl, this.options.minWidth, this.options.maxWidth); + Constraints.addTooltip( + this.zTopEl, this.options.minZTop, this.options.maxZTop); + + // Listen for changes on fault form inputs and sliders + $('.fault-form').bind('input keyup mouseup', (event) => { + this.inputsOnInput(); + this.faultSliderOnChange(event) + }); + } + + /** + * @method faultSlidersOnChange + * + * Update the fault plane plot with change in each slider or input + * field and update ground motion Vs. distance plot if inputted + * values are good. + * @param {!Event} event - Event that triggered the change + */ + faultSliderOnChange(event) { + let minVal; + let maxVal; + let maxValStr; + let parEl; + let step; + let sliderEl; + let valueEl; + + let id = event.target.id; + let value = parseFloat(event.target.value); + let inputType = event.target.type; + let eventType = event.type; + + if (!id || id.length == 0 || isNaN(value)){ + return; + } + + if (id == this.dipSliderEl.id || id == this.dipEl.id) { + parEl = this.dipEl; + sliderEl = this.dipSliderEl; + valueEl = this.dipEl; + maxValStr = 'maxDip'; + step = this.options.stepDip; + maxVal = this.options.maxDip; + minVal = this.options.minDip; + + } else if (id == this.widthSliderEl.id || id == this.widthEl.id) { + parEl = this.widthEl; + sliderEl = this.widthSliderEl; + valueEl = this.widthEl; + step = this.options.stepWidth; + maxValStr = 'maxWidth'; + maxVal = this.options.maxWidth; + minVal = this.options.minWidth; + } else if (id == this.zTopSliderEl.id || id == this.zTopEl.id) { + parEl = this.zTopEl; + sliderEl = this.zTopSliderEl; + valueEl = this.zTopEl; + maxValStr = 'maxZTop'; + step = this.options.stepZTop; + maxVal = this.options.maxZTop; + minVal = this.options.minZTop; + } + + let canSubmit = Constraints.check(valueEl, minVal, maxVal); + if (!canSubmit) return; + + parEl.value = value; + sliderEl.value = value; + valueEl.value = value; + let faultCheck = this.checkFaultExtent(); + if (faultCheck.pastExtent) { + // Round down to nearest step + event.target.value = + Math.round((faultCheck[maxValStr] - step) / step) * step; + valueEl.value = event.target.value; + parEl.value = event.target.value; + return; + } + + this.plotFaultPlane(); + if (inputType == 'range' && + (eventType == 'keyup' || eventType == 'mouseup')) { + this.updatePlot(); + } else if (inputType != 'range') { + this.updatePlot(); + } + } + + /** + * Get current fault plane line data + */ + getFaultPlaneLineData() { + let dip = this.dip_val(); + let width = this.width_val(); + let zTop = this.zTop_val(); + + let xMin = 0; + let xMax = width * Math.cos(dip); + let x = [xMin, Number(xMax.toFixed(4))]; + + let yMin = zTop; + let yMax = width * Math.sin(dip) + zTop; + let y = [yMin, Number(yMax.toFixed(4))]; + + let lineOptions = D3LineOptions.builder() + .id('fault') + .label('Fault Plane') + .markerSize(0) + .build(); + + let lineData = D3LineData.builder() + .subView(this.gmmView.lowerSubView) + .data(x, y, lineOptions) + .xLimit(this.faultXLimit) + .yLimit(this.faultYLimit) + .build(); + + return lineData; + } + + /** + * Get current chosen parameters. + * @return {Map<String, Array<String>>} The metadata Map + */ + getMetadata() { + let gmms = this.getCurrentGmms(); + + let metadata = new Map(); + metadata.set('Ground Motion Model:', gmms); + metadata.set('Intensity Measure Type:', [$(this.imtEl).find(':selected').text()]); + metadata.set('M<sub>W</sub>:', [this.MwEl.value]); + metadata.set('Z<sub>Top</sub> (km):', [this.zTopEl.value]); + metadata.set('Dip (°):', [this.dipEl.value]); + metadata.set('Width (km):', [this.widthEl.value]); + metadata.set('Minimum Rupture Distance (km):', [this.rMin]); + metadata.set('Maximum Rupture Distance (km):', [this.rMax]); + metadata.set('V<sub>S</sub>30 (m/s):', [this.vs30El.value]); + metadata.set('Z<sub>1.0</sub> (km):', [this.z1p0El.value]); + metadata.set('Z<sub>2.5</sub> (km):', [this.z2p5El.value]); + + return metadata; + } + + plotFaultPlane() { + this.gmmLinePlot.clear(this.gmmView.lowerSubView); + let lineData = this.getFaultPlaneLineData(); + this.gmmLinePlot.plot(lineData); + } + + /** + * Plot the ground motion vs. distance response. + * + * @param {Object} response + */ + plotGMM(response) { + this.gmmLinePlot.clear(this.gmmView.upperSubView); + let lineData = this.responseToLineData(response, this.gmmView.upperSubView); + this.gmmLinePlot.plot(lineData); + + let metadata = this.getMetadata(); + this.gmmView.setMetadata(metadata); + this.gmmView.createMetadataTable(); + + this.gmmView.setSaveData(lineData); + this.gmmView.createDataTable(lineData); + + this.plotFaultPlane(); + } + + /** + * Convert the response to line data. + * + * @param {Object} response The response + * @param {D3LineSubView} subView The sub view to plot the line data + */ + responseToLineData(response, subView) { + let dataBuilder = D3LineData.builder().subView(subView).xLimit(this.xLimit); + + for (let responseData of response.means.data) { + let lineOptions = D3LineOptions.builder() + .id(responseData.id) + .label(responseData.label) + .markerSize(4) + .build(); + + dataBuilder.data(responseData.data.xs, responseData.data.ys, lineOptions); + } + + return dataBuilder.build(); + } + + + /** + * Setup the plot view + */ + setupGMMView() { + /* Upper sub view options: gmm vs distance */ + let upperSubViewOptions = D3LineSubViewOptions.upperBuilder() + .defaultXLimit(this.xLimit) + .filename('hanging-wall-effects') + .label('Ground Motion Vs. Distance') + .lineLabel('Ground Motion Model') + .xAxisScale('linear') + .xLabel('Distance (km)') + .yAxisScale('linear') + .yLabel('Median Ground Motion (g)') + .build(); + + let lowerPlotWidth = Math.floor(upperSubViewOptions.plotWidth * this.options.lowerPlotWidth); + + let margins = upperSubViewOptions.paddingLeft + + upperSubViewOptions.paddingRight + upperSubViewOptions.marginLeft + + upperSubViewOptions.marginRight; + + let upperXWidth = upperSubViewOptions.plotWidth - margins; + let lowerXWidth = lowerPlotWidth - margins; + + /* Calculate lower plot X limit to match upper plot */ + let lowerXMax = ((lowerXWidth * (this.rMax - this.rMin)) / upperXWidth) + this.rMin; + + /* Lower sub view save figure options */ + let lowerSaveOptions = D3SaveFigureOptions.builder() + .addTitle(false) + .build(); + + /* Lower sub view options: fault plane */ + let lowerSubViewOptions = D3LineSubViewOptions.lowerBuilder() + .defaultXLimit([ this.options.rMin, lowerXMax ]) + .filename('fault-plane') + .label('Ground Motion Vs. Distance') + .lineLabel('Ground Motion Model') + .marginBottom(20) + .marginTop(20) + .paddingTop(30) + .plotWidth(lowerPlotWidth) + .saveFigureOptions(lowerSaveOptions) + .showLegend(false) + .xAxisLocation('top') + .xAxisNice(false) + .xAxisScale('linear') + .xLabel('Distance (km)') + .xTickMarks(Math.floor(upperSubViewOptions.xTickMarks / 2)) + .yAxisReverse(true) + .yAxisScale('linear') + .yLabel('Median Ground Motion (g)') + .build(); + + let viewOptions = D3LineViewOptions.builder() + .disableXAxisBtns(true) + .syncYAxisScale(false) + .build(); + + let view = D3LineView.builder() + .addLowerSubView(true) + .containerEl(this.contentEl) + .upperSubViewOptions(upperSubViewOptions) + .lowerSubViewOptions(lowerSubViewOptions) + .viewOptions(viewOptions) + .build(); + + view.setTitle('Hanging Wall Effects'); + + return view; + } + + /** + * @method setSliderValues + * + * Set the slider values to match the input fields + */ + setSliderValues() { + this.dipSliderEl.value = this.dipEl.value; + this.widthSliderEl.value = this.widthEl.value; + this.zTopSliderEl.value = this.zTopEl.value; + } + + /** + * @override + * @method serializeGmmUrl + * + * Serialize all forms for ground motion web wervice and set + * set the hash of the window location to reflect the form values. + */ + serializeGmmUrl(){ + let controlInputs = $(this.inputsEl).serialize(); + let faultInputs = $(this.faultFormEl).serialize(); + let inputs = controlInputs + '&' + faultInputs + + '&rMin=' + this.rMin + + '&rMax=' + this.rMax; + let dynamic = this.config.server.dynamic; + let url = dynamic + this.webServiceUrl + '?' + inputs; + window.location.hash = inputs; + + return url; + } + + /** + * Call the ground motion web service and plot the results + */ + updatePlot() { + let url = this.serializeGmmUrl(); + // Call ground motion hw-fw web service + let jsonCall = Tools.getJSON(url); + this.spinner.on(jsonCall.reject, 'Calculating'); + + jsonCall.promise.then((response) => { + this.spinner.off(); + this.footer.setMetadata(response.server); + + let selectedImt = $(':selected', this.imtEl); + let selectedImtDisplay = selectedImt.text(); + this.gmmView.setTitle(`Hanging Wall Effects: ${selectedImtDisplay}`); + + this.plotGMM(response); + + $(this.footer.rawBtnEl).off() + $(this.footer.rawBtnEl).click((event) => { + window.open(url); + }); + }).catch((errorMessage) => { + this.spinner.off(); + NshmpError.throwError(errorMessage); + }); + } + +} diff --git a/webapp/apps/js/ModelCompare.js b/webapp/apps/js/ModelCompare.js new file mode 100644 index 000000000..02ffd157d --- /dev/null +++ b/webapp/apps/js/ModelCompare.js @@ -0,0 +1,286 @@ + +import { D3LineData } from './d3/data/D3LineData.js'; +import { D3LineLegendOptions } from './d3/options/D3LineLegendOptions.js'; +import { D3LineOptions } from './d3/options/D3LineOptions.js'; +import { D3LinePlot } from './d3/D3LinePlot.js'; +import { D3LineSubViewOptions } from './d3/options/D3LineSubViewOptions.js'; +import { D3LineView } from './d3/view/D3LineView.js'; +import { D3LineViewOptions } from './d3/options/D3LineViewOptions.js'; + +import { Hazard } from './lib/Hazard.js'; +import NshmpError from './error/NshmpError.js'; + +export class ModelCompare extends Hazard { + + constructor(config) { + super(config); + + this.header.setTitle("Model Comparison"); + + this.options = { + type: "compare", + regionDefault: "COUS", + imtDefault: "PGA", + vs30Default: 760, + }; + + this.contentEl = document.querySelector('#content'); + this.hazardPlotTitle = 'Hazard Curves'; + + /* Plot view options */ + this.viewOptions = D3LineViewOptions.builder() + .titleFontSize(14) + .build(); + + /* Hazard curve plot setup */ + this.hazardView = this.setupHazardView(); + this.hazardLinePlot = new D3LinePlot(this.hazardView); + + this.comparableRegions = [ + { + display: "Alaska", + value: "AK", + staticValue: "AK0P10", + dynamicValue: "AK" + }, { + display: "Central & Eastern US", + value: "CEUS", + staticValue: "CEUS0P10", + dynamicValue: "CEUS" + }, { + display: "Conterminous US", + value: "COUS", + staticValue: "COUS0P05", + dynamicValue: "COUS" + }, { + display: "Western US", + value: "WUS", + staticValue: "WUS0P05", + dynamicValue: "WUS" + } + ]; + + let setParameters = (par) => { + this.parameters = par; + this.buildInputs() + }; + + this.getHazardParameters(setParameters); + + $(this.footer.updateBtnEl).click(() => { + this.callHazard((result) => { this.callHazardCallback(result) }); + }); + + } + + /** + * Build the view for the hazard curves. + * + * @returns {D3LineView} The hazard line view + */ + setupHazardView() { + /* Upper sub view legend options: hazard plot */ + let upperLegendOptions = D3LineLegendOptions.upperBuilder() + .location('bottom-left') + .build(); + + /* Upper sub view options: hazard plot */ + let upperSubViewOptions = D3LineSubViewOptions.upperBuilder() + .filename('hazard-compare') + .label('Hazard Curves') + .lineLabel('Edition') + .legendOptions(upperLegendOptions) + .xAxisScale('log') + .xLabel('Ground Motion (g)') + .yAxisScale('log') + .yLabel('Annual Frequency of Exceedence') + .yValueToExponent(true) + .build(); + + /* Build the view */ + let view = D3LineView.builder() + .addLowerSubView(false) + .containerEl(this.contentEl) + .viewOptions(this.viewOptions) + .upperSubViewOptions(upperSubViewOptions) + .build(); + + view.setTitle(this.hazardPlotTitle); + + return view; + } + + buildInputs() { + this.spinner.off(); + + this.testSitePicker.on('testSiteLoad', (event) => { + this.checkQuery(); + }); + + this.setParameterMenu("region", this.comparableRegions); + this.setBounds(); + + let supportedEditions = this.supportedEditions(); + this.setParameterMenu("edition", supportedEditions); + d3.select(this.editionEl) + .selectAll("option") + .attr("selected", true); + + let supportedImt = this.supportedValues("imt"); + let supportedVs30 = this.supportedValues("vs30"); + this.setParameterMenu("imt", supportedImt); + this.setParameterMenu("vs30", supportedVs30); + + $(this.regionEl).change(() => { + this.hazardLinePlot.clearAll(); + + this.clearCoordinates(); + this.setBounds(); + supportedEditions = this.supportedEditions(); + this.setParameterMenu("edition", supportedEditions); + d3.select(this.editionEl) + .selectAll("option") + .attr("selected", true); + + supportedImt = this.supportedValues("imt"); + supportedVs30 = this.supportedValues("vs30"); + this.setParameterMenu("imt", supportedImt); + this.setParameterMenu("vs30", supportedVs30); + }); + + $(this.editionEl).change(() => { + supportedImt = this.supportedValues("imt"); + supportedVs30 = this.supportedValues("vs30"); + this.setParameterMenu("imt", supportedImt); + this.setParameterMenu("vs30", supportedVs30); + }); + + $(this.controlEl).removeClass('hidden'); + + let canSubmit = this.checkQuery(); + if (canSubmit) this.callHazard((result) => { this.callHazardCallback(result) }); + } + + /** + * Get the metadata + * @return {Map<String, Array<String>>} The metadata Map + */ + getMetadata() { + let editionVals = $(this.editionEl).val(); + let editions = []; + editionVals.forEach((val) => { + editions.push(d3.select('#' + val).text()); + }); + + let metadata = new Map(); + metadata.set('Region:', [$(this.regionEl).find(':selected').text()]); + metadata.set('Edition:', editions); + metadata.set('Latitude (°):', [this.latEl.value]); + metadata.set('Longitude (°):', [this.lonEl.value]); + metadata.set('Intensity Measure Type:', [$(this.imtEl).find(':selected').text()]); + metadata.set('V<sub>S</sub>30:', [$(this.vs30El).find(':selected').text()]); + + return metadata; + } + + supportedEditions() { + var selectedRegion = this.comparableRegions.find((region) => { + return region.value == this.regionEl.value; + }); + var supportedEditions = this.parameters.edition + .values.filter((editionValue) => { + return editionValue.supports.region.find((regionValue) => { + return regionValue == selectedRegion.staticValue || + regionValue == selectedRegion.dynamicValue; + }) + }); + + return supportedEditions; + } + + plotHazardCurves(hazardResponse) { + this.spinner.off(); + this.hazardLinePlot.clearAll(); + let lineData = this.hazardResponseToLineData(hazardResponse); + + this.hazardLinePlot.plot(lineData); + this.updatePlotTitle(this.hazardView); + + let metadata = this.getMetadata(); + this.hazardView.setMetadata(metadata); + this.hazardView.createMetadataTable(); + + this.hazardView.setSaveData(lineData); + this.hazardView.createDataTable(lineData); + } + + /** + * + * @param {D3LineView} view + */ + updatePlotTitle(view) { + let imt = $(':selected', this.imtEl).text(); + let vs30 = $(':selected', this.vs30El).text(); + let siteTitle = this.testSitePicker.getTestSiteTitle(this.region()); + let title = `${siteTitle}, ${imt}, ${vs30}`; + + view.setTitle(title); + } + + callHazardCallback(hazardReturn) { + this.plotHazardCurves(hazardReturn); + $(this.imtEl).off(); + $(this.imtEl).change(() => { + this.plotHazardCurves(hazardReturn); + }); + + } + + hazardResponseToLineData(hazardResponses) { + let dataBuilder = D3LineData.builder() + .subView(this.hazardView.upperSubView) + .removeSmallValues(this.Y_MIN_CUTOFF); + + if (hazardResponses.length > 10) { + dataBuilder.colorScheme(d3.schemeCategory20); + } + + for (let response of hazardResponses) { + let dataType = response.dataType; + + let responseData = response.find((responseData) => { + return responseData.metadata.imt.value == this.imtEl.value; + }); + + let data = responseData.data; + let metadata = responseData.metadata; + + let xValues = []; + let yValues = []; + + switch (dataType) { + case 'dynamic': + let componentData = data.find((d) => { return d.component == 'Total'; }); + xValues = metadata.xvalues; + yValues = componentData.yvalues; + break; + case 'static': + xValues = metadata.xvals; + yValues = data[0].yvals; + break; + default: + throw new NshmpError(`Response data type [${dataType}] not found`); + } + + let lineOptions = D3LineOptions.builder() + .id(metadata.edition.value) + .label(metadata.edition.display) + .build(); + + dataBuilder.data(xValues, yValues, lineOptions); + } + + return dataBuilder.build(); + } + +} diff --git a/webapp/apps/js/ModelExplorer.js b/webapp/apps/js/ModelExplorer.js new file mode 100644 index 000000000..c6239474a --- /dev/null +++ b/webapp/apps/js/ModelExplorer.js @@ -0,0 +1,394 @@ + +import { D3LineData } from './d3/data/D3LineData.js'; +import { D3LineLegendOptions } from './d3/options/D3LineLegendOptions.js'; +import { D3LineOptions } from './d3/options/D3LineOptions.js'; +import { D3LinePlot } from './d3/D3LinePlot.js'; +import { D3LineSeriesData } from './d3/data/D3LineSeriesData.js'; +import { D3LineSubViewOptions } from './d3/options/D3LineSubViewOptions.js'; +import { D3LineView } from './d3/view/D3LineView.js'; +import { D3LineViewOptions } from './d3/options/D3LineViewOptions.js'; + +import { Hazard } from './lib/Hazard.js'; +import NshmpError from './error/NshmpError.js'; + +export class ModelExplorer extends Hazard { + + constructor(config) { + super(config); + + this.header.setTitle("Model Explorer"); + + this.options = { + type: "explorer", + editionDefault: "E2014", + regionDefault: "COUS", + imtDefault: "PGA", + vs30Default: 760, + }; + + this.contentEl = document.querySelector("#content"); + this.hazardComponentPlotTitle = 'Hazard Component Curves'; + this.hazardPlotTitle = 'Hazard Curves'; + + /* Plot view options */ + this.viewOptions = D3LineViewOptions.builder() + .titleFontSize(14) + .viewSize('min') + .build(); + + /* Hazard curve plot setup */ + this.hazardView = this.setupHazardView(); + this.hazardView.updateViewSize('max'); + this.hazardLinePlot = new D3LinePlot(this.hazardView); + + /* Hazard component curves setup */ + this.hazardComponentView = this.setupHazardComponentView(); + this.hazardComponentView.hide(); + this.hazardComponentLinePlot = new D3LinePlot(this.hazardComponentView); + + let setParameters = (par) => { + this.parameters = par; + this.buildInputs(); + }; + + this.getHazardParameters(setParameters); + + $(this.footer.updateBtnEl).click(() => { + this.callHazard((result) => { this.callHazardCallback(result); }); + }); + } + + + /** + * Build the view for the hazard component curves plot. + * + * @returns {D3LineView} The hazard component line view + */ + setupHazardComponentView() { + /* Upper sub view legend options: hazard plot */ + let upperLegendOptions = D3LineLegendOptions.upperBuilder() + .location('bottom-left') + .build(); + + /* Upper sub view options: hazard plot */ + let upperSubViewOptions = D3LineSubViewOptions.upperBuilder() + .dragLineSnapTo(1e-10) + .filename('hazard-explorer-components') + .label('Hazard Component Curves') + .legendOptions(upperLegendOptions) + .lineLabel('IMT') + .xLabel('Ground Motion (g)') + .xAxisScale('log') + .yAxisScale('log') + .yLabel('Annual Frequency of Exceedence') + .yValueToExponent(true) + .build(); + + /* Build the view */ + let view = D3LineView.builder() + .containerEl(this.contentEl) + .viewOptions(this.viewOptions) + .upperSubViewOptions(upperSubViewOptions) + .build(); + + view.setTitle(this.hazardComponentPlotTitle); + + return view; + } + + /** + * Build the view for the hazard curve plot. + * + * @returns {D3LineView} The hazard line view + */ + setupHazardView() { + /* Upper sub view legend options: hazard plot */ + let upperLegendOptions = D3LineLegendOptions.upperBuilder() + .location('bottom-left') + .build(); + + /* Upper sub view options: hazard plot */ + let upperSubViewOptions = D3LineSubViewOptions.upperBuilder() + .dragLineSnapTo(1e-8) + .filename('hazard-explorer') + .label('Hazard Curves') + .lineLabel('IMT') + .legendOptions(upperLegendOptions) + .xAxisScale('log') + .xLabel('Ground Motion (g)') + .yAxisScale('log') + .yLabel('Annual Frequency of Exceedence') + .yValueToExponent(true) + .build(); + + /* Build the view */ + let view = D3LineView.builder() + .addLowerSubView(false) + .containerEl(this.contentEl) + .viewOptions(this.viewOptions) + .upperSubViewOptions(upperSubViewOptions) + .build(); + + view.setTitle(this.hazardPlotTitle); + + return view; + } + + buildInputs() { + this.spinner.off(); + + let editionValues = this.parameters.edition.values; + this.setParameterMenu("edition", editionValues); + + let supportedRegions = this.supportedRegions(); + this.setParameterMenu("region", supportedRegions); + this.setBounds(); + + let supportedImt = this.supportedValues("imt") + let supportedVs30 = this.supportedValues("vs30") + this.setParameterMenu("imt", supportedImt); + this.setParameterMenu("vs30", supportedVs30); + + $(this.editionEl).change(() => { + this.resetPlots(); + this.clearCoordinates(); + supportedRegions = this.supportedRegions(); + this.setParameterMenu("region", supportedRegions); + this.setBounds(); + supportedImt = this.supportedValues("imt") + supportedVs30 = this.supportedValues("vs30") + this.setParameterMenu("imt", supportedImt); + this.setParameterMenu("vs30", supportedVs30); + this.testSitePicker.checkForRegion(this.region()); + }); + + $(this.regionEl).change(() => { + this.resetPlots(); + this.clearCoordinates(); + this.setBounds(); + supportedImt = this.supportedValues("imt") + supportedVs30 = this.supportedValues("vs30") + this.setParameterMenu("imt", supportedImt); + this.setParameterMenu("vs30", supportedVs30); + this.testSitePicker.checkForRegion(this.region()); + }); + + $(this.controlEl).removeClass('hidden'); + + this.testSitePicker.on('testSiteLoad', (event) => { + let urlInfo = this.checkQuery(); + if (urlInfo) this.callHazard((result) => { this.callHazardCallback(result); }); + }); + } + + resetPlots() { + this.hazardLinePlot.clearAll(); + this.hazardComponentLinePlot.clearAll(); + this.hazardView.setTitle(this.hazardPlotTitle); + this.hazardComponentView.setTitle(this.hazardComponentPlotTitle); + } + + /** + * Get the metadata + * @return {Map<String, Array<String>>} The metadata Map + */ + getMetadata() { + let metadata = new Map(); + metadata.set('Edition:', [$(this.editionEl).find(':selected').text()]); + metadata.set('Region:', [$(this.regionEl).find(':selected').text()]); + metadata.set('Latitude (°):', [this.latEl.value]); + metadata.set('Longitude (°):', [this.lonEl.value]); + metadata.set('Intensity Measure Type:', [$(this.imtEl).find(':selected').text()]); + metadata.set('V<sub>S</sub>30:', [$(this.vs30El).find(':selected').text()]); + + return metadata; + } + + supportedRegions() { + let selectedEdition = this.parameters.edition + .values.find((edition, i) => { + return edition.value == this.editionEl.value; + }); + + let supportedRegions = this.parameters.region.values.filter((region, ir) => { + return selectedEdition.supports.region.find((regionVal, irv) => { + return regionVal == region.value; + }) + }); + + return supportedRegions; + } + + callHazardCallback(hazardReturn) { + this.plotHazardCurves(hazardReturn); + } + + /** + * + * @param {D3LineView} view + */ + updatePlotTitle(view) { + let imt = $(':selected', this.imtEl).text(); + let vs30 = $(':selected', this.vs30El).text(); + let siteTitle = this.testSitePicker.getTestSiteTitle(this.region()); + let title = `${siteTitle}, ${imt}, ${vs30}`; + + view.setTitle(title); + } + + onIMTChange(response) { + this.hazardLinePlot.selectLine( + this.imtEl.value, + this.hazardResponseToLineData(response)); + + this.updatePlotTitle(this.hazardView); + if (response.dataType == 'dynamic') { + this.plotComponentCurves(response); + } + } + + plotHazardCurves(responses) { + this.spinner.off(); + this.hazardLinePlot.clearAll(); + let response = responses[0]; + + let dataType = response.dataType; + + let lineData = this.hazardResponseToLineData(response); + this.hazardLinePlot.plot(lineData); + this.hazardLinePlot.selectLine(this.imtEl.value, lineData); + this.updatePlotTitle(this.hazardView); + + $(this.imtEl).off(); + + $(this.imtEl).change(() => { + this.onIMTChange(response); + }); + + this.hazardLinePlot.onPlotSelection( + lineData, + (/** @type {D3LineSeriesData} */ dataSeries) => { + this.onIMTSelection(dataSeries, response); + }); + + switch (dataType) { + case 'dynamic': + this.hazardView.updateViewSize('min'); + this.hazardComponentView.show(); + this.hazardComponentView.updateViewSize('min'); + this.plotComponentCurves(response); + break; + case 'static': + this.hazardView.updateViewSize('max'); + this.hazardComponentView.hide(); + break; + default: + throw new NshmpError(`Response data type [${dataType}] not found`); + } + + let metadata = this.getMetadata(); + this.hazardView.setMetadata(metadata); + this.hazardView.createMetadataTable(); + + this.hazardView.setSaveData(lineData); + this.hazardView.createDataTable(lineData); + } + + /** + * + * @param {D3LineSeriesData} dataSeries + */ + onIMTSelection(dataSeries, hazardResponse) { + this.imtEl.value = dataSeries.lineOptions.id; + this.updatePlotTitle(this.hazardView); + + if (hazardResponse.dataType == 'dynamic') { + this.plotComponentCurves(hazardResponse); + } + } + + hazardResponseToLineData(response) { + let dataBuilder = D3LineData.builder() + .subView(this.hazardView.upperSubView) + .removeSmallValues(this.Y_MIN_CUTOFF); + + if (response.length > 10) { + dataBuilder.colorScheme(d3.schemeCategory20); + } + + let dataType = response.dataType; + + for (let responseData of response) { + let data = responseData.data; + let xValues = []; + let yValues = []; + + switch (dataType) { + case 'dynamic': + let componentData = data.find((d) => { return d.component == 'Total'; }); + xValues = responseData.metadata.xvalues; + yValues = componentData.yvalues; + break; + case 'static': + xValues = responseData.metadata.xvals; + yValues = data[0].yvals; + break; + default: + throw new NshmpError(`Response data type [${dataType}] not found`); + } + + let lineOptions = D3LineOptions.builder() + .id(responseData.metadata.imt.value) + .label(responseData.metadata.imt.display) + .build(); + + dataBuilder.data(xValues, yValues, lineOptions); + } + + return dataBuilder.build(); + } + + plotComponentCurves(response) { + this.hazardComponentLinePlot.clearAll(); + let lineData = this.hazardResponseToComponentLineData(response); + this.hazardComponentLinePlot.plot(lineData); + this.updatePlotTitle(this.hazardComponentView); + + let metadata = this.getMetadata(); + this.hazardComponentView.setMetadata(metadata); + this.hazardComponentView.createMetadataTable(); + + this.hazardComponentView.setSaveData(lineData); + this.hazardComponentView.createDataTable(lineData); + } + + hazardResponseToComponentLineData(hazardResponse) { + let response = hazardResponse.find((response) => { + return response.metadata.imt.value == this.imtEl.value; + }); + + let metadata = response.metadata; + + let components = response.data.filter((data) => { + return data.component != 'Total'; + }); + + let xValues = metadata.xvalues; + + let dataBuilder = D3LineData.builder() + .subView(this.hazardComponentView.upperSubView) + .removeSmallValues(this.Y_MIN_CUTOFF); + + for (let componentData of components) { + let lineOptions = D3LineOptions.builder() + .id(componentData.component) + .label(componentData.component) + .build(); + + dataBuilder.data(xValues, componentData.yvalues, lineOptions); + } + + return dataBuilder.build(); + } + +} diff --git a/webapp/apps/js/Services.js b/webapp/apps/js/Services.js new file mode 100644 index 000000000..2332da797 --- /dev/null +++ b/webapp/apps/js/Services.js @@ -0,0 +1,500 @@ +'use strict'; + +import Header from './lib/Header.js'; + +/** +* @fileoverview Set all service information. +* +* @class Services +* @author bclayton@usgs.gov (Brandon Clayton) +*/ +export default class Services { + + constructor(config) { + /** @type {Header} */ + this.header = new Header(); + this.header.setTitle('Services'); + + /** @type {String} */ + this.urlPrefix = config.server.dynamic.trim() != '' ? + config.server.dynamic + '/nshmp-haz-ws' : + window.location.protocol + '//' + + window.location.host + '/nshmp-haz-ws'; + + /** @type {HTMLElement} */ + this.servicesEl = undefined; + /** @type {HTMLElement} */ + this.serviceMenuEl = undefined; + /** @type {HTMLElement} */ + this.menuListEl = undefined; + + // Create services + this.createOutline(); + this.hazardService(); + this.deaggService(); + this.rateService(); + this.probabilityService(); + this.spectraService(); + this.gmmDistanceService(); + this.hwFwService(); + + //this.formatUrl(); + this.backToTop() + } + + /** + * @method backToTop + * + * Create an anchor for going back to the top of the page + */ + backToTop() { + d3.select(this.menuListEl) + .append('li') + .attr('class', 'back-to-top') + .append('a') + .attr('href', '#top') + .text('Back to top'); + } + + /** + * @method createOutline + * + * Create the outline of the web page + */ + createOutline() { + let containerD3 = d3.select('body') + .append('div') + .attr('class', 'container content') + .attr('id', 'top'); + + let rowD3 = containerD3.append('div') + .attr('class', 'row'); + + let servicesD3 = rowD3.append('div') + .attr('class', 'col-sm-9 services') + .attr('role', 'main'); + + let serviceMenuD3 = rowD3.append('div') + .attr('class', 'col-sm-3 service-menu') + .attr('id', 'service-menu') + .attr('role', 'complimentary') + .append('div') + .attr('class', 'affix hidden-xs'); + + let menuListD3 = serviceMenuD3.append('ul') + .attr('class', 'nav service-menu-list'); + + containerD3.lower(); + d3.select(this.header.headerEl).lower(); + this.servicesEl = servicesD3.node(); + this.serviceMenuEl = serviceMenuD3.node(); + this.menuListEl = menuListD3.node(); + } + + /** + * @method deaggService + * + * Create the service information for deagg + */ + deaggService() { + let svc = {}; + svc.name = 'Deaggregation'; + svc.service = 'deagg'; + svc.id = 'deagg'; + svc.usage = '/nshmp-haz-ws/deagg'; + + svc.description = 'Deaggregate seismic hazard.'; + svc.formats = [ + '/deagg/edition/region/longitude/latitude/imt/vs30/returnperiod', + '/deagg?edition=value®ion=value&longitude=value&' + + 'latitude=value&imt=value&vs30=value&returnperiod=value', + ]; + svc.parameters = [ + 'edition <code>[E2008, E2014]</code>', + 'region <code>[COUS, WUS, CEUS]</code>', + 'longitude <code>(-360..360)</code> °', + 'latitude <code>[-90..90]</code> °', + 'imt (intensity measure type) <code>[PGA, SA0P2, SA1P0]</code>', + 'vs30 <code>[180, 259, 360, 537, 760, 1150, 2000]</code> m/s', + 'returnperiod <code>[1..4000]</code> years', + ]; + svc.examples = [ + '/deagg/E2008/WUS/-118.25/34.05/PGA/760/2475', + '/deagg?edition=E2008®ion=WUS&longitude=-118.25&' + + 'latitude=34.05&imt=PGA&vs30=760&returnperiod=2475', + ]; + svc.info = 'The deaggregation service only supports calculations for' + + ' a single IMT, which <em>must</em> be specified'; + + this.makeService(svc); + } + + /** + * @method gmmDistanceService + * + * Create the service information for gmm distance + */ + gmmDistanceService() { + let svc = {}; + svc.name = 'Ground Motion Vs. Distance'; + svc.service = 'gmm/distance'; + svc.id = 'gmm-distance'; + svc.usage = '/nshmp-haz-ws/gmm/distance'; + + svc.description = 'Compute ground motion Vs. distance.' + svc.formats = [ + '/gmm/distance?gmm=value&Mw=value&imt=value&dip=value&' + + 'width=value&ztop=value&vs30=value&vsinf=boolean&' + + 'rMin=value&rMax=value&z2p5=value&z1p0=value' + ]; + svc.parameters = [ + 'gmm <code> [AS_97, ZHAO_16_UPPER_MANTLE]</code>', + 'Mw <code> [-2, 9.7]</code>', + 'imt (intensity measure type) <code>[PGA, SA10P0]</code>', + 'dip <code> [0, 90]</code> °', + 'width <code> [0, 60]</code> km', + 'ztop <code> [0, 700]</code> km', + 'vs30 <code> [150, 2000]</code> m/s', + 'vsinf <code> boolean</code>', + 'rMin <code> 0.001</code> km', + 'rMax <code> 300</code> km', + 'z1p0 <code> [0, 5]</code> km', + 'z2p5 <code> [0, 10]</code> km', + ]; + svc.examples = [ + '/gmm/distance?gmm=AB_06_PRIME&gmm=CAMPBELL_03&gmm=FRANKEL_96&' + + 'imt=PGA&Mw=6.5&zTop=0.5&dip=90&width=14&vs30=760&' + + 'vsInf=true&z1p0=&z2p5=&rMin=0.001&rMax=300' + ]; + svc.info = 'Not all parameters are used by every ground motion' + + ' model (gmm). At least one "gmm" must be specified. Default' + + ' values will be used for any parameters not specified.'; + + this.makeService(svc); + } + + /** + * @method hazardService + * + * Create the service information for hazard + */ + hazardService() { + let svc = {}; + svc.name = 'Hazard'; + svc.service = 'hazard'; + svc.id = 'hazard'; + svc.usage = '/nshmp-haz-ws/hazard'; + + svc.description = 'Compute probabilisitic seismic hazard ' + + 'curves at a site of interest.'; + svc.formats = [ + '/hazard/edition/region/longitude/latitude/imt/vs30', + '/hazard?edition=value®ion=value&longitude=value&' + + 'latitude=value&imt=value&vs30=value', + ]; + svc.parameters = [ + 'edition <code>[E2008, E2014]</code>', + 'region <code>[COUS, WUS, CEUS]</code>', + 'longitude <code>(-360..360)</code> °', + 'latitude <code>[-90..90]</code> °', + 'imt (intensity measure type) <code>[PGA, SA0P2, SA1P0]</code>', + 'vs30 <code>[180, 259, 360, 537, 760, 1150, 2000]</code> m/s', + ]; + svc.examples = [ + '/hazard/E2008/WUS/-118.25/34.05/PGA/760', + '/hazard?edition=E2008®ion=WUS&longitude=-118.25&' + + 'latitude=34.05&imt=PGA&vs30=760', + ]; + svc.info = 'For the slash delimited format, multiple, comma-delimited' + + ' IMTs may be supplied. Alternatively, "any" may be supplied as' + + ' the IMT id and the service will return curves for all supported' + + ' IMTs. For the name-value pair format, one may use multiple IMT' + + ' name-value pairs, or omit the IMT argument altogether to return' + + ' curves for all supported IMTs' + + this.makeService(svc); + } + + /** + * @method gmmDistanceService + * + * Create the service information for hanging wall effects + */ + hwFwService() { + let svc = {}; + svc.name = 'Hanging Wall Effect'; + svc.service = 'gmm/hw-fw'; + svc.id = 'hw-fw'; + svc.usage = '/nshmp-haz-ws/gmm/hw-fw'; + + svc.description = 'Compute ground motion Vs. distance.' + svc.formats = [ + '/gmm/hw-fw?gmm=value&Mw=value&imt=value&dip=value&' + + 'width=value&ztop=value&vs30=value&vsinf=boolean&' + + 'rMin=value&rMax=value&z2p5=value&z1p0=value' + ]; + svc.parameters = [ + 'gmm <code> [AS_97, ZHAO_16_UPPER_MANTLE]</code>', + 'Mw <code> [-2, 9.7]</code>', + 'imt (intensity measure type) <code>[PGA, SA10P0]</code>', + 'dip <code> [0, 90]</code> °', + 'width <code> [0, 60]</code> km', + 'ztop <code> [0, 700]</code> km', + 'vs30 <code> [150, 2000]</code> m/s', + 'vsinf <code> boolean </code>', + 'rMin <code> -20 </code> km', + 'rMax <code> 70 </code> km', + 'z1p0 <code> [0, 5]</code> km', + 'z2p5 <code> [0, 10]</code> km', + ]; + svc.examples = [ + '/gmm/hw-fw?gmm=AB_06_PRIME&gmm=CAMPBELL_03&gmm=FRANKEL_96&' + + 'imt=PGA&Mw=6.5&zTop=0.5&dip=90&width=14&vs30=760&' + + 'vsInf=true&z1p0=&z2p5=&rMin=-20&rMax=70' + ]; + svc.info = 'Not all parameters are used by every ground motion' + + ' model (gmm). At least one "gmm" must be specified. Default' + + ' values will be used for any parameters not specified.'; + + this.makeService(svc); + } + + /** + * @method makeService + * + * Create a panel with each service's information + * @param {{ + * name: {String} - Name of service, + * service: {String} - Service url, + * id: {String} - an id, + * usage: {String} - url, + * description: {String} - Description of service, + * formats: {Array<String>} - Service url format, + * parameters: {Array<String>} - Parameters used in the url, + * examples: {Array<String>} - Example urls, + * info: {String} - Any additional info abput service, + * }} svc - Service object + */ + makeService(svc) { + this.serviceMenu(svc); + + let panelD3 = d3.select(this.servicesEl) + .append('div') + .attr('class', 'service') + .attr('id', svc.id) + .append('div') + .attr('class', 'panel panel-default'); + + // Service name + panelD3.append('div') + .attr('class', 'panel-heading') + .append('h3') + .text(svc.service); + + let serviceD3 = panelD3.append('div') + .attr('class', 'panel-body'); + + // Service description + serviceD3.append('div') + .attr('class', 'service-div') + .text(svc.description); + + // Format list + let formatD3 = serviceD3.append('div') + .attr('class', 'service-div'); + formatD3.append('h4') + .text('Formats'); + formatD3.append('ul') + .selectAll('li') + .data(svc.formats) + .enter() + .append('li') + .attr('class', 'format-url list-group-item') + .text((d, i) => { return this.urlPrefix + d }); + + // Parameter list + let parD3 = serviceD3.append('div') + .attr('class', 'service-div'); + parD3.append('h4') + .text('Parameters'); + parD3.append('ul') + .selectAll('li') + .data(svc.parameters) + .enter() + .append('li') + .attr('class', 'list-group-item') + .html((d, i) => {return d}); + + // Additional info + serviceD3.append('div') + .attr('class', 'service-div') + .html(svc.info); + + // Usage URL + serviceD3.append('div') + .attr('class', 'service-div') + .html('See' + '<a href="' + svc.usage + '"> usage </a> ' + + 'for parameter dependencies'); + + // Examples + let exD3 = panelD3.append('div') + .attr('class', 'panel-footer'); + exD3.append('h4') + .attr('class', 'examples') + .text('Examples'); + exD3.selectAll('div') + .data(svc.examples) + .enter() + .append('div') + .attr('class', 'service-link') + .append('a') + .attr('href', (d,i) => {return this.urlPrefix + d}) + .text((d, i) => {return this.urlPrefix + d}); + } + + /** + * @method probabilityService + * + * Create the service information for probability + */ + probabilityService() { + let svc = {}; + svc.name = 'Probability'; + svc.service = 'probability'; + svc.id = 'probability'; + svc.usage = '/nshmp-haz-ws/probability'; + + svc.description = 'Compute the Poisson probability of earthquake' + + ' occurrence at a site of interest.'; + svc.formats = [ + '/probability/edition/region/longitude/latitude/distance', + '/probability?edition=value®ion=value&longitude=value&' + + 'latitude=value&distance=value×pan=value', + ]; + svc.parameters = [ + 'edition <code>[E2008, E2014]</code>', + 'region <code>[COUS, WUS, CEUS]</code>', + 'longitude <code>(-360..360)</code> °', + 'latitude <code>[-90..90]</code> °', + 'distance <code>[0.01..1000]</code> km', + 'timespan <code>[1..10000]</code> years', + ]; + svc.examples = [ + '/probability/E2008/WUS/-118.25/34.05/20/50', + '/probability?edition=E2008®ion=WUS&longitude=-118.25' + + '&latitude=34.05&distance=20×pan=50', + ]; + svc.info = ''; + + this.makeService(svc); + } + + /** + * @method rateService + * + * Create the service information for rate + */ + rateService() { + let svc = {}; + svc.name = 'Rate'; + svc.service = 'rate'; + svc.id = 'rate'; + svc.usage = '/nshmp-haz-ws/rate'; + + svc.description = 'Compute the annual rate of earthquakes at a site' + + ' of interest.'; + svc.formats = [ + '/rate/edition/region/longitude/latitude/distance', + '/rate?edition=value®ion=value&longitude=value&' + + 'latitude=value&distance=value', + ]; + svc.parameters = [ + 'edition <code>[E2008, E2014]</code>', + 'region <code>[COUS, WUS, CEUS]</code>', + 'longitude <code>(-360..360)</code> °', + 'latitude <code>[-90..90]</code> °', + 'distance <code>[0.01..1000]</code> km', + ]; + svc.examples = [ + '/rate/E2008/WUS/-118.25/34.05/20', + '/rate?edition=E2008®ion=WUS&longitude=-118.25&' + + 'latitude=34.05&distance=20', + ]; + svc.info = ''; + + this.makeService(svc); + } + + /** + * @method serviceMenu + * + * Add onto the service menu + * @param {{ + * name: {String} - Name of service, + * service: {String} - Service url, + * id: {String} - an id, + * usage: {String} - url, + * description: {String} - Description of service, + * formats: {Array<String>} - Service url format, + * parameters: {Array<String>} - Parameters used in the url, + * examples: {Array<String>} - Example urls, + * info: {String} - Any additional info abput service, + * }} svc - Service object + */ + serviceMenu(svc) { + d3.select(this.menuListEl).append('li') + .append('a') + .attr('href', '#' + svc.id) + .text(svc.name); + } + + /** + * @method spectraService + * + * Create the service information for response spectra + */ + spectraService() { + let svc = {}; + svc.name = 'Response Spectra'; + svc.service = 'gmm/spectra'; + svc.id = 'spectra'; + svc.usage = '/nshmp-haz-ws/gmm/spectra'; + + svc.description = 'Compute determinisitic reponse spectra.' + svc.formats = [ + '/gmm/spectra?gmm=value&Mw=value&rjb=value&rrup=value&' + + 'rx=value&dip=value&width=value&ztop=value&' + + 'zhyp=value&rake=value&vs30=value&vsinf=boolean&' + + 'z2p5=value&z1p0=value' + ]; + svc.parameters = [ + 'gmm <code> [AS_97, ZHAO_16_UPPER_MANTLE]</code>', + 'Mw <code> [-2, 9.7]</code>', + 'rjb <code> [0, 1000]</code> km', + 'rrup <code> [0, 1000]</code> km', + 'rx <code> [0, 1000]</code> km', + 'dip <code> [0, 90]</code> °', + 'width <code> [0, 60]</code> km', + 'ztop <code> [0, 700]</code> km', + 'zhyp <code> [0, 700]</code> km', + 'rake <code> [-180, 180]</code> °', + 'vs30 <code> [150, 2000]</code> m/s', + 'vsinf <code> boolean </code>', + 'z1p0 <code> [0, 5]</code> km', + 'z2p5 <code> [0, 10]</code> km', + ]; + svc.examples = [ + '/gmm/spectra?gmm=AB_06_PRIME&gmm=CAMPBELL_03&gmm=FRANKEL_96&' + + 'mw=8.7&rjb=10.0&rrup=23.0&' + + 'rx=32.0&dip=30.0&width=25.0&ztop=10.0&' + + 'zhyp=20.0&rake=90.0&vs30=760.0&vsinf=true&' + + 'z2p5=NaN&z1p0=NaN' + ]; + svc.info = 'Not all parameters are used by every ground motion' + + ' model (gmm). At least one "gmm" must be specified. Default' + + ' values will be used for any parameters not specified.'; + + this.makeService(svc); + } + +} diff --git a/webapp/apps/js/Spectra.js b/webapp/apps/js/Spectra.js new file mode 100644 index 000000000..4be24c98c --- /dev/null +++ b/webapp/apps/js/Spectra.js @@ -0,0 +1,1156 @@ + +import { D3LineData } from './d3/data/D3LineData.js'; +import { D3LineOptions } from './d3/options/D3LineOptions.js'; +import { D3LinePlot } from './d3/D3LinePlot.js'; +import { D3LineSubViewOptions } from './d3/options/D3LineSubViewOptions.js'; +import { D3LineView } from './d3/view/D3LineView.js'; +import { D3LineViewOptions } from './d3/options/D3LineViewOptions.js'; +import { D3SaveFigureOptions } from './d3/options/D3SaveFigureOptions.js'; + +import { GmmBeta } from './lib/GmmBeta.js'; +import Tools from './lib/Tools.js'; +import NshmpError from './error/NshmpError.js'; +import { Preconditions } from './error/Preconditions.js'; + +/** + * @fileoverview Class for spectra-plot.html, response spectra web app. + * This class plots the results of nshmp-haz-ws/gmm/spectra web service. + * This class will first call out to nshmp-haz-ws/gmm/spectra web service + * to obtain the usage and create the control panel with the following: + * - Ground motions models + * - Magnitude + * - Rake + * - zHyp + * - Fault mech (strike-slip, normal, reverse) + * - zTop + * - Dip + * - Width + * - rX + * - rRup + * - rJB + * - Vs30 + * - Vs30 measured or inferred + * - Z1.0 + * - Z2.5 + * Once the control panel is set, it can be used to select desired + * parameters and plot ground motion vs. period. + * + * @class Spectra + * @extends GmmBeta + * @author bclayton@usgs.gov (Brandon Clayton) + */ +export class Spectra extends GmmBeta { + + constructor(config) { + let webApp = 'Spectra'; + let wsUrl = '/nshmp-haz-ws/gmm/spectra' + + super(webApp, wsUrl, config); + + this.header.setTitle('Response Spectra'); + + /** The main content element for plots - @type {HTMLElement} */ + this.contentEl = document.querySelector('#content'); + + /* Magnitude buttons */ + this.MwBtns = [ + { text: '4.0', value: 4.0 }, + { text: '4.5', value: 4.5 }, + { text: '5.0', value: 5.0 }, + { text: '5.5', value: 5.5 }, + { text: '6.0', value: 6.0 }, + { text: '6.5', value: 6.5 }, + { text: '7.0', value: 7.0 }, + { text: '7.5', value: 7.5 }, + { text: '8.0', value: 8.0 }, + { text: '8.5', value: 8.5 }, + ]; + + /* Rake Buttons */ + this.rakeBtns = [ + { text: 'Strike-Slip', id: 'fault-style-strike', value: 0.0 }, + { text: 'Normal', id: 'fault-style-normal', value: -90 }, + { text: 'Reverse', id: 'fault-style-reverse', value: 90 }, + ]; + + /* Hanging wall - foot wall */ + this.hwFwBtns = [ + { text: 'Hanging Wall', id: 'hw-fw-hw', value: 'hw', isActive: true }, + { text: 'Foot Wall', id: 'hw-fw-fw', value: 'fw'}, + ]; + + /* Vs30 buttons */ + this.vs30Btns = [ + { text: '150', value: 150.0 }, + { text: '185', value: 185.0 }, + { text: '260', value: 260.0 }, + { text: '365', value: 365.0 }, + { text: '530', value: 530.0 }, + { text: '760', value: 760.0 }, + { text: '1080', value: 1080.0 }, + { text: '2000', value: 2000.0 }, + { text: '3000', value: 3000.0 }, + ]; + + /* What parameters are multi-selectable */ + this.multiSelectValues = [ + { text: 'Ground Motion Models', value: 'gmms' }, + { text: 'Mw', value: 'Mw' }, + { text: 'Vs30', value: 'vs30' }, + ]; + + /** X-Axis domain for spectra plots - @type {Array<Number>} */ + this.spectraXDomain = [0.01, 10.0]; + + /* Get the usage and create control panel */ + this.getUsage(); + + this.spectraView = this.setupSpectraView(); + this.spectraLinePlot = new D3LinePlot(this.spectraView); + + this.pga = 0.01; + } + + /** + * Create the control panel with a: + * - Multi-selectable select menu + * - GMM select menu + * - Event parameters: + * - Mw + * - Rake + * - zHyp + * - Source parameters: + * - zTop + * - Dip + * - Width + * - Path parameters: + * - rX + * - rRup + * - rJB + * - Site parameters: + * - Vs30 + * - Z1p0 + * - Z2p5 + */ + createControlPanel() { + this.spinner.off(); + + /** Multi-selectable select menu - @type {HTMLElement} */ + this.multiSelectEl = this._createMultiSelect(); + + /* Create the GMM sorter and select menu */ + let gmmEls = this.controlPanel.createGmmSelect(this.parameters); + /** GMM select menu - @type {HTMLElement} */ + this.gmmsEl = gmmEls.gmmsEl; + /** GMM group sorter element - @type {HTMLElement} */ + this.gmmGroupEl = gmmEls.gmmGroupEl; + /** GMM alpha sorter element - @type {HTMLElement} */ + this.gmmAlphaEl = gmmEls.gmmAlphaEl; + /** GMM alpha select option elements - @type {Array<HTMLElement>} */ + this.gmmAlphaOptions = gmmEls.gmmAlphaOptions; + /** GMM group select option elements - @type {Array<HTMLElement>} */ + this.gmmGroupOptions = gmmEls.gmmGroupOptions; + + this._createEventParameters(this.parameters); + this._createSourceParameters(this.parameters); + this._createPathParameters(this.parameters); + this._createSiteParameters(this.parameters); + + /* Add event listeners */ + this._listeners(); + + /* Check URL query */ + this.checkQuery(); + } + + /** + * Get metadata about all chosen parameters. + * + * @return {Map<String, Array<String>} + * The metadata with all chosen parameters. + */ + getMetadata() { + let gmms = this.getCurrentGmms(); + + let metadata = new Map(); + metadata.set('Ground Motion Model:', gmms); + metadata.set('M<sub>W</sub>:', this.getValues(this.MwEl)); + metadata.set('Rake (°):', this.getValues(this.rakeEl)); + metadata.set('Z<sub>Top</sub> (km):', this.getValues(this.zTopEl)); + metadata.set('Dip (°):', this.getValues(this.dipEl)); + metadata.set('Width (km):', this.getValues(this.widthEl)); + metadata.set('R<sub>X</sub> (km):', this.getValues(this.rXEl)); + metadata.set('R<sub>Rup</sub> (km):', this.getValues(this.rRupEl)); + metadata.set('R<sub>JB</sub> (km):', this.getValues(this.rJBEl)); + metadata.set('V<sub>s</sub>30 (m/s):', this.getValues(this.vs30El)); + metadata.set('Z<sub>1.0</sub> (km):', this.getValues(this.z1p0El)); + metadata.set('Z<sub>2.5</sub> (km):', this.getValues(this.z2p5El)); + + return metadata; + } + + /** + * Find the value(s) of a particular element. + * + * @param {HTMLElement} el The parameter to get chosen values from. + * @returns {Array<Number> | Number} The parameter values. + */ + getValues(el) { + let multiSelectParam = this.multiSelectEl.value; + let btnGroupEl = d3.select(this.multiSelectEl).data()[0]; + + if (multiSelectParam == el.id) { + let tmpValues = $(':checked', btnGroupEl).map((i, d) => { + return parseFloat(d.value); + }).get(); + + let nValues = tmpValues.length; + let maxValues = 3; + let nLoops = Math.ceil( nValues / maxValues ); + let values = []; + + let iStart = 0; + let iEnd = 0; + for (let i = 0; i < nLoops; i++) { + iStart = iEnd; + iEnd = iStart + maxValues; + + values.push(tmpValues.slice(iStart, iEnd).join(', ')); + if (i < nLoops - 1) values[i] += ','; + } + + return values; + } else { + return [parseFloat(el.value)]; + } + } + + /** + * Plot spectra results. + * + * @param {Array} responses + */ + plotSpectra(responses) { + this.spectraLinePlot.clearAll(); + let metadata = this.getMetadata(); + let means = this.plotSpectraMeans(responses); + let sigmas = this.plotSpectraSigma(responses); + this.spectraLinePlot.syncSubViews(); + + this.spectraView.setMetadata(metadata); + this.spectraView.createMetadataTable(); + + let meanData = means.pgaData.concat(means.lineData); + let sigmaData = sigmas.pgaData.concat(sigmas.lineData); + this.spectraView.setSaveData(meanData, sigmaData); + this.spectraView.createDataTable(meanData, sigmaData); + } + + /** + * Plot the spectra means. + * + * @param {Array} responses + */ + plotSpectraMeans(responses) { + let data = this._responsesToLineData( + responses, + this.spectraView.upperSubView, + 'means'); + + let lineData = data.lineData; + let pgaData = data.pgaData; + + this.spectraLinePlot.plot(lineData); + this.spectraLinePlot.plot(pgaData); + + return data; + } + + /** + * Plot the spectra sigmas + * @param {Array} responses + */ + plotSpectraSigma(responses) { + let data = this._responsesToLineData( + responses, + this.spectraView.lowerSubView, + 'sigmas'); + + let lineData = data.lineData; + let pgaData = data.pgaData; + + this.spectraLinePlot.plot(lineData); + this.spectraLinePlot.plot(pgaData); + + return data; + } + + /** + * Setup the plot view + */ + setupSpectraView() { + /* Save figure options */ + let saveOptions = D3SaveFigureOptions.builder() + .metadataColumns(4) + .build(); + + /* Upper sub view options: means */ + let upperSubViewOptions = D3LineSubViewOptions.upperBuilder() + .filename('spectra-means') + .label('Response Spectra Means') + .lineLabel('Ground Motion Model') + .saveFigureOptions(saveOptions) + .xLabel('Period (s)') + .yAxisScale('linear') + .yLabel('Median Ground Motion (g)') + .build(); + + /* Lower sub view options: sigmas */ + let lowerSubViewOptions = D3LineSubViewOptions.lowerBuilder() + .filename('spectra-sigmas') + .label('Response Spectra Sigmas') + .lineLabel('Ground Motion Model') + .saveFigureOptions(saveOptions) + .showLegend(false) + .xLabel('Period (s)') + .yAxisScale('linear') + .yLabel('Standard Deviation') + .build(); + + let viewOptions = D3LineViewOptions.builder() + .syncXAxisScale(true, 'log') + .syncYAxisScale(false) + .viewSize('max') + .build(); + + let view = D3LineView.builder() + .addLowerSubView(true) + .containerEl(this.contentEl) + .viewOptions(viewOptions) + .lowerSubViewOptions(lowerSubViewOptions) + .upperSubViewOptions(upperSubViewOptions) + .build(); + + view.setTitle('Response Spectra'); + + return view; + } + + /** + * Call the ground motion web service and plot the results + */ + updatePlot() { + let urls = this.serializeGmmUrl(); + let jsonCall = Tools.getJSONs(urls); + + this.spinner.on(jsonCall.reject, 'Calculating'); + + Promise.all(jsonCall.promises).then((responses) => { + this.spinner.off(); + // NshmpError.checkResponses(responses, this.plot); + + this.footer.setMetadata(responses[0].server); + + this.plotSpectra(responses); + + $(this.footer.rawBtnEl).off(); + $(this.footer.rawBtnEl).click((event) =>{ + for (let url of urls) { + window.open(url); + } + }); + }).catch((errorMessage) => { + this.spinner.off(); + this.spectraLinePlot.clearAll(); + NshmpError.throwError(errorMessage); + }); + + } + + /** + * Create a form group for dip with a: + * - Input form + * - Slider + * + * @param {Object} params The spectra JSON usage. + */ + _createDipFormGroup(params) { + let inputOptions = { + id: 'dip', + label: 'Dip', + labelColSize: 'col-xs-2', + max: params.dip.max, + min: params.dip.min, + name: 'dip', + value: params.dip.value, + }; + + let sliderOptions = { + id: 'dip-slider', + } + + let dipEls = this.controlPanel.formGroupBuilder() + .addInput(inputOptions) + .addInputAddon('°') + .addInputTooltip() + .addInputSlider(sliderOptions) + .syncValues() + .build(); + + this.dipEl = dipEls.inputEl; + this.dipSliderEl = dipEls.sliderEl; + } + + /** + * Create all event parameters: + * - Magnitude (input form, slider, buttons) + * - Rake (input form, slider, buttons) + * - zHyp (input, checkbox) + * + * @param {Object} params The spectra JSON usage. + */ + _createEventParameters(params) { + this.controlPanel.createLabel({ + appendTo: this.controlPanel.formHorizontalEl, + label: 'Event Parameters:'}); + + /* Magnitude form group*/ + this._createMagnitudeFormGroup(params); + /* Rake form group */ + this._createRakeFormGroup(params); + /* zHyp form group */ + this._createZHypFormGroup(params); + } + + /** + * Create a form group for magnitude with a: + * - Input form + * - Slider + * - Buttons + * + * @param {Object} params The spectra JSON usage. + */ + _createMagnitudeFormGroup(params) { + let inputOptions = { + id: 'Mw', + label: 'M<sub>w</sub>', + labelColSize: 'col-xs-2', + max: params.Mw.max, + min: params.Mw.min, + name: 'Mw', + step: 0.1, + value: params.Mw.value, + }; + + let btnOptions = { + addLabel: false, + id: 'Mw-btn-group', + name: 'Mw', + }; + + let sliderOptions = { + id: 'Mw-slider', + }; + + this.MwEls = this.controlPanel.formGroupBuilder() + .addInput(inputOptions) + .addInputSlider(sliderOptions) + .addBtnGroup(this.MwBtns, btnOptions) + .syncValues() + .addInputTooltip() + .build(); + + this.MwEl = this.MwEls.inputEl; + this.MwBtnGroupEl = this.MwEls.btnGroupEl; + this.MwSliderEl = this.MwEls.sliderEl; + } + + /** + * Create the select menu with the multi-selectable options. + */ + _createMultiSelect() { + let selectOptions = { + id: 'multiple-select', + label: 'Multi Value Parameter:', + labelControl: false, + value: 'gmms', + }; + + let optionArray = this.controlPanel + .toSelectOptionArray(this.multiSelectValues); + + let multiSelectEls = this.controlPanel.formGroupBuilder() + .addSelect(optionArray, selectOptions) + .build(); + + return multiSelectEls.selectEl; + } + + /** + * Create the path parameters: + * - rX (input form, slider) + * - rRup (input form, checkbox) + * - rJB (input form, buttons) + * + * @param {Object} params The spectra JSON usage. + */ + _createPathParameters(params) { + this.controlPanel.createLabel({ + appendTo: this.controlPanel.formHorizontalEl, + label: 'Path Parameters:'}); + + /* rX form group */ + this._createRXFormGroup(params); + // TODO Implement rY parameters + // /* rY form group */ + // this._createRYFormGroup(params); + /* rRup form group */ + this._createRRupFormGroup(params); + /* rJB form group */ + this._createRJBFormGroup(params); + } + + /** + * Create a form group for rake with a: + * - Input form + * - Buttons + * + * @param {Object} params The spectra JSON usage. + */ + _createRakeFormGroup(params) { + let inputOptions = { + id: 'rake', + label: 'Rake', + labelColSize: 'col-xs-2', + max: params.rake.max, + min: params.rake.min, + name: 'rake', + value: params.rake.value, + }; + + let btnOptions = { + addLabel: false, + id: 'fault-style', + name: 'rake', + }; + + let sliderOptions = { + id: 'rake-slider', + }; + + this.rakeEls = this.controlPanel.formGroupBuilder() + .addInput(inputOptions) + .addInputAddon('°') + .addInputSlider(sliderOptions) + .addBtnGroup(this.rakeBtns, btnOptions) + .syncValues() + .addInputTooltip() + .build(); + + this.rakeEl = this.rakeEls.inputEl; + this.faultStyleEl = this.rakeEls.btnGroupEl; + this.rakeSliderEl = this.rakeEls.sliderEl; + this.faultStyleStrikeEl = this.rakeEls.btnGroupEl + .querySelector('#fault-style-strike'); + this.faultStyleNormalEl = this.rakeEls.btnGroupEl + .querySelector('#fault-style-normal'); + this.faultStyleReverseEl = this.rakeEls.btnGroupEl + .querySelector('#fault-style-reverse'); + } + + /** + * Create a form group for rJB with a: + * - Input form + * - Buttons (hanging wall / footwall) + * + * @param {Object} params The spectra JSON usage. + */ + _createRJBFormGroup(params) { + let inputOptions = { + id: 'rJB', + label: 'R<sub>JB</sub>', + labelColSize: 'col-xs-2', + max: params.rJB.max, + min: params.rJB.min, + name: 'rJB', + readOnly: true, + value: params.rJB.value, + } + + let btnOptions = { + btnGroupColSize: 'col-xs-6 col-xs-offset-1', + id: 'hw-fw', + paddingTop: 'initial', + }; + + let rJBEls = this.controlPanel.formGroupBuilder() + .addInput(inputOptions) + .addInputAddon(params.rJB.units) + .addInputTooltip() + .addBtnGroup(this.hwFwBtns, btnOptions) + .build(); + + this.rJBEl = rJBEls.inputEl; + this.hwFwEl = rJBEls.btnGroupEl; + this.hwFwHwEl = this.hwFwEl.querySelector('#hw-fw-hw'); + this.hwFwFwEl = this.hwFwEl.querySelector('#hw-fw-fw'); + } + + /** + * Create a form group for rRup with a: + * - Input form + * - Checkbox (Derive rJB and rRup) + * + * @param {Object} params + */ + _createRRupFormGroup(params) { + let inputOptions = { + id: 'rRup', + label: 'R<sub>Rup</sub>', + labelColSize: 'col-xs-2', + max: params.rRup.max, + min: params.rRup.min, + name: 'rRup', + readOnly: true, + value: params.rRup.value, + }; + + let checkboxOptions = { + checked: true, + id: 'r-check', + inputColSize: 'col-xs-6 col-xs-offset-1', + text: 'Derive R<sub>JB</sub> and R<sub>Rup</sub>', + }; + + let rRupEls = this.controlPanel.formGroupBuilder() + .addInput(inputOptions) + .addInputAddon(params.rRup.units) + .addInputTooltip() + .addCheckbox(checkboxOptions) + .build(); + + this.rRupEl = rRupEls.inputEl; + this.rCheckEl = rRupEls.checkboxEl; + } + + /** + * Create a rX form group with a: + * - Input form + * - Slider + * + * @param {Object} params The spectra JSON usage. + */ + _createRXFormGroup(params) { + let inputOptions = { + id: 'rX', + label: 'R<sub>X</sub>', + labelColSize: 'col-xs-2', + max: params.rX.max, + min: params.rX.min, + name: 'rX', + value: params.rX.value, + }; + + let sliderOptions = { + id: 'rX-slider', + }; + + let rXEls = this.controlPanel.formGroupBuilder() + .addInput(inputOptions) + .addInputAddon(params.rX.units) + .addInputTooltip() + .addInputSlider(sliderOptions) + .syncValues() + .build(); + + this.rXEl = rXEls.inputEl; + this.rXSliderEl = rXEls.sliderEl; + } + + /** + * Create a form group for rY with a: + * - Input form + * + * NOTE: Currently not implimented. + * + * @param {Object} params The spectra JSON usage. + */ + _createRYFormGroup(params) { + let inputOptions = { + disabled: true, + label: 'R<sub>Y</sub>', + labelColSize: 'col-xs-2', + id: 'rY', + name: 'rY', + }; + + let rYEls = this.controlPanel.formGroupBuilder() + .addInput(inputOptions) + .addInputAddon('km') + .build(); + + this.rYEl = rYEls.inputEl; + } + + /** + * Create site parameters: + * - Vs30 (input form, slider, buttons) + * - Z1p0 (input form) + * - Z2p5 (input form) + * + * @param {Object} params The spectra JSON usage. + */ + _createSiteParameters(params) { + this.controlPanel.createLabel({ + appendTo: this.controlPanel.formHorizontalEl, + label: 'Site & Basin:'}); + + /* Vs30 form group */ + this._createVs30FormGroup(params); + /* Z1p0 */ + this._createZ1p0FormGroup(params); + /* Z2p5 */ + this._createZ2p5FormGroup(params); + } + + _createSourceParameters(params) { + this.controlPanel.createLabel({ + appendTo: this.controlPanel.formHorizontalEl, + label: 'Source Parameters:'}); + + /* zTop form group */ + this._createZTopFormGroup(params); + /* Dip form group */ + this._createDipFormGroup(params); + /* Width form group */ + this._createWidthFormGroup(params); + } + + /** + * Create a form group for vs30 with a: + * - Input form + * - Slider + * - Button group + * + * @param {Object} params The spectra JSON usage. + */ + _createVs30FormGroup(params) { + let inputOptions = { + id: 'vs30', + label: 'V<sub>S</sub>30', + labelColSize: 'col-xs-2', + max: params.vs30.max, + min: params.vs30.min, + name: 'vs30', + value: params.vs30.value, + }; + + let sliderOptions = { + id: 'vs30-slider', + }; + + let btnOptions = { + id: 'vs30-btn-group', + name: 'vs30', + }; + + this.vs30Els = this.controlPanel.formGroupBuilder() + .addInput(inputOptions) + .addInputAddon(params.vs30.units) + .addInputTooltip() + .addInputSlider(sliderOptions) + .addBtnGroup(this.vs30Btns, btnOptions) + .syncValues() + .build(); + + this.vs30El = this.vs30Els.inputEl; + this.vs30SliderEl = this.vs30Els.sliderEl; + this.vs30BtnGroupEl = this.vs30Els.btnGroupEl; + } + + /** + * Create a form group for width with a: + * - Input form + * - Slider + * + * @param {Object} params The spectra JSON usage. + */ + _createWidthFormGroup(params) { + let inputOptions = { + id: 'width', + label: 'Width', + labelColSize: 'col-xs-2', + max: params.width.max, + min: params.width.min, + name: 'width', + value: params.width.value, + }; + + let sliderOptions = { + id: 'width-slider', + }; + + let widthEls = this.controlPanel.formGroupBuilder() + .addInput(inputOptions) + .addInputAddon(params.width.units) + .addInputTooltip() + .addInputSlider(sliderOptions) + .syncValues() + .build(); + + this.widthEl = widthEls.inputEl; + this.widthSliderEl = widthEls.sliderEl; + } + + /** + * Create a form group for z1p0 with a: + * - Input form + * + * @param {Object} params The spectra JSON usage. + */ + _createZ1p0FormGroup(params) { + let inputOptions = { + id: 'z1p0', + label: 'Z<sub>1.0</sub>', + labelColSize: 'col-xs-2', + max: params.z1p0.max, + min: params.z1p0.min, + name: 'z1p0', + value: params.z1p0.value, + }; + + let z1p0Els = this.controlPanel.formGroupBuilder() + .addInput(inputOptions) + .addInputAddon(params.z1p0.units) + .addInputTooltip() + .build(); + + this.z1p0El = z1p0Els.inputEl; + } + + /** + * Create a form group for z2p5 with a: + * - Input form + * + * @param {Object} params The spectra JSON usage. + */ + _createZ2p5FormGroup(params) { + let inputOptions = { + id: 'z2p5', + label: 'Z<sub>2.5</sub>', + labelColSize: 'col-xs-2', + max: params.z2p5.max, + min: params.z2p5.min, + name: 'z2p5', + value: params.z2p5.value, + }; + + let z2p5Els = this.controlPanel.formGroupBuilder() + .addInput(inputOptions) + .addInputAddon(params.z2p5.units) + .addInputTooltip() + .build(); + + this.z2p5El = z2p5Els.inputEl; + } + + /** + * Create a form group for zHyp with a: + * - Input form + * - Checkbox (Centered down dip) + * + * @param {Object} params The spectra JSON usage. + */ + _createZHypFormGroup(params) { + let inputOptions = { + id: 'zHyp', + label: 'z<sub>Hyp</sub>', + labelColSize: 'col-xs-2', + max: params.zHyp.max, + min: params.zHyp.min, + name: 'zHyp', + readOnly: true, + step: 0.5, + value: params.zHyp.value, + }; + + let checkboxOptions = { + checked: true, + id: 'z-check', + inputColSize: 'col-xs-6 col-xs-offset-1', + text: 'Centered down-dip', + }; + + let zHypEls = this.controlPanel.formGroupBuilder() + .addInput(inputOptions) + .addInputAddon(params.zHyp.units) + .addCheckbox(checkboxOptions) + .build(); + + this.zHypEl = zHypEls.inputEl; + this.zCheckEl = zHypEls.checkboxEl; + } + + /** + * Create a form group for zTop with a: + * - Input form + * - Slider + * @param {Object} params The spectra JSON usage. + */ + _createZTopFormGroup(params) { + let inputOptions = { + id: 'zTop', + label: 'z<sub>Top</sub>', + labelColSize: 'col-xs-2', + max: params.zTop.max, + min: params.zTop.min, + name: 'zTop', + value: params.zTop.value, + }; + + let sliderOptions = { + id: 'zTop-slider', + } + + let zTopEls = this.controlPanel.formGroupBuilder() + .addInput(inputOptions) + .addInputAddon(params.zTop.units) + .addInputTooltip() + .addInputSlider(sliderOptions) + .syncValues() + .build(); + + this.zTopEl = zTopEls.inputEl; + this.zTopSliderEl = zTopEls.sliderEl; + } + + /** + * Event listeners + */ + _listeners() { + d3.select(this.multiSelectEl).datum(this.gmmsEl); + $(this.multiSelectEl).on('input', (event) => { + this._onMultiSelectClick(event); + }); + $(this.multiSelectEl).trigger('input'); + + $('input', this.hwFwEl).on('change', (event) => { + this.updateDistance(); + Tools.resetRadioButton(event.target); + }); + + $(this.rCheckEl).on('change', (event) => { + let rCompute = event.target.checked; + $(this.rJBEl).prop('readonly', rCompute); + $(this.rRupEl).prop('readonly', rCompute); + $(this.hwFwHwEl.parentNode).toggleClass('disabled', !rCompute); + $(this.hwFwFwEl.parentNode).toggleClass('disabled', !rCompute); + this.updateDistance(); + }); + this.updateDistance(); + + + $(this.rXEl).on('input', () => { this.updateDistance(); }); + + $(this.dipEl).on('input', () => { + if (isNaN(this.dip_val())) return; + this.updateDistance(); + this.updateHypoDepth(); + }); + + $(this.widthEl).on('input', () => { + if (isNaN(this.width_val())) return; + this.updateDistance(); + this.updateHypoDepth(); + }); + + $(this.zCheckEl).change((event) => { + $(this.zHypEl).prop('readonly', event.target.checked); + this.updateHypoDepth(); + }); + + $(this.zTopEl).on('input', () => { + if (isNaN(this.zTop_val())) return; + this.updateDistance(); + this.updateHypoDepth(); + }); + + // On any input + $(this.controlPanel.inputsEl) + .on('input change', (event) => { this.inputsOnInput(event); }); + } + + /** + * Update the parameters based on the chosen parameter allowed + * to be multi-selectable. + * + * @param {Event} event The event. + */ + _onMultiSelectClick(event) { + this._resetMultiSelectMenus(); + let param = event.target.value; + + switch(param) { + case 'gmms': + d3.select(this.gmmsEl) + .property('multiple', true) + .attr('size', 16); + d3.select(this.multiSelectEl).datum(this.gmmsEl); + break; + case 'Mw': + this.controlPanel.toMultiSelectable(this.MwEls); + d3.select(this.multiSelectEl).datum(this.MwBtnGroupEl); + break; + case 'vs30': + this.controlPanel.toMultiSelectable(this.vs30Els); + d3.select(this.multiSelectEl).datum(this.vs30BtnGroupEl); + }; + + $(this.controlPanel.inputsEl).trigger('change'); + } + + /** + * Reset the multi-selectable parameters to single selectable. + */ + _resetMultiSelectMenus() { + /* Reset GMMs */ + d3.select(this.gmmsEl) + .property('multiple', false) + .attr('size', 16); + /* Reset magnitude */ + this.controlPanel.toSingleSelectable(this.MwEls); + /* Reset vs30 */ + this.controlPanel.toSingleSelectable(this.vs30Els); + } + + /** + * Convert an array of JSON responses into arrays + * of data, labels, and ids for D3LinePlot. + * + * @param {Array<Object>} responses An array of JSON returns + * from gmm/spectra web service + * @param {String} whichDataSet Which data set to get from the + * JSON return: 'means' || 'sigmas' + * @returns {Object} An object with data series information to + * create plots. + */ + _responsesToData(responses, whichDataSet) { + let dataSets = []; + let multiParam = $(':selected', this.multiSelectEl).text(); + let multiParamVal = this.multiSelectEl.value; + + for (let response of responses) { + for (let data of response[whichDataSet].data) { + if (multiParamVal != 'gmms') { + let val = response.request.input[multiParamVal]; + let valStr = val.toString().replace('.', 'p'); + data.id = data.id + '_' + multiParamVal + '_' + valStr; + data.label = data.label + ' - ' + multiParam + ' = ' + val; + } + dataSets.push(data); + } + } + + let seriesLabels = []; + let seriesIds = []; + let seriesData = []; + + dataSets.forEach((d, i) => { + d.data.xs[0] = 'PGA'; + seriesLabels.push(d.label); + seriesIds.push(d.id); + seriesData.push(d3.zip(d.data.xs, d.data.ys)); + }); + + let singleResponse = responses[0][whichDataSet]; + + let dataInfo = { + data: seriesData, + display: singleResponse.label, + ids: seriesIds, + labels: seriesLabels, + xLabel: singleResponse.xLabel, + yLabel: singleResponse.yLabel, + }; + + return dataInfo; + } + + _responsesToLineData(responses, subView, whichDataSet) { + let multiParam = $(':selected', this.multiSelectEl).text(); + let multiParamVal = this.multiSelectEl.value; + + let dataBuilder = D3LineData.builder() + .subView(subView) + .xLimit(this.spectraXDomain); + + let pgaBuilder = D3LineData.builder() + .subView(subView) + .xLimit(this.spectraXDomain); + + for (let response of responses) { + for (let responseData of response[whichDataSet].data) { + if (multiParamVal != 'gmms') { + let val = response.request.input[multiParamVal]; + let valStr = val.toString().replace('.', 'p'); + + responseData.id = responseData.id + '_' + multiParamVal + '_' + valStr; + responseData.label = responseData.label + ' - ' + multiParam + ' = ' + val; + } + + let xValues = responseData.data.xs; + let yValues = responseData.data.ys; + + let iPGA = xValues.indexOf(Tools.imtToValue('PGA')); + xValues.splice(iPGA, 1); + let pgaX = [this.pga]; + let pgaY = yValues.splice(iPGA, 1); + + let pgaOptions = D3LineOptions.builder() + .id(responseData.id) + .label(responseData.label) + .lineStyle('none') + .markerStyle('s') + .markerColor('none') + .markerEdgeWidth(2) + .markerSize(9) + .showInLegend(false) + .build(); + + pgaBuilder.data(pgaX, pgaY, pgaOptions, ['PGA']); + + let lineOptions = D3LineOptions.builder() + .id(responseData.id) + .label(responseData.label) + .markerSize(5) + .build(); + + dataBuilder.data(xValues, yValues, lineOptions); + } + } + + let lineData = dataBuilder.build() + let pgaData = pgaBuilder.build(); + + return new SpectraData(lineData, pgaData); + } + +} + +class SpectraData { + + /** + * Spectra data. + * + * @param {D3LineData} lineData Line data + * @param {D3LineData} pgaData PGA data + */ + constructor(lineData, pgaData) { + Preconditions.checkArgumentInstanceOf(lineData, D3LineData); + Preconditions.checkArgumentInstanceOf(pgaData, D3LineData); + + this.lineData = lineData; + this.pgaData = pgaData; + } + +} diff --git a/webapp/apps/js/Util.js b/webapp/apps/js/Util.js new file mode 100644 index 000000000..8b837198e --- /dev/null +++ b/webapp/apps/js/Util.js @@ -0,0 +1,34 @@ +'use strict'; + +import Footer from './lib/Footer.js'; +import Header from './lib/Header.js'; + +export default class Util{ + + constructor(){ + let _this = this; + + _this.footer = new Footer(); + _this.header = new Header(); + _this.header.setTitle("Utilities"); + + var urlPrefix = window.location.protocol + + "//" + window.location.host + "/nshmp-haz-ws"; + + $(".serviceLink").each(function() { + var serviceUrl = urlPrefix + $(this).text(); + $(this).empty().append($("<a>") + .attr("href", serviceUrl) + .text(serviceUrl)); + }); + + + $(".formatUrl").each(function(){ + var serviceUrl = urlPrefix + $(this).text(); + $(this).empty().text(serviceUrl); + }); + + } + +} + diff --git a/webapp/apps/js/calc/ExceedanceModel.js b/webapp/apps/js/calc/ExceedanceModel.js new file mode 100644 index 000000000..a2b998074 --- /dev/null +++ b/webapp/apps/js/calc/ExceedanceModel.js @@ -0,0 +1,122 @@ + +import { D3XYPair } from '../d3/data/D3XYPair.js'; +import { Maths } from './Maths.js'; +import { Preconditions } from '../error/Preconditions.js'; +import { UncertaintyModel } from './UncertaintyModel.js'; + +export class ExceedanceModel { + + /** + * No truncation; model ignores truncation level n. + * + * Compute the probability of exceeding a value. + * @param {UncertaintyModel} model to compute exceedance + * @param {number} value for which to compute the exceedance probability + */ + static truncationOff(model, value) { + Preconditions.checkArgumentInstanceOf(model, UncertaintyModel); + Preconditions.checkArgumentNumber(value); + + return this._boundedCcdFn(model, value, 0.0, 1.0); + } + + /** + * No truncation; model ignores truncation level n. + * + * Compute the probability of exceeding a value. + * + * @param {UncertaintyModel} model to compute exceedance + * @param {D3XYPair[]} sequence for which to compute + * the exceedance probability + */ + static truncationOffSequence(model, sequence) { + Preconditions.checkArgumentInstanceOf(model, UncertaintyModel); + Preconditions.checkArgumentArrayInstanceOf(sequence, D3XYPair); + + for (let xy of sequence) { + xy.y = this.truncationOff(model, xy.x); + } + + return sequence; + } + + /** + * Upper truncation only at μ + σ * n. + * + * Compute the probability of exceeding a value. + * + * @param {UncertaintyModel} model to compute exceedance + * @param {number} value for which to compute the exceedance probability + */ + static truncationUpperOnly(model, value) { + Preconditions.checkArgumentInstanceOf(model, UncertaintyModel); + Preconditions.checkArgumentNumber(value); + + return this._boundedCcdFn(model, value, this._prob(model), 1.0); + } + + /** + * Upper truncation only at μ + σ * n. + * + * Compute the probability of exceeding a value. + * + * @param {UncertaintyModel} model to compute exceedance + * @param {D3XYPair[]} sequence for which to compute the exceedance probability + */ + static truncationUpperOnlySequence(model, sequence) { + Preconditions.checkArgumentInstanceOf(model, UncertaintyModel); + Preconditions.checkArgumentArrayInstanceOf(sequence, D3XYPair); + + for (let xy of sequence) { + xy.y = this.truncationUpperOnly(model, xy.x); + } + + return sequence; + } + + /** + * @private + * + * Bounded complementary cumulative distribution. Compute the probability that + * a value will be exceeded, subject to upper and lower probability limits. + * + * @param {UncertaintyModel} model to compute exceedance + */ + static _boundedCcdFn(model, value, pHi, pLo) { + Preconditions.checkArgumentInstanceOf(model, UncertaintyModel); + Preconditions.checkArgumentNumber(value); + Preconditions.checkArgumentNumber(pHi); + Preconditions.checkArgumentNumber(pLo); + + const p = Maths.normalCcdf(model.μ, model.σ, value); + return this._probBoundsCheck((p - pHi) / (pLo - pHi)); + } + + /** + * @private + * + * For truncated distributions, p may be out of range. For upper truncations, + * p may be less than pHi, yielding a negative value in boundedCcdFn(); for + * lower truncations, p may be greater than pLo, yielding a value > 1.0 in + * boundedCcdFn(). + */ + static _probBoundsCheck(p) { + Preconditions.checkArgumentNumber(p); + + return (p < 0.0) ? 0.0 : (p > 1.0) ? 1.0 : p; + } + + /** + * @private + * + * Compute ccd value at μ + nσ. + * + * @param {UncertaintyModel} model to compute exceedance + */ + static _prob(model) { + Preconditions.checkArgumentInstanceOf(model, UncertaintyModel); + + return Maths.normalCcdf(model.μ, model.σ, model.μ + model.n * model.σ); + } + +} diff --git a/webapp/apps/js/calc/Maths.js b/webapp/apps/js/calc/Maths.js new file mode 100644 index 000000000..78c4c2d08 --- /dev/null +++ b/webapp/apps/js/calc/Maths.js @@ -0,0 +1,63 @@ + +import { Preconditions } from '../error/Preconditions.js'; + +export class Maths { + + /** + * Normal complementary cumulative distribution function. + * + * @param {number} μ mean + * @param {number} σ standard deviation + * @param {number} x variate + */ + static normalCcdf(μ, σ, x) { + Preconditions.checkArgumentNumber(μ); + Preconditions.checkArgumentNumber(σ); + Preconditions.checkArgumentNumber(x); + + return (1.0 + this.erf((μ - x) / (σ * Math.sqrt(2)))) * 0.5; + } + + /** + * Error function approximation of Abramowitz and Stegun, formula 7.1.26 in + * the <em>Handbook of Mathematical Functions with Formulas, Graphs, and + * Mathematical Tables</em>. Although the approximation is only valid for + * x ≥ 0, because erf(x) is an odd function, + * erf(x) = −erf(−x) and negative values are supported. + */ + static erf(x) { + Preconditions.checkArgumentNumber(x); + + return x < 0.0 ? -this._erfBase(-x) : this._erfBase(x); + } + + static round(value, scale) { + Preconditions.checkArgumentNumber(value); + Preconditions.checkArgumentInteger(scale); + + let format = d3.format(`.${scale}f`); + + return Number(format(value)); + } + + static _erfBase(x) { + Preconditions.checkArgumentNumber(x); + + const P = 0.3275911; + const A1 = 0.254829592; + const A2 = -0.284496736; + const A3 = 1.421413741; + const A4 = -1.453152027; + const A5 = 1.061405429; + + const t = 1 / (1 + P * x); + const tsq = t * t; + + return 1 - (A1 * t + + A2 * tsq + + A3 * tsq * t + + A4 * tsq * tsq + + A5 * tsq * tsq * t) * Math.exp(-x * x); + } + +} diff --git a/webapp/apps/js/calc/UncertaintyModel.js b/webapp/apps/js/calc/UncertaintyModel.js new file mode 100644 index 000000000..13a0eac88 --- /dev/null +++ b/webapp/apps/js/calc/UncertaintyModel.js @@ -0,0 +1,33 @@ + +import { Preconditions } from '../error/Preconditions.js'; + +/** + * @fileoverview Container class for mean, standard deviation, and + * truncation level. + * + * @class UncertaintyModel + * @author Brandon Clayton + */ +export class UncertaintyModel { + + /** + * @param {number} μ mean + * @param {number} σ standard deviation + * @param {number} n truncation level in units of σ (truncation = n * σ) + */ + constructor(μ, σ, n) { + Preconditions.checkArgumentNumber(μ); + Preconditions.checkArgumentNumber(σ); + Preconditions.checkArgumentNumber(n); + + /** Mean */ + this.μ = μ; + + /** Standard deviation */ + this.σ = σ; + + /** Truncation level */ + this.n = n; + } + +} \ No newline at end of file diff --git a/webapp/apps/js/d3/D3LinePlot.js b/webapp/apps/js/d3/D3LinePlot.js new file mode 100644 index 000000000..c0de08a86 --- /dev/null +++ b/webapp/apps/js/d3/D3LinePlot.js @@ -0,0 +1,1630 @@ + +import { D3LineAxes } from './axes/D3LineAxes.js'; +import { D3LineData } from './data/D3LineData.js'; +import { D3LineOptions } from './options/D3LineOptions.js'; +import { D3LineLegend } from './legend/D3LineLegend.js'; +import { D3LineSeriesData } from './data/D3LineSeriesData.js'; +import { D3LineSubView } from './view/D3LineSubView.js'; +import { D3LineView } from './view/D3LineView.js'; +import { D3SaveFigure} from './D3SaveFigure.js'; +import { D3SaveLineData } from './D3SaveLineData.js'; +import { D3TextOptions } from './options/D3TextOptions.js'; +import { D3Tooltip } from './D3Tooltip.js'; +import { D3Utils } from './D3Utils.js'; +import { D3XYPair } from './data/D3XYPair.js'; + +import { Preconditions } from '../error/Preconditions.js'; + +/** + * @fileoverview Plot D3LineData + * + * @class D3LinePlot + * @author Brandon Clayton + */ +export class D3LinePlot { + + /** + * New D3LinePlot instance. + * + * @param {D3LineView} view The line view + */ + constructor(view) { + Preconditions.checkArgumentInstanceOf(view, D3LineView); + + /** @type {D3LineView} */ + this.view = view; + + /** @type {D3LineAxes} */ + this.axes = new D3LineAxes(this.view); + + /** @type {D3LineData} */ + this.upperLineData = undefined; + this._setLineData(this._getDefaultUpperLineData()); + + /** @type {D3LineData} */ + this.lowerLineData = undefined; + if (this.view.addLowerSubView) { + this._setLineData(this._getDefaultLowerLineData()); + } + + /** @type {D3Tooltip} */ + this.tooltip = new D3Tooltip(); + + /** @type {D3LineLegend} */ + this.legend = new D3LineLegend(this); + + this._addDefaultAxes(); + this._addEventListeners(); + } + + /** + * Select lines on multiple sub views that have the same id. + * + * @param {String} id The id of the lines to select + * @param {Array<D3LinePlot>} linePlots The line plots + * @param {Array<D3LineData>} lineDatas The line data + */ + static selectLineOnSubViews(id, linePlots, lineDatas) { + Preconditions.checkArgumentString(id); + Preconditions.checkArgumentArrayInstanceOf(linePlots, D3LinePlot); + Preconditions.checkArgumentArrayInstanceOf(lineDatas, D3LineData); + Preconditions.checkState( + linePlots.length == lineDatas.length, + 'Number of line plots and line datas must be the same'); + + for (let i = 0; i < linePlots.length; i++) { + let linePlot = linePlots[i]; + let lineData = lineDatas[i]; + + Preconditions.checkStateInstanceOf(linePlot, D3LinePlot); + Preconditions.checkStateInstanceOf(lineData, D3LineData); + + linePlot.selectLine(id, lineData); + } + } + + /** + * Sync selections between multiple sub views. + * + * @param {Array<D3LinePlot>} linePlots The line plots + * @param {Array<D3LineData>} lineDatas The line data + */ + static syncSubViews(linePlots, lineDatas) { + Preconditions.checkArgumentArrayInstanceOf(linePlots, D3LinePlot); + Preconditions.checkArgumentArrayInstanceOf(lineDatas, D3LineData); + Preconditions.checkState( + linePlots.length == lineDatas.length, + 'Number of line plots and line datas must be the same'); + + for (let lineData of lineDatas) { + d3.select(lineData.subView.svg.dataContainerEl) + .selectAll('.data-enter') + .on('click', (/** @type {D3LineSeriesData} */ series) => { + Preconditions.checkStateInstanceOf(series, D3LineSeriesData); + D3LinePlot.selectLineOnSubViews( + series.lineOptions.id, + linePlots, + lineDatas); + }); + } + } + + /** + * Add text to a sub view's plot. + * + * @param {D3LineSubView} subView The sub view to add text + * @param {Number} x The X coordinate of text + * @param {Number} y The Y coordinate of text + * @param {String} text The text + * @param {D3TextOptions=} textOptions Optional text options + * @returns {SVGElement} The text element + */ + addText(subView, x, y, text, textOptions = D3TextOptions.withDefaults()) { + Preconditions.checkArgumentInstanceOf(subView, D3LineSubView); + Preconditions.checkArgumentNumber(x); + Preconditions.checkArgumentNumber(y); + Preconditions.checkArgumentString(text); + Preconditions.checkArgumentInstanceOf(textOptions, D3TextOptions); + + let textD3 = d3.select(subView.svg.dataContainerEl) + .append('g') + .attr('class', 'text-enter') + .append('text') + .datum(new D3XYPair(x, y)); + + let textEl = textD3.node(); + Preconditions.checkStateInstanceOfSVGElement(textEl); + + this.moveText(subView, x, y, textEl); + this.updateText(textEl, text); + this.updateTextOptions(textEl, textOptions); + + return textEl; + } + + /** + * Clear all plots off a D3LineSubView. + * + * @param {D3LineSubView} subView + */ + clear(subView) { + Preconditions.checkArgumentInstanceOf(subView, D3LineSubView); + + this.legend.remove(subView); + + d3.select(subView.svg.dataContainerEl).datum(null); + + d3.select(subView.svg.dataContainerEl) + .selectAll('*') + .remove(); + } + + /** + * Clear all plots off the sub views + */ + clearAll() { + this.clear(this.view.upperSubView); + + if (this.view.addLowerSubView) this.clear(this.view.lowerSubView); + } + + /** + * Get the current X domain of the plot. + * + * @param {D3LineSubView} subView The sub view to get domain + * @returns {Array<Number>} The X axis domain: [ xMin, xMax ] + */ + getXDomain(subView) { + Preconditions.checkArgumentInstanceOf(subView, D3LineSubView); + + let lineData = subView.options.subViewType == 'lower' ? + this.lowerLineData : this.upperLineData; + + return this.axes._getXAxisScale( + lineData, + this._getCurrentXScale(lineData.subView)) + .domain(); + } + + /** + * Get the current Y domain of the plot. + * + * @param {D3LineSubView} subView The sub view to get the domain + * @returns {Array<Number>} The Y axis domain: [ yMin, yMax ] + */ + getYDomain(subView) { + Preconditions.checkArgumentInstanceOf(subView, D3LineSubView); + + let lineData = subView.options.subViewType == 'lower' ? + this.lowerLineData : this.upperLineData; + + return this.axes._getYAxisScale( + lineData, + this._getCurrentYScale(lineData.subView)) + .domain(); + } + + /** + * Make a vertical line draggable. + * + * @param {D3LineSubView} subView The sub view were the line is to drag + * @param {SVGElement} refLineEl The reference line element + * @param {Array<Number>} xLimit The limits that the line can be dragged + * @param {Function} callback The funciton to call when the line is dragged. + * The arguments passed to the callback function are + * (Number, D3LineSeriesData, SVGElement) where: + * - Number is the current X value + * - D3LineSeriesData is updated line series data + * - SVGElement is element being dragged + */ + makeDraggableInX(subView, refLineEl, xLimit, callback = () => {}) { + Preconditions.checkArgumentInstanceOf(subView, D3LineSubView); + Preconditions.checkArgumentInstanceOfSVGElement(refLineEl); + Preconditions.checkArgumentArrayLength(xLimit, 2); + Preconditions.checkState( + xLimit[0] < xLimit[1], + `X limit min [${xLimit[0]}] must be less than X limit max [${xLimit[1]}]`); + Preconditions.checkArgumentInstanceOf(callback, Function); + + let lineData = subView.options.subViewType == 'lower' ? + this.lowerLineData : this.upperLineData; + + d3.select(refLineEl).select('.plot-line').style('cursor', 'col-resize'); + + d3.selectAll([ refLineEl ]) + .on('click', null) + .call(d3.drag() + .on('start', (/** @type {D3LineSeriesData*/ series) => { + Preconditions.checkStateInstanceOf(series, D3LineSeriesData); + this._onDragStart(series, refLineEl); + }) + .on('end', (/** @type {D3LineSeriesData*/ series) => { + Preconditions.checkStateInstanceOf(series, D3LineSeriesData); + this._onDragEnd(series, refLineEl); + }) + .on('drag', (/** @type {D3LineSeriesData */ series) => { + Preconditions.checkStateInstanceOf(series, D3LineSeriesData); + let xValues = series.xValues; + Preconditions.checkStateArrayLength(xValues, 2); + Preconditions.checkState( + xValues[0] == xValues[1], + 'Not a vertical line'); + this._onDragInX(lineData, series, refLineEl, xLimit, callback); + })); + } + + /** + * Make a horizontal line draggable. + * + * @param {D3LineSubView} subView The sub view were the line is to drag + * @param {SVGElement} refLineEl The reference line element + * @param {Array<Number>} yLimit The limits that the line can be dragged + * @param {Function} callback The funciton to call when the line is dragged. + * The arguments passed to the callback function are + * (Number, D3LineSeriesData, SVGElement) where: + * - Number is the current Y value + * - D3LineSeriesData is updated line series data + * - SVGElement is element being dragged + */ + makeDraggableInY(subView, refLineEl, yLimit, callback = () => {}) { + Preconditions.checkArgumentInstanceOf(subView, D3LineSubView); + Preconditions.checkArgumentInstanceOfSVGElement(refLineEl); + Preconditions.checkArgumentArrayLength(yLimit, 2); + Preconditions.checkState( + yLimit[0] < yLimit[1], + `Y limit min [${yLimit[0]}] must be less than Y limit max [${yLimit[1]}]`); + Preconditions.checkArgumentInstanceOf(callback, Function); + + let lineData = subView.options.subViewType == 'lower' ? + this.lowerLineData : this.upperLineData; + + d3.select(refLineEl).select('.plot-line').style('cursor', 'row-resize'); + + d3.selectAll([ refLineEl ]) + .selectAll('.plot-line') + .on('click', null) + .call(d3.drag() + .on('start', (/** @type {D3LineSeriesData*/ series) => { + Preconditions.checkStateInstanceOf(series, D3LineSeriesData); + this._onDragStart(series, refLineEl); + }) + .on('end', (/** @type {D3LineSeriesData*/ series) => { + Preconditions.checkStateInstanceOf(series, D3LineSeriesData); + this._onDragEnd(series, refLineEl); + }) + .on('drag', (/** @type {D3LineSeriesData */ series) => { + Preconditions.checkStateInstanceOf(series, D3LineSeriesData); + let yValues = series.yValues; + Preconditions.checkStateArrayLength(yValues, 2); + Preconditions.checkState( + yValues[0] == yValues[1], + 'Not a horizontal line'); + this._onDragInY(lineData, series, refLineEl, yLimit, callback); + })); + } + + /** + * Move a text element to a new location. + * + * @param {D3LineSubView} subView The sub view of the text + * @param {Number} x The new X coordinate + * @param {Number} y The new Y coordinate + * @param {SVGElement} textEl The text element + */ + moveText(subView, x, y, textEl) { + Preconditions.checkArgumentInstanceOf(subView, D3LineSubView); + Preconditions.checkArgumentNumber(x); + Preconditions.checkArgumentNumber(y); + Preconditions.checkArgumentInstanceOfSVGElement(textEl); + + let xScale = this._getCurrentXScale(subView); + let yScale = this._getCurrentYScale(subView); + + let lineData = subView.options.subViewType == 'lower' ? + this.lowerLineData : this.upperLineData; + + let xyPair = new D3XYPair(x, y); + + d3.select(textEl) + .datum(xyPair) + .attr('x', this.axes.x(lineData, xScale, xyPair)) + .attr('y', this.axes.y(lineData, yScale, xyPair)); + } + + /** + * Fire a custom function when a line or symbol is selected. + * Arguments passed to the callback function: + * - D3LineSeriesData: The series data from the plot selection + * + * @param {D3LineData} lineData The line data + * @param {Function} callback Function to call when plot is selected + */ + onPlotSelection(lineData, callback) { + Preconditions.checkArgumentInstanceOf(lineData, D3LineData); + Preconditions.checkArgumentInstanceOf(callback, Function); + + lineData.subView.svg.dataContainerEl.addEventListener('plotSelection', (e) => { + let series = e.detail; + Preconditions.checkStateInstanceOf(series, D3LineSeriesData); + callback(series); + }); + } + + /** + * Creates a 2-D line plot from D3LineData. + * + * @param {D3LineData} lineData The line data to plot + */ + plot(lineData) { + Preconditions.checkArgumentInstanceOf(lineData, D3LineData); + + lineData = this._dataEnter(lineData); + + let xScale = this._getCurrentXScale(lineData.subView); + let yScale = this._getCurrentYScale(lineData.subView); + + this.axes.createXAxis(lineData, xScale); + this.axes.createYAxis(lineData, yScale); + + this.legend.create(lineData); + + this._plotUpdateHorizontalRefLine(lineData, xScale, yScale); + this._plotUpdateVerticalRefLine(lineData, xScale, yScale); + this._plotSelectionEventListener(lineData); + } + + /** + * Plot a reference line at zero, y=0. + * + * @param {D3LineSubView} subView The sub view to add the line to + */ + plotZeroRefLine(subView) { + Preconditions.checkArgumentInstanceOf(subView, D3LineSubView); + + let lineOptions = D3LineOptions.builder() + .color(subView.options.referenceLineColor) + .id('zero-ref-line') + .lineWidth(subView.options.referenceLineWidth) + .markerSize(0) + .selectable(false) + .showInLegend(false) + .build(); + + let refLineEl = this.plotHorizontalRefLine(subView, 0, lineOptions); + + d3.select(refLineEl).lower(); + } + + /** + * Plot a horizontal reference line, y=value. + * + * @param {D3LineSubView} subView The sub view to put reference line + * @param {Number} y The Y value of reference line + * @param {D3LineOptions=} lineOptions The line options + * @returns {SVGElement} The reference line element + */ + plotHorizontalRefLine( + subView, + y, + lineOptions = D3LineOptions.withRefLineDefaults()) { + Preconditions.checkArgumentInstanceOf(subView, D3LineSubView); + Preconditions.checkArgumentNumber(y); + Preconditions.checkArgumentInstanceOf(lineOptions, D3LineOptions); + + let series = new D3LineSeriesData( + this.getXDomain(subView), + [ y, y ], + lineOptions); + + let lineData = subView.options.subViewType == 'lower' ? + this.lowerLineData : this.upperLineData; + + lineData = D3LineData.builder() + .of(lineData) + .data(series.xValues, series.yValues, series.lineOptions) + .build(); + + let refLineD3 = d3.select(subView.svg.dataContainerEl) + .append('g') + .attr('class', 'data-enter-ref-line horizontal-ref-line') + .attr('id', lineOptions.id); + + let refLineEl = refLineD3.node(); + Preconditions.checkStateInstanceOfSVGElement(refLineEl); + + this._plotRefLine(lineData, series, refLineEl); + + return refLineEl; + } + + /** + * Plot a vertical reference line, x=value. + * + * @param {D3LineSubView} subView The sub view to put reference line + * @param {Number} x The X value of reference line + * @param {D3LineOptions=} lineOptions The line options + * @returns {SVGElement} The reference line element + */ + plotVerticalRefLine( + subView, + x, + lineOptions = D3LineOptions.withRefLineDefaults()) { + Preconditions.checkArgumentInstanceOf(subView, D3LineSubView); + Preconditions.checkArgumentNumber(x); + Preconditions.checkArgumentInstanceOf(lineOptions, D3LineOptions); + + let series = new D3LineSeriesData( + [ x, x ], + this.getYDomain(subView), + lineOptions); + + let lineData = subView.options.subViewType == 'lower' ? + this.lowerLineData : this.upperLineData; + + lineData = D3LineData.builder() + .of(lineData) + .data(series.xValues, series.yValues, series.lineOptions) + .build(); + + let refLineD3 = d3.select(subView.svg.dataContainerEl) + .append('g') + .attr('class', 'data-enter-ref-line vertical-ref-line') + .attr('id', lineOptions.id); + + let refLineEl = refLineD3.node(); + Preconditions.checkStateInstanceOfSVGElement(refLineEl); + + this._plotRefLine(lineData, series, refLineEl); + + return refLineEl; + } + + /** + * Get an SVG element in a sub view's plot based on the data's id. + * + * @param {D3LineSubView} subView The sub view the data element is in + * @param {String} id The id of the data element + * @returns {SVGElement} The SVG element with that id + */ + querySelector(subView, id) { + Preconditions.checkArgumentInstanceOf(subView, D3LineSubView); + Preconditions.checkArgumentString(id); + + let dataEl = subView.svg.dataContainerEl.querySelector(`#${id}`); + Preconditions.checkNotNull( + dataEl, + `Id [${id}] not found in [${subView.options.subViewType}] sub view`); + Preconditions.checkStateInstanceOfSVGElement(dataEl); + + return dataEl; + } + + /** + * Get SVG elements in a sub view's plot based on the data's id. + * + * @param {D3LineSubView} subView The sub view the data element is in + * @param {String} id The id of the data element + * @returns {NodeList<SVGElement>} Node list of SVG elements with that id + */ + querySelectorAll(subView, id) { + Preconditions.checkArgumentInstanceOf(subView, D3LineSubView); + Preconditions.checkArgumentString(id); + + let dataEls = subView.svg.dataContainerEl.querySelectorAll(`#${id}`); + Preconditions.checkStateInstanceOf(dataEls, NodeList); + Preconditions.checkState( + dataEls.length > 0, + `Id [${id}] not found in [${subView.options.subViewType}] sub view`); + + for (let el of dataEls) { + Preconditions.checkStateInstanceOfSVGElement(el); + } + + return dataEls; + } + + /** + * Select lines of multiple line data given an id. + * + * @param {String} id of line to select + * @param {...D3LineData} lineDatas The line data + */ + selectLine(id, ...lineDatas) { + Preconditions.checkArgumentString(id); + + this.legend.selectLegendEntry(id, ...lineDatas); + + for (let lineData of lineDatas) { + Preconditions.checkArgumentInstanceOf(lineData, D3LineData); + this._resetPlotSelection(lineData); + + d3.select(lineData.subView.svg.dataContainerEl) + .selectAll(`#${id}`) + .each(( + /** @type {D3LineSeriesData} */ series, + /** @type {Number} */ i, + /** @type {NodeList} */ els) => { + Preconditions.checkStateInstanceOf(series, D3LineSeriesData); + Preconditions.checkStateInstanceOfSVGElement(els[i]); + this._plotSelection(lineData, series, els[i]); + }); + } + } + + /** + * Sync the plot selections between the upper and lower sub views. + */ + syncSubViews() { + this.legend.syncSubViews(); + for (let lineData of [this.upperLineData, this.lowerLineData]) { + d3.select(lineData.subView.svg.dataContainerEl) + .selectAll('.data-enter') + .on('click', (/** @type {D3LineSeriesData} */ series) => { + Preconditions.checkStateInstanceOf(series, D3LineSeriesData); + this.selectLine( + series.lineOptions.id, + this.upperLineData, + this.lowerLineData); + }); + } + } + + /** + * Update the text on a text element. + * + * @param {SVGElement} textEl The text element + * @param {String} text The new text + */ + updateText(textEl, text) { + Preconditions.checkArgumentInstanceOfSVGElement(textEl); + Preconditions.checkArgumentString(text); + + d3.select(textEl).text(text) + } + + /** + * Update the text on a text element. + * + * @param {SVGElement} textEl The text element + * @param {D3TextOptions=} textOptions Optional text options + */ + updateTextOptions(textEl, textOptions) { + Preconditions.checkArgumentInstanceOfSVGElement(textEl); + Preconditions.checkArgumentInstanceOf(textOptions, D3TextOptions); + + let cxRotate = textEl.getAttribute('x'); + let cyRotate = textEl.getAttribute('y'); + + d3.select(textEl) + .attr('alignment-baseline', textOptions.alignmentBaseline) + .attr('dx', textOptions.dx) + .attr('dy', -textOptions.dy) + .attr('fill', textOptions.color) + .attr('stroke', textOptions.stroke) + .attr('stroke-width', textOptions.strokeWidth) + .attr('transform', `rotate(${textOptions.rotate}, ${cxRotate}, ${cyRotate})`) + .style('font-size', `${textOptions.fontSize}px`) + .style('font-weight', textOptions.fontWeight) + .style('text-anchor', textOptions.textAnchor); + } + + /** + * @private + * Add the default X and Y axes. + * + * Based on D3LineSubViewOptions.defaultXLimit and + * D3LineSubViewOptions.defaultYLimit. + */ + _addDefaultAxes() { + this.axes.createXAxis( + this.upperLineData, + this.view.getXAxisScale(this.view.upperSubView)); + + this.axes.createYAxis( + this.upperLineData, + this.view.getYAxisScale(this.view.upperSubView)); + + if (this.view.addLowerSubView) { + this.axes.createXAxis( + this.lowerLineData, + this.view.getXAxisScale(this.view.lowerSubView)); + + this.axes.createYAxis( + this.lowerLineData, + this.view.getYAxisScale(this.view.lowerSubView)); + } + } + + /** + * @private + * Add all event listeners + */ + _addEventListeners() { + this.view.viewFooter.saveMenuEl.querySelectorAll('a').forEach((el) => { + el.addEventListener('click', (e) => { + this._onSaveMenu(e); + }); + }); + + this.view.viewFooter.xAxisBtnEl.addEventListener('click', () => { + this._onXAxisClick(event); + }); + + this.view.viewFooter.yAxisBtnEl.addEventListener('click', () => { + this._onYAxisClick(event); + }); + + this.view.viewHeader.gridLinesCheckEl.addEventListener('click', () => { + this._onGridLineIconClick(); + }); + + this.view.viewHeader.legendCheckEl.addEventListener('click', () => { + this._onLegendIconClick(); + }); + } + + /** + * @private + * Enter all data from D3LineData.series and any existing data + * into new SVG elements. + * + * @param {D3LineData} lineData The data + */ + _dataEnter(lineData) { + Preconditions.checkArgumentInstanceOf(lineData, D3LineData); + + /** @type {Array<D3LineData>} */ + let currentLineData = d3.select(lineData.subView.svg.dataContainerEl) + .datum(); + + let data = currentLineData || []; + data.push(lineData); + let updatedLineData = D3LineData.of(...data); + + d3.select(lineData.subView.svg.dataContainerEl) + .selectAll('.data-enter') + .remove(); + + let seriesEnter = d3.select(lineData.subView.svg.dataContainerEl) + .datum([ updatedLineData ]) + .selectAll('.data-enter') + .data(updatedLineData.series); + + seriesEnter.exit().remove(); + + let seriesDataEnter = seriesEnter.enter() + .append('g') + .attr('class', 'data-enter') + .attr('id', (/** @type {D3LineSeriesData} */ series) => { + Preconditions.checkStateInstanceOf(series, D3LineSeriesData); + return series.lineOptions.id; + }); + + /* Update upperLineData or lowerLineData */ + this._setLineData(updatedLineData); + + let seriesDataEnterEls = seriesDataEnter.nodes(); + this._plotLine(updatedLineData, seriesDataEnterEls); + this._plotSymbol(updatedLineData, seriesDataEnterEls); + + return updatedLineData; + } + + /** + * @private + * Returns a default D3LineData for the lower sub view to + * show a empty plot on startup. + * + * @returns {D3LineData} The default line data for lower view + */ + _getDefaultLowerLineData() { + let lowerXLimit = this.view.lowerSubView.options.defaultXLimit; + let lowerYLimit = this.view.lowerSubView.options.defaultYLimit; + + let lowerLineData = D3LineData.builder() + .xLimit(lowerXLimit) + .yLimit(lowerYLimit) + .subView(this.view.lowerSubView) + .build(); + + return lowerLineData; + } + + /** + * @private + * Returns a default D3LineData for the upper sub view to + * show a empty plot on startup. + * + * @returns {D3LineData} The default line data for upper view + */ + _getDefaultUpperLineData() { + let upperXLimit = this.view.upperSubView.options.defaultXLimit; + let upperYLimit = this.view.upperSubView.options.defaultYLimit; + + let upperLineData = D3LineData.builder() + .xLimit(upperXLimit) + .yLimit(upperYLimit) + .subView(this.view.upperSubView) + .build(); + + return upperLineData; + } + + /** + * @private + * Get the current X scale of a D3LineSubView, either: 'log' || 'linear' + * + * @param {D3LineSubView} subView The sub view to get X scale + * @returns {String} The X scale: 'log' || 'linear' + */ + _getCurrentXScale(subView) { + Preconditions.checkArgumentInstanceOf(subView, D3LineSubView); + + if (this.view.viewOptions.syncXAxisScale || + subView.options.subViewType == 'upper') { + return this.view.viewFooter.xLinearBtnEl.classList.contains('active') ? + this.view.viewFooter.xLinearBtnEl.getAttribute('value') : + this.view.viewFooter.xLogBtnEl.getAttribute('value'); + } else { + return subView.options.xAxisScale; + } + } + + /** + * @private + * Get the current Y scale of a D3LineSubView, either: 'log' || 'linear' + * + * @param {D3LineSubView} subView The sub view to get Y scale + * @returns {String} The Y scale: 'log' || 'linear' + */ + _getCurrentYScale(subView) { + Preconditions.checkArgumentInstanceOf(subView, D3LineSubView); + + if (this.view.viewOptions.syncYAxisScale || + subView.options.subViewType == 'upper') { + return this.view.viewFooter.yLinearBtnEl.classList.contains('active') ? + this.view.viewFooter.yLinearBtnEl.getAttribute('value') : + this.view.viewFooter.yLogBtnEl.getAttribute('value'); + } else { + return subView.options.yAxisScale; + } + } + + /** + * @private + * Handler to add the grid lines when the grid lines icon is checked. + */ + _onAddGridLines() { + this.view.viewHeader.gridLinesCheckEl.setAttribute('checked', 'true'); + + this.axes.createXGridLines( + this.upperLineData, + this._getCurrentXScale(this.view.upperSubView)); + + this.axes.createYGridLines( + this.upperLineData, + this._getCurrentYScale(this.view.upperSubView)); + + if (this.view.addLowerSubView) { + this.axes.createXGridLines( + this.lowerLineData, + this._getCurrentXScale(this.view.lowerSubView)); + + this.axes.createYGridLines( + this.lowerLineData, + this._getCurrentYScale(this.view.lowerSubView)); + } + } + + /** + * @private + * Event handler for mouse over plot symbols; add tooltip. + * + * @param {D3LineData} lineData The line data + * @param {D3LineSeriesData} series The data series + */ + _onDataSymbolMouseover(lineData, series) { + Preconditions.checkArgumentInstanceOf(lineData, D3LineData); + Preconditions.checkArgumentInstanceOf(series, D3LineSeriesData); + + let xScale = this._getCurrentXScale(lineData.subView); + let yScale = this._getCurrentYScale(lineData.subView); + + let xyPair = series.data[0]; + let tooltipX = this.axes.x(lineData, xScale, xyPair); + let tooltipY = this.axes.y(lineData, yScale, xyPair); + + let subViewOptions = lineData.subView.options; + + let x = subViewOptions.xValueToExponent ? + xyPair.x.toExponential(subViewOptions.xExponentFractionDigits) : + xyPair.x; + + let y = subViewOptions.yValueToExponent ? + xyPair.y.toExponential(subViewOptions.yExponentFractionDigits) : + xyPair.y; + + let tooltipText = [ + `${lineData.subView.options.lineLabel}: ${series.lineOptions.label}`, + `${lineData.subView.options.xLabel}: ${xyPair.xString || x}`, + `${lineData.subView.options.yLabel}: ${xyPair.yString || y}`, + ]; + + this.tooltip.create(lineData.subView, tooltipText, tooltipX, tooltipY); + } + + /** + * @private + * Event handler for mouse out of plot symols; remove toolip. + * + * @param {D3LineData} lineData The line data + */ + _onDataSymbolMouseout(lineData) { + Preconditions.checkArgumentInstanceOf(lineData, D3LineData); + this.tooltip.remove(lineData.subView); + } + + /** + * @private + * Drag line in X direction. + * + * @param {D3LineData} lineData The line data + * @param {D3LineSeriesData} series The series data + * @param {SVGElement} dataEl The element being dragged + * @param {Array<Number>} yLimit The Y limit + * @param {Function} callback The function to call + */ + _onDragInX(lineData, series, dataEl, xLimit, callback) { + Preconditions.checkArgumentInstanceOf(lineData, D3LineData); + Preconditions.checkArgumentInstanceOf(series, D3LineSeriesData); + Preconditions.checkArgumentInstanceOfSVGElement(dataEl); + Preconditions.checkArgumentArrayLength(xLimit, 2); + Preconditions.checkArgumentInstanceOf(callback, Function); + + d3.event.sourceEvent.stopPropagation(); + + let xScale = this._getCurrentXScale(lineData.subView); + let yScale = this._getCurrentYScale(lineData.subView); + let xBounds = this.axes._getXAxisScale(lineData, xScale); + let xDomain = xBounds.domain(); + + let xMinLimit = xLimit[0] < xDomain[0] ? xDomain[0] : xLimit[0]; + let xMaxLimit = xLimit[1] > xDomain[1] ? xDomain[1] : xLimit[1]; + + let x = xBounds.invert(d3.event.x); + x = x < xMinLimit ? xMinLimit : x > xMaxLimit ? xMaxLimit : x; + + let snapTo = 1 / lineData.subView.options.dragLineSnapTo; + x = Math.round( x * snapTo ) / snapTo; + let xValues = series.xValues.map(() => { return x; }); + + let updatedSeries = new D3LineSeriesData( + xValues, + series.yValues, + series.lineOptions, + series.xStrings, + series.yStrings); + + d3.select(dataEl).raise(); + callback(x, updatedSeries, dataEl); + let duration = 0; + this._plotUpdateDataEl(lineData, updatedSeries, dataEl, xScale, yScale, duration); + } + + /** + * @private + * Drag line in Y direction. + * + * @param {D3LineData} lineData The line data + * @param {D3LineSeriesData} series The series data + * @param {SVGElement} dataEl The element being dragged + * @param {Array<Number>} yLimit The Y limit + * @param {Function} callback The function to call + */ + _onDragInY(lineData, series, dataEl, yLimit, callback) { + Preconditions.checkArgumentInstanceOf(lineData, D3LineData); + Preconditions.checkArgumentInstanceOf(series, D3LineSeriesData); + Preconditions.checkArgumentInstanceOfSVGElement(dataEl); + Preconditions.checkArgumentArrayLength(yLimit, 2); + Preconditions.checkArgumentInstanceOf(callback, Function); + + d3.event.sourceEvent.stopPropagation(); + + let xScale = this._getCurrentXScale(lineData.subView); + let yScale = this._getCurrentYScale(lineData.subView); + let yBounds = this.axes._getYAxisScale(lineData, yScale); + let yDomain = yBounds.domain(); + + let yMinLimit = yLimit[0] < yDomain[0] ? yDomain[0] : yLimit[0]; + let yMaxLimit = yLimit[1] > yDomain[1] ? yDomain[1] : yLimit[1]; + + let y = yBounds.invert(d3.event.y); + y = y < yMinLimit ? yMinLimit : y > yMaxLimit ? yMaxLimit : y; + + let snapTo = 1 / lineData.subView.options.dragLineSnapTo; + y = Math.round( y * snapTo ) / snapTo; + let yValues = series.yValues.map(() => { return y; }); + + let updatedSeries = new D3LineSeriesData( + series.xValues, + yValues, + series.lineOptions, + series.xStrings, + series.yStrings); + + d3.select(dataEl).raise(); + callback(y, updatedSeries, dataEl); + let duration = 0; + this._plotUpdateDataEl(lineData, updatedSeries, dataEl, xScale, yScale, duration); + } + + /** + * @private + * Reset line and symbol size on drag end. + * + * @param {D3LineSeriesData} series The series + * @param {SVGElement} dataEl + */ + _onDragEnd(series, dataEl) { + Preconditions.checkArgumentInstanceOf(series, D3LineSeriesData); + Preconditions.checkArgumentInstanceOfSVGElement(dataEl); + + d3.select(dataEl) + .selectAll('.plot-line') + .attr('stroke-width', series.lineOptions.lineWidth); + + d3.select(dataEl) + .selectAll('.plot-symbol') + .attr('d', series.d3Symbol.size(series.lineOptions.d3SymbolSize)()) + .attr('stroke-width', series.lineOptions.markerEdgeWidth); + } + + /** + * @private + * Increase line and symbol size on drag start. + * + * @param {D3LineSeriesData} series The series + * @param {SVGElement} dataEl + */ + _onDragStart(series, dataEl) { + Preconditions.checkArgumentInstanceOf(series, D3LineSeriesData); + Preconditions.checkArgumentInstanceOfSVGElement(dataEl); + + let lineOptions = series.lineOptions; + let lineWidth = lineOptions.lineWidth * lineOptions.selectionMultiplier; + let symbolSize = lineOptions.d3SymbolSize * lineOptions.selectionMultiplier; + let edgeWidth = lineOptions.markerEdgeWidth * lineOptions.selectionMultiplier; + + d3.select(dataEl) + .selectAll('.plot-line') + .attr('stroke-width', lineWidth); + + d3.select(dataEl) + .selectAll('.plot-symbol') + .attr('d', series.d3Symbol.size(symbolSize)()) + .attr('stroke-width', edgeWidth); + } + + /** + * @private + * Event handler to add or remove the grid lines when the grid lines + * icon is clicked. + */ + _onGridLineIconClick() { + let isChecked = this.view.viewHeader.gridLinesCheckEl.getAttribute('checked'); + + d3.select(this.view.viewHeader.gridLinesCheckEl) + .style('color', !isChecked ? 'black' : '#bfbfbf'); + + if (isChecked) { + this._onRemoveGridLines(); + } else { + this._onAddGridLines(); + } + } + + /** + * @private + * Event handler for legend icon click; add/remove legend. + * + * @param {D3LineData} lineData The line data + */ + _onLegendIconClick() { + let isChecked = this.view.viewHeader.legendCheckEl.getAttribute('checked'); + + if (isChecked) { + this.view.viewHeader.legendCheckEl.removeAttribute('checked'); + this.legend.hideAll(); + } else { + this.view.viewHeader.legendCheckEl.setAttribute('checked', 'true'); + this.legend.showAll(); + } + } + + /** + * @private + * Handler to remove the grid lines when the grid lines icon is + * not checked + */ + _onRemoveGridLines() { + this.view.viewHeader.gridLinesCheckEl.removeAttribute('checked'); + + this.axes.removeXGridLines(this.view.upperSubView); + this.axes.removeYGridLines(this.view.upperSubView); + + if (this.view.addLowerSubView) { + this.axes.removeXGridLines(this.view.lowerSubView); + this.axes.removeYGridLines(this.view.lowerSubView); + } + } + + /** + * Save/preview figure or data + * + * @param {Event} event The event + */ + _onSaveMenu(event) { + let saveType = event.target.getAttribute('data-type'); + let saveFormat = event.target.getAttribute('data-format'); + let imageOnly = this.view.viewFooter.imageOnlyEl.checked; + + switch(saveType) { + case 'save-figure': + if (imageOnly) D3SaveFigure.saveImageOnly(this.view, saveFormat); + else D3SaveFigure.save(this.view, saveFormat); + break; + case 'preview-figure': + if (imageOnly) D3SaveFigure.previewImageOnly(this.view, saveFormat); + else D3SaveFigure.preview(this.view, saveFormat); + break; + case 'save-data': + Preconditions.checkNotUndefined( + this.view.getSaveData(), + 'Must set the save data, D3LineView.setSaveData()'); + D3SaveLineData.saveCSV(...this.view.getSaveData()); + break; + default: + throw new NshmpError(`Save type [${saveType}] not supported`); + } + } + + /** + * @private + * Update the plot when the X axis buttons are clicked. + * + * @param {Event} event The click event + */ + _onXAxisClick(event) { + if (event.target.hasAttribute('disabled')) return; + + let xScale = event.target.getAttribute('value'); + let yScaleUpper = this._getCurrentYScale(this.view.upperSubView); + + this.axes.createXAxis(this.upperLineData, xScale); + this._plotUpdate(this.upperLineData, xScale, yScaleUpper); + + if (this.view.addLowerSubView && this.view.viewOptions.syncXAxisScale) { + let yScaleLower = this._getCurrentYScale(this.view.lowerSubView); + this.axes.createXAxis(this.lowerLineData, xScale); + this._plotUpdate(this.lowerLineData, xScale, yScaleLower); + } + } + + /** + * @private + * Update the plot when the Y axus buttons are clicked. + * + * @param {Event} event The click event + */ + _onYAxisClick(event) { + if (event.target.hasAttribute('disabled')) return; + + let xScaleUpper = this._getCurrentXScale(this.view.upperSubView); + let yScale = event.target.getAttribute('value'); + + this.axes.createYAxis(this.upperLineData, yScale); + this._plotUpdate(this.upperLineData, xScaleUpper, yScale); + + if (this.view.addLowerSubView && this.view.viewOptions.syncYAxisScale) { + let xScaleLower = this._getCurrentXScale(this.view.lowerSubView); + this.axes.createYAxis(this.lowerLineData, yScale); + this._plotUpdate(this.lowerLineData, xScaleLower, yScale); + } + } + + /** + * @private + * Plot the lines. + * + * @param {D3LineData} lineData The line data + * @param {Array<SVGElement>} seriesEnter + */ + _plotLine(lineData, seriesEnterEls) { + let xScale = this._getCurrentXScale(lineData.subView); + let yScale = this._getCurrentYScale(lineData.subView); + let line = this.axes.line(lineData, xScale, yScale); + + d3.selectAll(seriesEnterEls) + .append('path') + .each(( + /** @type {D3LineSeriesData} */ series, + /** @type {Number} */ i, + /** @type {Array<SVGElement>} */ els) => { + Preconditions.checkStateInstanceOf(series, D3LineSeriesData); + Preconditions.checkStateInteger(i); + Preconditions.checkStateArrayInstanceOf(els, SVGElement); + this._plotLineSeries(series, els[i], line); + }); + } + + /** + * @private + * Add a D3LineSeries line to the plot. + * + * @param {D3LineSeriesData} series The series to add + * @param {SVGElement} dataEl The plot data element + * @param {Function} line The line function + */ + _plotLineSeries(series, dataEl, line) { + Preconditions.checkArgumentInstanceOf(series, D3LineSeriesData); + Preconditions.checkArgumentInstanceOfSVGElement(dataEl); + Preconditions.checkArgumentInstanceOf(line, Function); + + d3.select(dataEl) + .classed('plot-line', true) + .attr('d', (/** @type {D3LineSeriesData} */series) => { + return line(series.data); + }) + .attr('stroke-dasharray', (/** @type {D3LineSeriesData} */ series) => { + return series.lineOptions.svgDashArray; + }) + .attr('stroke', (/** @type {D3LineSeriesData} */ series) => { + return series.lineOptions.color; + }) + .attr('stroke-width', (/** @type {D3LineSeriesData} */ series) => { + return series.lineOptions.lineWidth; + }) + .attr('fill', 'none') + .style('shape-rendering', 'geometricPrecision') + .style('cursor', 'pointer'); + } + + /** + * @private + * Add a reference line to the plot. + * + * @param {D3LineData} lineData The upper or lower line data + * @param {D3LineSeriesData} series The series to add + * @param {SVGElement} refLineEl The reference line element + */ + _plotRefLine(lineData, series, refLineEl) { + Preconditions.checkArgumentInstanceOf(lineData, D3LineData); + Preconditions.checkArgumentInstanceOf(series, D3LineSeriesData); + Preconditions.checkArgumentInstanceOfSVGElement(refLineEl); + + let xScale = this._getCurrentXScale(lineData.subView); + let yScale = this._getCurrentYScale(lineData.subView); + let line = this.axes.line(lineData, xScale, yScale); + + d3.select(refLineEl) + .datum(series) + .selectAll('path') + .data([ series ]) + .enter() + .append('path') + .each(( + /** @type {D3LineSeriesData} */ series, + /** @type {Number} */ i, + /** @type {Array<SVGElement>} */ els) => { + Preconditions.checkStateInstanceOf(series, D3LineSeriesData); + Preconditions.checkStateInteger(i); + Preconditions.checkStateArrayInstanceOf(els, SVGElement); + this._plotLineSeries(series, els[i], line); + }); + } + + /** + * @private + * Select the line and symbol. + * + * @param {D3LineData} lineData The line data + * @param {D3LineSeriesData} series The data series + * @param {SVGElement} dataEl The data SVG element + */ + _plotSelection(lineData, series, dataEl) { + Preconditions.checkArgumentInstanceOf(lineData, D3LineData); + Preconditions.checkArgumentInstanceOf(series, D3LineSeriesData); + Preconditions.checkArgumentInstanceOfSVGElement(dataEl); + + d3.select(dataEl).raise(); + let isActive = !dataEl.classList.contains('active'); + let lineEls = dataEl.querySelectorAll('.plot-line'); + let symbolEls = dataEl.querySelectorAll('.plot-symbol'); + + d3.select(lineData.subView.svg.dataContainerEl) + .selectAll('.data-enter') + .classed('active', false); + + dataEl.classList.toggle('active', isActive); + D3Utils.linePlotSelection(series, lineEls, symbolEls, isActive); + + let selectionEvent = new CustomEvent( + 'plotSelection', + { detail: series }); + lineData.subView.svg.dataContainerEl.dispatchEvent(selectionEvent); + } + + /** + * @private + * Plot selection event listner. + * + * @param {D3LineData} lineData The line data + */ + _plotSelectionEventListener(lineData) { + Preconditions.checkArgumentInstanceOf(lineData, D3LineData); + + d3.select(lineData.subView.svg.dataContainerEl) + .selectAll('.data-enter') + .filter((/** @type {D3LineSeriesData */ series) => { + Preconditions.checkArgumentInstanceOf(series, D3LineSeriesData); + return series.lineOptions.selectable; + }) + .on('click', (/** @type {D3LineSeriesData} */ series) => { + Preconditions.checkStateInstanceOf(series, D3LineSeriesData); + this.selectLine(series.lineOptions.id, lineData); + }); + } + + /** + * @private + * Plot the symbols. + * + * @param {D3LineData} lineData The line data + * @param {Array<SVGElement>} seriesEnter The SVG elements + */ + _plotSymbol(lineData, seriesEnterEls) { + d3.selectAll(seriesEnterEls) + .selectAll('.plot-symbol') + .data((/** @type {D3LineSeriesData} */ series) => { + Preconditions.checkStateInstanceOf(series, D3LineSeriesData); + return lineData.toMarkerSeries(series); + }) + .enter() + .filter((/** @type {D3LineSeriesData */ series) => { + Preconditions.checkStateInstanceOf(series, D3LineSeriesData); + return series.data[0].x != null && series.data[0].y != null; + }) + .append('path') + .attr('class', 'plot-symbol') + .each(( + /** @type {D3LineSeriesData} */ series, + /** @type {Number} */ i, + /** @type {Array<SVGElement>} */ els) => { + Preconditions.checkStateInstanceOf(series, D3LineSeriesData); + Preconditions.checkStateInteger(i); + Preconditions.checkStateArrayInstanceOf(els, SVGElement); + this._plotSymbolSeries(lineData, series, els[i]); + }); + } + + /** + * @private + * Add a D3LineSeries symbols to the plot. + * + * @param {D3LineData} lineData The line data + * @param {D3LineSeriesData} series The series to add + * @param {SVGElement} dataEl The plot data element + */ + _plotSymbolSeries(lineData, series, dataEl) { + Preconditions.checkArgumentInstanceOf(lineData, D3LineData); + Preconditions.checkArgumentInstanceOf(series, D3LineSeriesData); + Preconditions.checkArgumentInstanceOfSVGElement(dataEl); + + let xScale = this._getCurrentXScale(lineData.subView); + let yScale = this._getCurrentYScale(lineData.subView); + + d3.select(dataEl) + .attr('d', (/** @type {D3LineSeriesData} */ series) => { + return series.d3Symbol(); + }) + .attr('transform', (/** @type {D3LineSeriesData} */ series) => { + Preconditions.checkStateInstanceOf(series, D3LineSeriesData); + + let x = this.axes.x(lineData, xScale, series.data[0]); + let y = this.axes.y(lineData, yScale, series.data[0]); + let rotate = series.lineOptions.d3SymbolRotate; + + return `translate(${x}, ${y}) rotate(${rotate})`; + }) + .attr('fill', (/** @type {D3LineSeriesData} */ series) => { + Preconditions.checkStateInstanceOf(series, D3LineSeriesData); + return series.lineOptions.markerColor; + }) + .attr('stroke', (/** @type {D3LineSeriesData} */ series) => { + Preconditions.checkStateInstanceOf(series, D3LineSeriesData); + return series.lineOptions.markerEdgeColor; + }) + .attr('stroke-width', (/** @type {D3LineSeriesData} */ series) => { + Preconditions.checkStateInstanceOf(series, D3LineSeriesData); + return series.lineOptions.markerEdgeWidth; + }) + .style('shape-rendering', 'geometricPrecision') + .style('cursor', 'pointer') + .on('mouseover', (/** @type {D3LineSeriesData} */ series) => { + Preconditions.checkStateInstanceOf(series, D3LineSeriesData); + this._onDataSymbolMouseover(lineData, series); + }) + .on('mouseout', () => { + this._onDataSymbolMouseout(lineData); + }); + } + + /** + * @private + * Update the plot + * + * @param {D3LineData} lineData The line data + * @param {String} xScale The current X scale + * @param {String} yScale The current Y scale + */ + _plotUpdate(lineData, xScale, yScale) { + this._plotUpdateHorizontalRefLine(lineData, xScale, yScale); + this._plotUpdateVerticalRefLine(lineData, xScale, yScale); + this._plotUpdateText(lineData, xScale, yScale); + + /* Update lines */ + let line = this.axes.line(lineData, xScale, yScale); + + d3.select(lineData.subView.svg.dataContainerEl) + .selectAll('.data-enter') + .selectAll('.plot-line') + .transition() + .duration(lineData.subView.options.translationDuration) + .attr('d', (/** @type {D3LineSeriesData} */ series) => { + Preconditions.checkStateInstanceOf(series, D3LineSeriesData); + return line(series.data); + }); + + /* Update symbols */ + d3.select(lineData.subView.svg.dataContainerEl) + .selectAll('.data-enter') + .selectAll('.plot-symbol') + .transition() + .duration(lineData.subView.options.translationDuration) + .attr('d', (/** @type {D3LineSeriesData} */ series) => { + Preconditions.checkStateInstanceOf(series, D3LineSeriesData); + return series.d3Symbol(); + }) + .attr('transform', (/** @type {D3LineSeriesData} */ series) => { + Preconditions.checkStateInstanceOf(series, D3LineSeriesData); + + let x = this.axes.x(lineData, xScale, series.data[0]); + let y = this.axes.y(lineData, yScale, series.data[0]); + let rotate = series.lineOptions.d3SymbolRotate; + + return `translate(${x}, ${y}) rotate(${rotate})`; + }); + } + + /** + * @private + * Update plot for single data element. + * + * @param {D3LineData} lineData The line data + * @param {D3LineSeriesData} series The line series + * @param {SVGElement} dataEl The plot data element + * @param {Number} translationDuration The duration for translation + */ + _plotUpdateDataEl(lineData, series, dataEl, xScale, yScale, translationDuration) { + Preconditions.checkArgumentInstanceOf(lineData, D3LineData); + Preconditions.checkArgumentInstanceOf(series, D3LineSeriesData); + Preconditions.checkArgumentInstanceOfSVGElement(dataEl); + + let line = this.axes.line(lineData, xScale, yScale); + + /* Update data */ + d3.select(dataEl).datum(series); + + /* Update line */ + d3.select(dataEl) + .selectAll('.plot-line') + .datum(series) + .transition() + .duration(translationDuration) + .attr('d', (/** @type {D3LineSeriesData*/ series) => { + Preconditions.checkStateInstanceOf(series, D3LineSeriesData); + return line(series.data); + }); + + let lineOptions = series.lineOptions; + let symbolSize = lineOptions.d3SymbolSize * lineOptions.selectionMultiplier; + let edgeWidth = lineOptions.markerEdgeWidth * lineOptions.selectionMultiplier; + + /* Update symbols */ + d3.select(dataEl) + .selectAll('.plot-symbol') + .data(() => { + let symbolLineData = D3LineData.builder() + .subView(lineData.subView) + .data( + series.xValues, + series.yValues, + series.lineOptions, + series.xStrings, + series.yStrings) + .build(); + + return symbolLineData.toMarkerSeries(series); + }) + .transition() + .duration(translationDuration) + .attr('d', (/** @type {D3LineSeriesData} */ series) => { + Preconditions.checkStateInstanceOf(series, D3LineSeriesData); + return series.d3Symbol.size(symbolSize)(); + }) + .attr('stroke-width', edgeWidth) + .attr('transform', (/** @type {D3LineSeriesData} */ series) => { + Preconditions.checkStateInstanceOf(series, D3LineSeriesData); + let x = this.axes.x(lineData, xScale, series.data[0]); + let y = this.axes.y(lineData, yScale, series.data[0]); + let rotate = series.lineOptions.d3SymbolRotate; + return `translate(${x}, ${y}) rotate(${rotate})`; + }); + } + + /** + * @private + * Update any horizontal reference lines. + * + * @param {D3LineData} lineData The upper or lower line data + * @param {String} xScale The X scale + * @param {String} yScale The Y scale + */ + _plotUpdateHorizontalRefLine(lineData, xScale, yScale) { + Preconditions.checkArgumentInstanceOf(lineData, D3LineData); + Preconditions.checkArgumentString(xScale); + Preconditions.checkArgumentString(yScale); + + d3.select(lineData.subView.svg.dataContainerEl) + .selectAll('.data-enter-ref-line.horizontal-ref-line') + .selectAll('.plot-line') + .each(( + /** @type {D3LineSeriesData */ series, + /** @type {Number} */ i, + /** @type {Array<SVGElement>} */ els) => { + Preconditions.checkArgumentInstanceOf(series, D3LineSeriesData); + + let refLineEl = els[i].parentNode; + Preconditions.checkArgumentInstanceOfSVGElement(refLineEl); + + let xDomain = this.axes._getXAxisScale(lineData, xScale).domain(); + + let updatedSeries = new D3LineSeriesData( + xDomain, + series.yValues, + series.lineOptions, + series.xStrings, + series.yStrings); + + let duration = lineData.subView.options.translationDuration; + this._plotUpdateDataEl( + lineData, + updatedSeries, + refLineEl, + xScale, + yScale, + duration); + }); + } + + /** + * @private + * Update any added text. + * + * @param {D3LineData} lineData The line data + * @param {String} xScale The X axis scale + * @param {String} yScale The Y axis scale + */ + _plotUpdateText(lineData, xScale, yScale) { + Preconditions.checkArgumentInstanceOf(lineData, D3LineData); + + d3.select(lineData.subView.svg.dataContainerEl) + .selectAll('.text-enter') + .selectAll('text') + .transition() + .duration(lineData.subView.options.translationDuration) + .attr('x', (/** @type {D3XYPair} */ xyPair) => { + Preconditions.checkStateInstanceOf(xyPair, D3XYPair); + return this.axes.x(lineData, xScale, xyPair); + }) + .attr('y', (/** @type {D3XYPair} */ xyPair) => { + Preconditions.checkStateInstanceOf(xyPair, D3XYPair); + return this.axes.y(lineData, yScale, xyPair); + }); + } + + /** + * @private + * Update any vertical reference lines. + * + * @param {D3LineData} lineData The upper or lower line data + * @param {String} xScale The X scale + * @param {String} yScale The Y scale + */ + _plotUpdateVerticalRefLine(lineData, xScale, yScale) { + Preconditions.checkArgumentInstanceOf(lineData, D3LineData); + Preconditions.checkArgumentString(xScale); + Preconditions.checkArgumentString(yScale); + + d3.select(lineData.subView.svg.dataContainerEl) + .selectAll('.data-enter-ref-line.vertical-ref-line') + .selectAll('.plot-line') + .each(( + /** @type {D3LineSeriesData */ series, + /** @type {Number} */ i, + /** @type {Array<SVGElement>} */ els) => { + Preconditions.checkArgumentInstanceOf(series, D3LineSeriesData); + + let yDomain = this.axes._getYAxisScale(lineData, yScale).domain(); + let refLineEl = els[i].parentNode; + Preconditions.checkArgumentInstanceOfSVGElement(refLineEl); + + let updatedSeries = new D3LineSeriesData( + series.xValues, + yDomain, + series.lineOptions, + series.xStrings, + series.yStrings); + + let duration = lineData.subView.options.translationDuration; + this._plotUpdateDataEl( + lineData, + updatedSeries, + refLineEl, + xScale, + yScale, + duration); + }); + } + + /** + * @private + * Reset all plot selections + * + * @param {D3LineData} lineData The line data + */ + _resetPlotSelection(lineData) { + d3.select(lineData.subView.svg.dataContainerEl) + .selectAll('.plot-line') + .attr('stroke-width', (/** @type {D3LineSeriesData} */ series) => { + Preconditions.checkStateInstanceOf(series, D3LineSeriesData); + return series.lineOptions.lineWidth; + }); + + d3.select(lineData.subView.svg.dataContainerEl) + .selectAll('.plot-symbol') + .attr('d', (/** @type {D3LineSeriesData} */ series) => { + Preconditions.checkStateInstanceOf(series, D3LineSeriesData); + return series.d3Symbol.size(series.lineOptions.d3SymbolSize)(); + }) + .attr('stroke-width', (/** @type {D3LineSeriesData} */ series) => { + Preconditions.checkStateInstanceOf(series, D3LineSeriesData); + return series.lineOptions.markerEdgeWidth; + }); + } + + /** + * @private + * Set the current line data. + * + * @param {D3LineData} lineData The line data to set + */ + _setLineData(lineData) { + if (lineData.subView.options.subViewType == 'lower') { + this.lowerLineData = lineData; + } else { + this.upperLineData = lineData; + } + } + +} diff --git a/webapp/apps/js/d3/D3SaveFigure.js b/webapp/apps/js/d3/D3SaveFigure.js new file mode 100644 index 000000000..e89282c4d --- /dev/null +++ b/webapp/apps/js/d3/D3SaveFigure.js @@ -0,0 +1,811 @@ + +import { D3BaseSubView } from './view/D3BaseSubView.js'; +import { D3BaseView } from './view/D3BaseView.js'; +import { D3SaveFigureOptions } from './options/D3SaveFigureOptions.js'; +import { D3XYPair } from './data/D3XYPair.js'; + +import NshmpError from '../error/NshmpError.js'; +import { Preconditions } from '../error/Preconditions.js'; + +/** + * @fileoverview Preview and/or save a view's figures to: + * - JPEG + * - PNG + * - SVG + * + * Use D3SaveFigure.save, D3SaveFigure.saveImageOnly, + * D3SaveFigure.preview, and D3SaveFigure.previewImageOnly + * to save and preview the figures. + * + * @class D3SaveFigure + * @author Brandon Clayton + */ +export class D3SaveFigure { + + /** + * @private + * Use either: + * - D3SaveFigure.save + * - D3SaveFigure.preview + * + * @param {D3BaseView} view The view + * @param {D3BaseSubView} subView The sub view + * @param {String} format The save format: 'jpeg' || 'png' || 'svg' + * @param {Boolean} saveFigure Whether to save the figure + * @param {Boolean} imageOnly Whether to save or preview just the image + * (page size is image size) + */ + constructor(view, subView, format, saveFigure, imageOnly) { + Preconditions.checkArgumentInstanceOf(view, D3BaseView); + Preconditions.checkArgumentInstanceOf(subView, D3BaseSubView); + Preconditions.checkArgument( + format == 'jpeg' || format == 'png' || format == 'svg', + `Format [${format}] not supported`); + Preconditions.checkArgumentBoolean(saveFigure); + + /** @type {D3BaseView} */ + this.view = view; + + /** @type {D3BaseSubView} */ + this.subView = subView; + + /** @type {Boolean} */ + this.imageOnly = imageOnly; + + /** @type {String} */ + this.saveFormat = format; + + /** @type {D3SaveFigureOptions} */ + this.options = this.subView.options.saveFigureOptions; + + /** @type {String} */ + this.filename = this.subView.options.filename; + + /** @type {Number} */ + this.baseDPI = 96; + + /** @type {Number} */ + this.printDPI = format == 'svg' ? this.baseDPI : this.options.dpi; + + let marginTopBasePx = this.options.marginTop * this.baseDPI; + let dpiRatio = this.printDPI / this.baseDPI; + + /** @type {Number} */ + this.pageHeightPxPrintDPI = this.imageOnly ? + ( this.subView.options.plotHeight + marginTopBasePx ) * dpiRatio: + this.options.pageHeight * this.printDPI; + + /** @type {Number} */ + this.pageWidthPxPrintDPI = this.imageOnly ? + this.subView.options.plotWidth * dpiRatio : + this.options.pageWidth * this.printDPI; + + /** @type {Number} */ + this.pageHeightPxBaseDPI = this.imageOnly ? + this.subView.options.plotHeight + marginTopBasePx : + this.options.pageHeight * this.baseDPI; + + /** @type {Number} */ + this.pageWidthPxBaseDPI = this.imageOnly ? + this.subView.options.plotWidth : + this.options.pageWidth * this.baseDPI; + + /** @type {SVGElement} */ + this.svgEl = this.subView.svg.svgEl.cloneNode(true); + + /** @type {SVGElement} */ + this.svgOuterPlotEl = this.svgEl.querySelector('.outer-plot'); + + /** @type {SVGElement} */ + this.svgOuterPlotFrameEl = this.svgOuterPlotEl.querySelector('.outer-frame'); + + /** @type {SVGElement} */ + this.svgInnerPlotEl = this.svgEl.querySelector('.inner-plot'); + + /** @type {SVGElement} */ + this.svgInnerPlotFrameEl = this.svgInnerPlotEl.querySelector('.inner-frame'); + + /** @type {Number} */ + this.footerHeightInch = 0.5; + + Preconditions.checkStateInstanceOfSVGElement(this.svgEl); + Preconditions.checkStateInstanceOfSVGElement(this.svgInnerPlotEl); + Preconditions.checkStateInstanceOfSVGElement(this.svgInnerPlotFrameEl); + Preconditions.checkStateInstanceOfSVGElement(this.svgOuterPlotEl); + Preconditions.checkStateInstanceOfSVGElement(this.svgOuterPlotFrameEl); + + /** @type {HTMLIFrameElement} */ + this.iFrameEl = document.createElement('iFrame'); + document.body.appendChild(this.iFrameEl); + Preconditions.checkStateInstanceOf(this.iFrameEl, HTMLIFrameElement); + this.iFrameEl.style.visibility = 'hidden'; + + /** @type {HTMLIFrameElement} */ + let iFrameBodyEl = this.iFrameEl.contentWindow.document.body; + Preconditions.checkStateInstanceOf( + iFrameBodyEl, + this.iFrameEl.contentWindow.HTMLElement); + iFrameBodyEl.appendChild(this.svgEl); + + if (saveFigure) { + this._saveFigure(); + } else { + this._previewFigure(); + } + + this.iFrameEl.remove(); + } + + /** + * Preview a view's figures in a new window with a metadata table and a + * footer with a date and the URL. + * + * @param {D3BaseView} view The view with the plot to preview + * @param {String} previewFormat The preview format: 'jpeg' || 'png' || 'svg' + */ + static preview(view, previewFormat) { + Preconditions.checkArgumentInstanceOf(view, D3BaseView); + Preconditions.checkArgumentString(previewFormat); + + D3SaveFigure._create( + view, + previewFormat.toLowerCase(), + false /* Save figure */, + false /* Image only */); + } + + /** + * Preview a view's figures in a new window as just the images. + * + * @param {D3BaseView} view The view with the plot to preview + * @param {String} previewFormat The preview format: 'jpeg' || 'png' || 'svg' + */ + static previewImageOnly(view, previewFormat) { + Preconditions.checkArgumentInstanceOf(view, D3BaseView); + Preconditions.checkArgumentString(previewFormat); + + D3SaveFigure._create( + view, + previewFormat.toLowerCase(), + false /* Save figure */, + true /* Image only */); + } + + /** + * Save a view's figures in a specific format with a metadata table + * and a footer with the date and the URL. + * + * @param {D3BaseView} view The view with the plots + * @param {String} saveFormat The save format: 'jpeg' || 'png' || 'svg' + */ + static save(view, saveFormat) { + Preconditions.checkArgumentInstanceOf(view, D3BaseView); + Preconditions.checkArgumentString(saveFormat); + + D3SaveFigure._create( + view, + saveFormat.toLowerCase(), + true /* Save figure */, + false /* Image only */); + } + + /** + * Save a view's figures in a specific format as just the images. + * + * @param {D3BaseView} view The view with the plots + * @param {String} saveFormat The save format: 'jpeg' || 'png' || 'svg' + */ + static saveImageOnly(view, saveFormat) { + Preconditions.checkArgumentInstanceOf(view, D3BaseView); + Preconditions.checkArgumentString(saveFormat); + + D3SaveFigure._create( + view, + saveFormat.toLowerCase(), + true /* Save figure */, + true /* Image only */); + } + + /** + * @private + * Save or preview both the upper and lower sub view's figures. + * + * @param {D3BaseView} view The view + * @param {String} format The save/preview format: 'jpeg' || 'png' || 'svg' + * @param {Boolean} saveFigure Whether to save the figure + * @param {Boolean} imageOnly Whether to save or preview the image only + * (page size is image size) + */ + static _create(view, format, saveFigure, imageOnly) { + Preconditions.checkArgumentInstanceOf(view, D3BaseView); + Preconditions.checkArgumentString(format); + Preconditions.checkArgumentBoolean(saveFigure); + Preconditions.checkArgumentBoolean(imageOnly); + + format = format.toLowerCase(); + + if (view.addLowerSubView) { + new D3SaveFigure(view, view.lowerSubView, format, saveFigure, imageOnly); + } + + new D3SaveFigure(view, view.upperSubView, format, saveFigure, imageOnly); + } + + /** + * @private + * Add the URL and date to the bottom of the plot. + */ + _addFooter() { + let footerText = [ + window.location.href.toString(), + new Date().toLocaleString(), + ]; + + let footerD3 = d3.select(this.svgEl) + .append('g') + .attr('class', 'print-footer') + .style('font-size', `${this.options.footerFontSize}px`); + + footerD3.selectAll('text') + .data(footerText.reverse()) + .enter() + .append('text') + .attr('y', (/** @type {String} */ text, /** @type {Number} */ i) => { + Preconditions.checkStateString(text); + Preconditions.checkStateInteger(i); + return -this.options.footerLineBreak * i; + }) + .text((/** @type {String} */ text) => { + Preconditions.checkStateString(text); + return text; + }) + + let footerEl = footerD3.node(); + + this._fitFooter(footerEl); + } + + /** + * @private + * Add the metadata table to the page. + * + * @param {D3XYPair} plotTranslate The X and Y translate + */ + _addMetadataTable(plotTranslate) { + if (!this.options.addMetadata) return; + Preconditions.checkArgument(plotTranslate, D3XYPair); + + let tableInfo = this._updateMetadataForTable(); + let maxColumnValues = this.options.metadataMaxColumnValues; + + let foreignD3 = d3.select(this.svgEl) + .select('g') + .append('foreignObject') + .style('overflow', 'visible'); + + let tableD3 = foreignD3.append('xhtml:table') + .attr('xmlns', 'http://www.w3.org/1999/xhtml') + .style('font-family', '"Helvetica Neue",Helvetica,Arial,sans-serif') + .style('font-size', `${this.options.metadataFontSize}px`) + .style('border-collapse', 'collapse') + .style('background', 'white'); + + for (let data of tableInfo.metadataSet) { + let tableRowD3 = tableD3.append('tr'); + + for (let datum of data) { + let key = datum[0]; + let values = datum[1]; + let rowSpan = values.length > 1 ? tableInfo.rows : 1; + + tableRowD3.append('th') + .attr('nowrap', true) + .attr('valign', 'top') + .attr('rowspan', rowSpan) + .style('padding', '4px 4px 4px 8px') + .style('text-align', 'right') + .html(key); + + let innerTableD3 = tableRowD3.append('td') + .attr('rowspan', rowSpan) + .attr('valign', 'top') + .style('padding', '4px 4px') + .append('table') + .style('font-size', `${this.options.metadataFontSize}px`) + .style('border-collapse', 'collapse') + .style('background', 'white'); + + let nValues = values.length; + if (nValues > maxColumnValues) { + values = values.slice(0, maxColumnValues); + let nMore = nValues - maxColumnValues + values.push(`... and ${nMore} others ...`); + } + + for (let value of values) { + value = Number.isNaN(value) || value == null ? '--' : value; + innerTableD3.append('tr') + .append('td') + .attr('nowrap', true) + .style('text-align', 'left') + .text(value); + } + } + } + + let tableEl = tableD3.node(); + Preconditions.checkStateInstanceOf( + tableEl, + this.iFrameEl.contentWindow.HTMLElement); + + let tableDim = tableEl.getBoundingClientRect(); + let tableWidthInPx = tableDim.width; + let tableHeightInPx = tableDim.height; + + this._fitMetadataTable( + foreignD3.node(), + tableHeightInPx, + tableWidthInPx, + plotTranslate); + } + + /** + * @private + * Add the plot title. + * + * @param {D3XYPair} plotTranslate The X and Y translate + */ + _addPlotTitle(plotTranslate) { + if (!this.options.addTitle) return; + + Preconditions.checkArgumentInstanceOf(plotTranslate, D3XYPair); + let titleTranslate = this._titlePosition(plotTranslate); + + d3.select(this.svgInnerPlotEl) + .append('text') + .attr('class', 'plot-title') + .attr('x', titleTranslate.x) + .attr('y', titleTranslate.y) + .attr('text-anchor', () => { + return this.options.titleLocation == 'left' ? 'start' : 'middle'; + }) + .attr('alignment-baseline', 'middle') + .text(this.view.getTitle()); + } + + /** + * @private + * Create the canvas element. + * + * @returns {HTMLCanvasElement} The canvas element + */ + _createCanvas() { + let canvasDivD3 = d3.select('body') + .append('div') + .attr('class', 'svg-to-canvas hidden'); + + let canvasD3 = canvasDivD3.append('canvas') + .attr('height', this.pageHeightPxPrintDPI) + .attr('width', this.pageWidthPxPrintDPI) + .style('height', `${this.options.pageHeight}in`) + .style('width', `${this.options.pageWidth}in`); + + let canvasEl = canvasD3.node(); + Preconditions.checkStateInstanceOf(canvasEl, HTMLCanvasElement); + + canvasDivD3.remove(); + + return canvasEl; + } + + /** + * @private + * Create a new window to preview the image. + * + * @param {String} imageSrc The image source + */ + _createPreviewWindow(imageSrc) { + Preconditions.checkArgumentString(imageSrc); + + let win = window.open('', '_blank') + win.document.title = this.filename; + win.focus(); + + d3.select(win.document.body) + .style('margin', '0 5em') + .style('background', 'black') + .append('img') + .attr('src', imageSrc) + .style('height', 'auto') + .style('width', 'auto') + .style('max-height', '-webkit-fill-available') + .style('max-width', '100%') + .style('display', 'block') + .style('margin', 'auto') + .style('padding', '2em 0') + .style('top', '50%') + .style('transform', 'translate(0, -50%)') + .style('position', 'relative'); + + this._createPreviewDownloadButton(win, imageSrc); + } + + /** + * @private + * Add a download button to the preview window. + * + * @param {Window} win The preview window + * @param {String} imageSrc The image src to download + */ + _createPreviewDownloadButton(win, imageSrc) { + Preconditions.checkStateObjectProperty(win, 'Window'); + Preconditions.checkArgumentInstanceOf(win, win.Window); + Preconditions.checkArgumentString(imageSrc); + + d3.select(win.document.body) + .append('button') + .style('background', 'white') + .style('position', 'absolute') + .style('bottom', '0.5em') + .style('left', '1em') + .style('padding', '0.5em') + .style('font-size', '1em') + .style('border-radius', '4px') + .attr('href', imageSrc) + .text('Download') + .on('click', () => { + new D3SaveFigure( + this.view, + this.subView, + this.saveFormat, + true /* Save figure */, + this.imageOnly); + }); + } + + /** + * @private + * Create the image source string + */ + _createSVGImageSource() { + return `data:image/svg+xml;base64,` + + `${btoa(unescape(encodeURIComponent(this.svgEl.outerHTML)))}`; + } + + /** + * @private + * Draw the canvas + * + * @param {HTMLCanvasElement} canvasEl The canvas element + * @param {HTMLImageElement} svgImage The svg image to draw + */ + _drawCanvas(canvasEl, svgImage) { + Preconditions.checkArgumentInstanceOf(canvasEl, HTMLCanvasElement); + Preconditions.checkArgumentInstanceOf(svgImage, HTMLImageElement); + + let dpiScale = this.printDPI / this.baseDPI; + let canvasContext = canvasEl.getContext('2d'); + + canvasContext.scale(dpiScale, dpiScale); + + canvasContext.fillStyle = 'white'; + + canvasContext.fillRect(0, 0, + this.pageWidthPxPrintDPI, this.pageHeightPxPrintDPI); + + canvasContext.drawImage(svgImage, 0, 0, + this.pageWidthPxPrintDPI, this.pageHeightPxPrintDPI); + } + + /** + * @private + * Scale the footer such that it will fit on the page. + * + * @param {SVGElement} footerEl The footer element to scale + */ + _fitFooter(footerEl) { + Preconditions.checkArgumentInstanceOf( + footerEl, + this.iFrameEl.contentWindow.SVGElement); + + let footerDim = footerEl.getBoundingClientRect(); + let footerWidthInInch = footerDim.width / this.baseDPI; + let footerHeightInInch = footerDim.height / this.baseDPI; + let footerPaddingInInch = this.options.footerPadding / this.baseDPI; + + let pageWidth = this.options.pageWidth - footerPaddingInInch * 2; + + let widthScale = footerWidthInInch > pageWidth ? + pageWidth / footerWidthInInch : 1; + + let footerHeight = this.footerHeightInch - footerPaddingInInch; + let heightScale = footerHeightInInch > footerHeight ? + footerHeight / footerHeightInInch : 1; + + let footerScale = Math.min(heightScale, widthScale); + + let footerPadding = this.options.footerPadding; + let footerTransform = `translate(${footerPadding}, ` + + `${this.pageHeightPxBaseDPI - footerPadding}) scale(${footerScale})`; + + d3.select(footerEl).attr('transform', footerTransform); + } + + /** + * @private + * Scale the metadata table to fit under the figure. + * + * @param {SVGElement} foreignObjectEl The Foriegn Object SVG element + * @param {Number} tableHeightInPx The table height in pixels + * @param {Number} tableWidthInPx The table width in pixels + * @param {D3XYPair} plotTranslate The plot margins + */ + _fitMetadataTable(foreignObjectEl, tableHeightInPx, tableWidthInPx, plotTranslate) { + Preconditions.checkArgumentInstanceOf( + foreignObjectEl, + this.iFrameEl.contentWindow.SVGElement); + Preconditions.checkArgumentNumber(tableHeightInPx); + Preconditions.checkArgumentNumber(tableWidthInPx); + Preconditions.checkArgumentInstanceOf(plotTranslate, D3XYPair); + + let svgHeightInInch = this.subView.options.plotHeight / this.baseDPI; + let tableMarginTopInPx = this.options.metadataMarginTop * this.baseDPI; + + let tableHeightInInch = tableHeightInPx / this.baseDPI; + let tableWidthInInch = tableWidthInPx / this.baseDPI; + + let availableHeight = this.options.pageHeight - + this.options.marginTop - + svgHeightInInch - + this.options.metadataMarginTop - + this.footerHeightInInch; + + let heightScale = tableHeightInInch > availableHeight ? + availableHeight / tableHeightInInch : 1; + + let widthScale = tableWidthInInch > this.options.pageWidth ? + this.options.pageWidth / tableWidthInInch : 1; + + let tableScale = Math.min(heightScale, widthScale); + + let centerTableX = - plotTranslate.x + ( this.pageWidthPxBaseDPI - + ( tableWidthInPx * tableScale )) / 2; + + d3.select(foreignObjectEl) + .attr('transform', `scale(${tableScale})`) + .attr('height', tableHeightInPx) + .attr('width', tableWidthInPx) + .attr('y', (this.subView.options.plotHeight + tableMarginTopInPx) / tableScale) + .attr('x', centerTableX / tableScale); + } + + /** + * @private + * Convert the metadata Map into a set of arrays to be used to create + * the table. + * + * @param {Map<String, Array<String>} metadata The metadata Map + * @param {Number} rows The number of rows the table will have + * @param {Number} maxColumns The number of key-value columns the table + * will have + * @param {Boolean} expandColumn Whether the first item in the metadata + * Map will expand all rows + * @return {Set<Array<String, Array<String>>>} The metadata set of arrays + */ + _metadataToSet(metadata, rows, maxColumns, expandColumn) { + Preconditions.checkArgumentInstanceOfMap(metadata); + Preconditions.checkArgumentInteger(rows); + Preconditions.checkArgumentInteger(maxColumns); + Preconditions.checkArgumentBoolean(expandColumn); + + let metadataSet = new Set(); + let tmpArray = Array.from(metadata); + + let iStart = 0; + let iEnd = 0; + for (let jl = 0; jl < rows; jl++) { + iStart = iEnd; + iEnd = iStart + maxColumns; + if (jl == 0 && expandColumn) iEnd++; + metadataSet.add(tmpArray.slice(iStart, iEnd)); + } + + return metadataSet; + } + + /** + * @private + * Preview the figure + */ + _previewFigure() { + let svgImage = new Image(); + this._updateSVGElement(); + + d3.select(this.svgEl) + .attr('height', this.pageHeightPxBaseDPI * this.printDPI) + .attr('width', this.pageWidthPxBaseDPI * this.printDPI) + .style('background', 'white'); + + let svgImageSrc = this._createSVGImageSource(); + let canvasEl = this._createCanvas(); + + svgImage.onload = () => { + this._drawCanvas(canvasEl, svgImage); + + try { + switch (this.saveFormat) { + /* SVG format */ + case 'svg': + this._createPreviewWindow(svgImageSrc); + break; + /* JPEG or PNG format */ + case 'png': + case 'jpeg': + canvasEl.toBlob((blob) => { + let canvasImageSrc = URL.createObjectURL(blob); + this._createPreviewWindow(canvasImageSrc); + }, `image/${this.saveFormat}`, 1.0); + break; + default: + throw new NshmpError(`Plot format [${this.saveFormat}] not supported`); + } + } catch (err) { + d3.select(this.iFrameEl).remove(); + throw new NshmpError(err); + } + }; + + svgImage.setAttribute('src', svgImageSrc); + } + + /** + * @private + * Save the figure + */ + _saveFigure() { + let svgImage = new Image(); + this._updateSVGElement(); + let svgImageSrc = this._createSVGImageSource(); + let canvasEl = this._createCanvas(); + + svgImage.onload = () => { + this._drawCanvas(canvasEl, svgImage); + + let aEl = document.createElement('a'); + aEl.download = this.filename; + + try { + switch (this.saveFormat) { + /* SVG format */ + case 'svg': + aEl.setAttribute('href', svgImage.getAttribute('src')); + aEl.click(); + break; + /* JPEG or PNG format */ + case 'png': + case 'jpeg': + canvasEl.toBlob((blob) => { + aEl.setAttribute('href', URL.createObjectURL(blob)); + aEl.click(); + }, `image/${this.saveFormat}`, 1.0); + break; + default: + throw new NshmpError(`Plot format [${this.saveFormat}] not supported`); + } + } catch (err) { + d3.select(this.iFrameEl).remove(); + throw new NshmpError(err); + } + + }; + + svgImage.setAttribute('src', svgImageSrc); + } + + /** + * @private + * Calculate the translation needed to center the plot in the page. + * + * @returns {D3XYPair} The translations X and Y + */ + _svgTranslate() { + let innerPlotDim = this.svgInnerPlotEl.getBoundingClientRect(); + let innerPlotWidth = innerPlotDim.width; + let marginLeftInPx = ( this.pageWidthPxBaseDPI - innerPlotWidth ) / 2; + let marginTopInPx = this.options.marginTop * this.baseDPI; + + return new D3XYPair(marginLeftInPx, marginTopInPx); + } + + /** + * @private + * Calculate the translation needed for the title. + * + * @param {D3XYPair} plotTranslate The plot translations + * @returns {D3XYPair} The title translation X and Y + */ + _titlePosition(plotTranslate) { + Preconditions.checkArgumentInstanceOf(plotTranslate, D3XYPair); + + let innerPlotWidth = this.svgInnerPlotFrameEl.getBoundingClientRect().width; + let titleX = this.options.titleLocation == 'left' ? 0 : innerPlotWidth / 2; + let titleY = - plotTranslate.y / 2; + + return new D3XYPair(titleX, titleY); + } + + /** + * @private + * Update the metadata Map to put the key with a values array + * greater than 1 in the first position and convert Map to + * a Set. + * + * @typedef {Object} MetadataTableInfo - Parameters to make table. + * @property {Set<Array<String, Array<String>>>} metadataSet + * Set of metadata + * @property {Number} rows The number of rows in table + * + * @return {MetadataTableInfo} The table parameters + */ + _updateMetadataForTable() { + let metadata = this.view.getMetadata(); + + let maxKey; + let maxValue = []; + + for (let [key, value] of metadata) { + if (value.length > maxValue.length) { + maxKey = key; + maxValue = value; + } + } + + metadata.delete(maxKey); + let reMapped = new Map(); + reMapped.set(maxKey, maxValue); + for (let [key, value] of metadata) { + reMapped.set(key, value); + } + + let expandColumn = maxValue.length > 1; + let maxColumns = expandColumn ? this.options.metadataColumns - 1 : + this.options.metadataColumns; + + let rows = Math.ceil( reMapped.size / maxColumns ); + let metadataSet = this._metadataToSet( + reMapped, + rows, + maxColumns, + expandColumn); + + return { + metadataSet: metadataSet, + rows: rows, + }; + } + + /** + * @private + * Update the SVG element + */ + _updateSVGElement() { + d3.select(this.svgEl) + .attr('viewBox', `0 0 ${this.pageWidthPxPrintDPI} ${this.pageHeightPxPrintDPI}`) + .style('font-family', '"helvetica neue",helvetica,arial,sans-serif') + .attr('height', this.pageHeightPxPrintDPI) + .attr('width', this.pageWidthPxPrintDPI); + + let translate = this._svgTranslate(); + + this.svgOuterPlotEl.setAttribute( + 'transform', + `translate(${translate.x}, ${translate.y})`); + + this._addPlotTitle(translate); + if (!this.imageOnly) { + this._addFooter(); + this._addMetadataTable(translate); + } + } + +} diff --git a/webapp/apps/js/d3/D3SaveLineData.js b/webapp/apps/js/d3/D3SaveLineData.js new file mode 100644 index 000000000..33cad5bc0 --- /dev/null +++ b/webapp/apps/js/d3/D3SaveLineData.js @@ -0,0 +1,79 @@ + +import { D3LineData } from './data/D3LineData.js'; + +import { Preconditions } from '../error/Preconditions.js'; + +/** + * @fileoverview Save D3LineData to a CSV file + * + * Use D3SaveLineData.saveCSV + * + * @class D3SaveLineData + * @author Brandon Clayton + */ +export class D3SaveLineData { + + /** + * @private + * Use D3SaveLineData.saveCSV + * + * @param {String} fileFormat The file format: 'csv' + * @param {...D3LineData} lineDatas The D3LineData(s) + */ + constructor(fileFormat, ...lineDatas) { + Preconditions.checkArgument( + fileFormat == 'csv', + `File format [${fileFormat}] not supported`); + Preconditions.checkArgumentArrayInstanceOf(lineDatas, D3LineData); + + let fileData = []; + + for (let lineData of lineDatas) { + let subViewOptions = lineData.subView.options; + + for (let series of lineData.series) { + fileData.push([ subViewOptions.lineLabel, series.lineOptions.label ]); + let xValues = []; + let yValues = []; + + for (let xyPair of series.data) { + let x = subViewOptions.xValueToExponent ? + xyPair.x.toExponential(subViewOptions.xExponentFractionDigits) : + xyPair.x; + + let y = subViewOptions.yValueToExponent ? + xyPair.y.toExponential(subViewOptions.yExponentFractionDigits) : + xyPair.y; + + xValues.push(xyPair.xString || x); + yValues.push(xyPair.yString || y); + } + + fileData.push([ subViewOptions.xLabel, xValues.join(',') ]); + fileData.push([ subViewOptions.yLabel, yValues.join(',') ]); + fileData.push(''); + } + + let file = new Blob( + [ fileData.join('\n') ], + { type: `text/${fileFormat}` }); + + let aEl = document.createElement('a'); + aEl.download = `${subViewOptions.filename}.${fileFormat}`; + aEl.href = URL.createObjectURL(file); + aEl.click(); + aEl.remove(); + } + } + + /** + * Save D3LineData(s) to CSV files. + * + * @param {...D3LineData} lineDatas The data + */ + static saveCSV(...lineDatas) { + Preconditions.checkArgumentArrayInstanceOf(lineDatas, D3LineData); + new D3SaveLineData('csv', ...lineDatas); + } + +} diff --git a/webapp/apps/js/d3/D3Tooltip.js b/webapp/apps/js/d3/D3Tooltip.js new file mode 100644 index 000000000..13186fca9 --- /dev/null +++ b/webapp/apps/js/d3/D3Tooltip.js @@ -0,0 +1,139 @@ + +import { D3BaseSubView } from './view/D3BaseSubView.js'; + +import { Preconditions } from '../error/Preconditions.js'; + +/** + * @fileoverview Create a tooltip on a D3BaseSubView. + * + * The tooltip is placed automatically to fit in the plot window + * so it will not go past the edge of the plot. + * + * @class D3Tooltip + * @author Brandon Clayton + */ +export class D3Tooltip { + + constructor() {} + + /** + * Create a tooltip on a sub view at a desired X and Y coordinate. + * + * @param {D3BaseSubView} subView The sub view to place the tooltip + * @param {Array<String>} tooltipText The array of text to display + * @param {Number} tooltipX The X coordinate in plot units to + * place the tooltip + * @param {Number} tooltipY The Y coordinate in plot units to + * place the tooltip + */ + create(subView, tooltipText, tooltipX, tooltipY) { + Preconditions.checkArgumentInstanceOf(subView, D3BaseSubView); + Preconditions.checkArgumentArrayOf(tooltipText, 'string'); + Preconditions.checkArgumentNumber(tooltipX); + Preconditions.checkArgumentNumber(tooltipY); + + this._createTooltipTable(subView, tooltipText); + this._setTooltipLocation(subView, tooltipX, tooltipY); + } + + /** + * Remove any tooltip from a sub view + * + * @param {D3BaseSubView} subView The sub view to remove the tooltip + */ + remove(subView) { + Preconditions.checkArgumentInstanceOf(subView, D3BaseSubView); + + d3.select(subView.svg.tooltipTableEl) + .selectAll('*') + .remove(); + + d3.select(subView.svg.tooltipForeignObjectEl) + .attr('height', 0) + .attr('width', 0); + } + + /** + * @private + * Create the tooltip table with tooltip text. + * + * @param {D3BaseSubView} subView The sub view to add a tooltip + * @param {Array<String>} tooltipText The tooltip text + */ + _createTooltipTable(subView, tooltipText) { + Preconditions.checkArgumentInstanceOf(subView, D3BaseSubView); + Preconditions.checkArgumentArrayOf(tooltipText, 'string'); + + let options = subView.options.tooltipOptions; + let padding = `${options.paddingTop}px ${options.paddingRight}px ` + + `${options.paddingBottom}px ${options.paddingLeft}px`; + let borderStyle = `${options.borderLineWidth}px ${options.borderStyle} ` + + `${options.borderColor}`; + + d3.select(subView.svg.tooltipForeignObjectEl) + .attr('height', '100%') + .attr('width', '100%') + + let tableD3 = d3.select(subView.svg.tooltipTableEl) + .style('font-size', `${options.fontSize}px`) + .style('border-collapse', 'separate') + .style('border', borderStyle) + .style('border-radius', `${options.borderRadius}px`) + .style('box-shadow', '0 1px 1px rgba(0, 0, 0, 0.05)') + .style('padding', padding) + .style('background', options.backgroundColor); + + tableD3.selectAll('tr') + .data(tooltipText) + .enter() + .append('tr') + .append('td') + .attr('nowrap', true) + .text((/** @type {String} */ text) => { return text; }); + + d3.select(subView.svg.tooltipEl).raise(); + } + + /** + * @private + * Set the tooltip location, making sure it does not go over the + * edge of the plot. + * + * @param {D3BaseSubView} subView The sub view + * @param {Number} tooltipX The X location of tooltip + * @param {Number} tooltipY The Y location of tooltip + */ + _setTooltipLocation(subView, tooltipX, tooltipY) { + Preconditions.checkArgumentInstanceOf(subView, D3BaseSubView); + Preconditions.checkArgumentNumber(tooltipX); + Preconditions.checkArgumentNumber(tooltipY); + + let foreignObjectEl = subView.svg.tooltipForeignObjectEl; + let tableEl = subView.svg.tooltipTableEl; + + let tooltipHeight = parseFloat(d3.select(tableEl).style('height')); + let tooltipWidth = parseFloat(d3.select(tableEl).style('width')); + + let plotHeight = subView.plotHeight; + let plotWidth = subView.plotWidth; + + let offsetX = subView.options.tooltipOptions.offsetX; + let offsetY = subView.options.tooltipOptions.offsetY; + + let availableWidth = plotWidth - tooltipX; + let xTranslate = ( tooltipWidth + offsetX ) > availableWidth ? + tooltipX - tooltipWidth - offsetX + availableWidth : + tooltipX + offsetX; + + let availableHeight = plotHeight - tooltipY; + let yTranslate = ( tooltipHeight + offsetY ) > availableHeight ? + tooltipY - tooltipHeight - offsetY : + tooltipY + offsetY; + + d3.select(foreignObjectEl) + .attr('height', tooltipHeight) + .attr('width', tooltipWidth) + .attr('transform', `translate(${xTranslate}, ${yTranslate})`); + } + +} diff --git a/webapp/apps/js/d3/D3Utils.js b/webapp/apps/js/d3/D3Utils.js new file mode 100644 index 000000000..31f0d91b6 --- /dev/null +++ b/webapp/apps/js/d3/D3Utils.js @@ -0,0 +1,71 @@ + +import { D3LineData } from './data/D3LineData.js'; +import { D3LineSeriesData } from './data/D3LineSeriesData.js'; + +import { Preconditions } from '../error/Preconditions.js'; + +/** + * @fileoverview D3 Utilities + * + * @class D3Utils + * @author Brandon Clayton + */ +export class D3Utils { + + /** @private */ + constructor() {} + + /** + * Check an array to see if if each value is a number or null. + * + * @param {Array<Number | Null>} values Values to check + */ + static checkArrayIsNumberOrNull(values) { + Preconditions.checkArgumentArray(values); + + for (let val of values) { + Preconditions.checkState( + typeof val == 'number' || val === null, + `Value [${val}] must be a number or null`); + } + } + + /** + * Increase/decrease the line width, marker size, and marker edge width + * of all lines and symbols. + * + * @param {D3LineSeriesData} series The data series + * @param {NodeList} lineEls The SVG elements of the lines + * @param {NodeList} symbolEls The SVG elements of the symbols + * @param {Boolean} isActive Whether the line/symbols have been selected + * or deselected + */ + static linePlotSelection(series, lineEls, symbolEls, isActive) { + Preconditions.checkArgumentInstanceOf(series, D3LineSeriesData); + Preconditions.checkArgumentInstanceOf(lineEls, NodeList); + Preconditions.checkStateInstanceOf(symbolEls, NodeList); + Preconditions.checkArgumentBoolean(isActive); + + let options = series.lineOptions; + + let lineWidth = isActive ? + options.lineWidth * options.selectionMultiplier : + options.lineWidth; + + let symbolSize = isActive ? + options.d3SymbolSize * options.selectionMultiplier : + options.d3SymbolSize; + + let edgeWidth = isActive ? + options.markerEdgeWidth * options.selectionMultiplier : + options.markerEdgeWidth; + + d3.selectAll(lineEls) + .attr('stroke-width', lineWidth); + + d3.selectAll(symbolEls) + .attr('d', series.d3Symbol.size(symbolSize)()) + .attr('stroke-width', edgeWidth); + } + +} diff --git a/webapp/apps/js/d3/axes/D3LineAxes.js b/webapp/apps/js/d3/axes/D3LineAxes.js new file mode 100644 index 000000000..b09401935 --- /dev/null +++ b/webapp/apps/js/d3/axes/D3LineAxes.js @@ -0,0 +1,418 @@ + +import { D3LineData } from '../data/D3LineData.js'; +import { D3LineSubView } from '../view/D3LineSubView.js'; +import { D3LineView } from '../view/D3LineView.js'; +import { D3XYPair } from '../data/D3XYPair.js'; + +import { Preconditions } from '../../error/Preconditions.js'; + +/** + * @fileoverview Add X and Y axes, axes labels, and gridlines to + * a D3LinePlot. + * + * @class D3LineAxes + * @author Brandon Clayton + */ +export class D3LineAxes { + + /** + * New instance of D3LineAxes + * + * @param {D3LineView} view The line view + */ + constructor(view) { + Preconditions.checkArgumentInstanceOf(view, D3LineView); + + /** @type {D3LineView} */ + this.view = view; + } + + /** + * Add a log or linear X axis to a D3LineSubView with + * a X label and grid lines. + * + * @param {D3LineData} lineData The line data + * @param {String} scale The scale: 'log' || 'linear' + */ + createXAxis(lineData, scale) { + Preconditions.checkArgumentInstanceOf(lineData, D3LineData); + this._checkScale(scale); + + let subView = lineData.subView; + let translate = subView.options.xAxisLocation == 'top' ? 0 : + subView.plotHeight; + + d3.select(subView.svg.xAxisEl) + .attr('transform', `translate(-0.5, ${translate - 0.5})`) + .style(subView.options.tickFontSize); + + d3.select(subView.svg.xTickMarksEl) + .call(this._getXAxis(lineData, scale)) + .each(() => { + this._setExponentTickMarks(subView, subView.svg.xTickMarksEl, scale); + }); + + this.createXGridLines(lineData, scale); + this._addXLabel(subView); + } + + /** + * Add log or linear X grid lines to a D3LineSubView. + * + * @param {D3LineData} lineData The line data + * @param {String} scale The scale: 'log' || 'linear' + */ + createXGridLines(lineData, scale) { + Preconditions.checkArgumentInstanceOf(lineData, D3LineData); + this._checkScale(scale); + + if (!this.view.viewHeader.gridLinesCheckEl.getAttribute('checked')) return; + + let subView = lineData.subView; + this.removeXGridLines(subView); + + let xGridLines = this._getXAxis(lineData, scale); + xGridLines.tickFormat('') + .tickSize(-subView.plotHeight); + + let xGridD3 = d3.select(subView.svg.xGridLinesEl) + .attr('transform', d3.select(subView.svg.xAxisEl).attr('transform')) + .call(xGridLines); + + xGridD3.selectAll('*') + .attr('stroke', subView.options.gridLineColor) + .attr('stroke-width', subView.options.gridLineWidth); + + xGridD3.selectAll('text') + .remove(); + } + + /** + * Add a log or linear Y axis to a D3LineSubView with + * a Y label and grid lines. + * + * @param {D3LineData} lineData The line data + * @param {String} scale The scale: 'log' || 'linear' + */ + createYAxis(lineData, scale) { + Preconditions.checkArgumentInstanceOf(lineData, D3LineData); + this._checkScale(scale); + + let subView = lineData.subView; + let translate = subView.options.yAxisLocation == 'right' ? + subView.plotWidth : 0; + + d3.select(subView.svg.yAxisEl) + .attr('transform', `translate(${translate - 0.5}, -0.5)`) + .style(subView.options.tickFontSize); + + d3.select(subView.svg.yTickMarksEl) + .call(this._getYAxis(lineData, scale)) + .each(() => { + this._setExponentTickMarks(subView, subView.svg.yTickMarksEl, scale); + }); + + this.createYGridLines(lineData, scale); + this._addYLabel(subView); + } + + /** + * Add log or linear Y grid lines to a D3LineSubView. + * + * @param {D3LineData} lineData The line data + * @param {String} scale The scale: 'log' || 'linear' + */ + createYGridLines(lineData, scale) { + Preconditions.checkArgumentInstanceOf(lineData, D3LineData); + this._checkScale(scale); + + if (!this.view.viewHeader.gridLinesCheckEl.getAttribute('checked')) return; + + let subView = lineData.subView; + this.removeYGridLines(subView); + + let yGridLines = this._getYAxis(lineData, scale); + yGridLines.tickFormat('') + .tickSize(-subView.plotWidth); + + let yGridD3 = d3.select(subView.svg.yGridLinesEl) + .attr('transform', d3.select(subView.svg.yAxisEl).attr('transform')) + .call(yGridLines); + + yGridD3.selectAll('*') + .attr('stroke', subView.options.gridLineColor) + .attr('stroke-width', subView.options.gridLineWidth); + + yGridD3.selectAll('text') + .remove(); + } + + /** + * Returns a D3 line generator. + * + * @param {D3LineData} lineData The D3LineData + * @param {String} xScale The X axis scale + * @param {String} yScale The Y axis scale + * @returns {Function} The line generator + */ + line(lineData, xScale, yScale) { + Preconditions.checkArgumentInstanceOf(lineData, D3LineData); + this._checkScale(xScale); + this._checkScale(yScale); + + let line = d3.line() + .defined((/** @type {D3XYPair} */ d) => { + return d.y != null; + }) + .x((/** @type {D3XYPair} */ d) => { + return this.x(lineData, xScale, d); + }) + .y((/** @type {D3XYPair} */ d) => { + return this.y(lineData, yScale, d); + }) + + return line; + } + + /** + * Remove the X axis grid lines. + * + * @param {D3LineSubView} subView The sub view to remove them from + */ + removeXGridLines(subView) { + Preconditions.checkArgumentInstanceOf(subView, D3LineSubView); + + d3.select(subView.svg.xGridLinesEl) + .selectAll('*') + .remove(); + } + + /** + * Remove the Y axis grid lines. + * + * @param {D3LineSubView} subView The sub view to remove them from + */ + removeYGridLines(subView) { + Preconditions.checkArgumentInstanceOf(subView, D3LineSubView); + + d3.select(subView.svg.yGridLinesEl) + .selectAll('*') + .remove(); + } + + /** + * Get the plotting X coordinate of a data point assosciated with a + * D3LineData + * + * @param {D3LineData} lineData The D3LineData for the X coordinate + * @param {String} scale The X axis scale + * @param {D3XYPair} xyPair The data point to plot + * @returns {Number} The plotting X coordinate of the X data point + */ + x(lineData, scale, xyPair) { + Preconditions.checkArgumentInstanceOf(lineData, D3LineData); + this._checkScale(scale); + Preconditions.checkArgumentInstanceOf(xyPair, D3XYPair); + + let d3Scale = this._getXAxisScale(lineData, scale); + return d3Scale(xyPair.x); + } + + /** + * Get the plotting Y coordinate of a data point assosciated with a + * D3LineData + * + * @param {D3LineData} lineData The D3LineData for the Y coordinate + * @param {String} scale The Y axis scale + * @param {D3XYPair} xyPair The data point to plot + * @returns {Number} The plotting Y coordinate of the Y data point + */ + y(lineData, scale, xyPair) { + Preconditions.checkArgumentInstanceOf(lineData, D3LineData); + this._checkScale(scale); + Preconditions.checkArgumentInstanceOf(xyPair, D3XYPair); + + let d3Scale = this._getYAxisScale(lineData, scale); + return d3Scale(xyPair.y); + } + + /** + * @private + * Add a X axis label. + * + * @param {D3LineSubView} subView Sub view to add X label + */ + _addXLabel(subView) { + Preconditions.checkArgumentInstanceOf(subView, D3LineSubView); + + let y = subView.options.xAxisLocation == 'top' ? + -subView.options.paddingTop : subView.options.paddingBottom; + + d3.select(subView.svg.xLabelEl) + .attr('x', subView.plotWidth / 2) + .attr('y', y) + .style('text-anchor', 'middle') + .style('alignment-baseline', 'middle') + .style('font-weight', subView.options.axisLabelFontWeight) + .text(subView.options.xLabel); + } + + /** + * @private + * Add a Y axis label. + * + * @param {D3LineSubView} subView Sub view to add Y label + */ + _addYLabel(subView) { + Preconditions.checkArgumentInstanceOf(subView, D3LineSubView); + + let y = subView.options.yAxisLocation == 'right' ? + subView.options.paddingRight : -subView.options.paddingLeft; + + d3.select(subView.svg.yLabelEl) + .attr('x', -subView.plotHeight / 2) + .attr('y', y) + .style('text-anchor', 'middle') + .style('alignment-baseline', 'middle') + .style('font-weight', subView.options.axisLabelFontWeight) + .text(subView.options.yLabel); + } + + /** + * @private + * Check that the scale is either 'log' || 'linear' + * + * @param {String} scale The scale + */ + _checkScale(scale) { + Preconditions.checkArgument( + scale == 'log' || scale == 'linear', + `Axis scale [${scale}] not supported`); + } + + /** + * @private + * Get the D3 axis scale: d3.scaleLog() || d3.scaleLinear() + * + * @param {String} scale The axis scale: 'log' || 'linear' + */ + _getD3AxisScale(scale) { + this._checkScale(scale); + return scale == 'log' ? d3.scaleLog() : d3.scaleLinear(); + } + + /** + * @private + * Get the D3 X axis: d3.axisTop() || d3.axisBottom() + * + * @param {D3LineData} lineData The line data + * @param {String} scale The axis scale: 'log' || 'linear' + */ + _getXAxis(lineData, scale) { + Preconditions.checkArgumentInstanceOf(lineData, D3LineData); + this._checkScale(scale); + + let d3Scale = this._getXAxisScale(lineData, scale); + let axis = lineData.subView.options.xAxisLocation == 'top' ? + d3.axisTop(d3Scale) : d3.axisBottom(d3Scale); + + axis.ticks(lineData.subView.options.xTickMarks); + + return axis; + } + + /** + * @private + * Get the X axis scale and set the range and domain. + * + * @param {D3LineData} lineData The line data + * @param {String} scale The axis scale + */ + _getXAxisScale(lineData, scale) { + Preconditions.checkArgumentInstanceOf(lineData, D3LineData); + this._checkScale(scale); + + let d3Scale = this._getD3AxisScale(scale); + d3Scale.range([ 0, lineData.subView.plotWidth ]) + .domain(lineData.getXLimit()); + + if (lineData.subView.options.xAxisNice) { + d3Scale.nice(lineData.subView.options.xTickMarks); + } + + return d3Scale; + } + + /** + * @private + * Get the Y axis: d3AxisLeft() || d3.axisRight() + * + * @param {D3LineData} lineData The line data + * @param {String} scale The axis scale + */ + _getYAxis(lineData, scale) { + Preconditions.checkArgumentInstanceOf(lineData, D3LineData); + this._checkScale(scale); + + let d3Scale = this._getYAxisScale(lineData, scale); + let axis = lineData.subView.options.yAxisLocation == 'right' ? + d3.axisRight(d3Scale) : d3.axisLeft(d3Scale); + + axis.ticks(lineData.subView.options.yTickMarks); + + return axis; + } + + /** + * @private + * Get the Y axis scale and set the range and domain. + * + * @param {D3LineData} lineData The line data + * @param {String} scale The axis scale + */ + _getYAxisScale(lineData, scale) { + Preconditions.checkArgumentInstanceOf(lineData, D3LineData); + this._checkScale(scale); + + let d3Scale = this._getD3AxisScale(scale); + d3Scale.range([ lineData.subView.plotHeight, 0 ]) + .domain(lineData.getYLimit()); + + if (lineData.subView.options.yAxisReverse) { + d3Scale.range([ 0, lineData.subView.plotHeight ]); + } + + if (lineData.subView.options.yAxisNice) { + d3Scale.nice(lineData.subView.options.yTickMarks); + } + + return d3Scale; + } + + /** + * If the axis scale is 'log' set the tick marks to be + * in exponential form. + * + * @param {D3LineSubView} subView The sub view to set the tick marks + * @param {HTMLElement} tickMarksEl The X or Y tick mark element + * @param {String} scale The axis scale + */ + _setExponentTickMarks(subView, tickMarksEl, scale) { + Preconditions.checkArgumentInstanceOf(subView, D3LineSubView); + Preconditions.checkArgumentInstanceOfSVGElement(tickMarksEl); + this._checkScale(scale); + + if (scale != 'log') return; + + d3.select(tickMarksEl) + .selectAll('.tick text') + .text(null) + .filter((d) => { return Number.isInteger(Math.log10(d)); }) + .text(10) + .append('tspan') + .text((d) => { return Math.round(Math.log10(d)); }) + .style('baseline-shift', 'super') + .attr('font-size', subView.options.tickExponentFontSize); + } + +} diff --git a/webapp/apps/js/d3/data/D3LineData.js b/webapp/apps/js/d3/data/D3LineData.js new file mode 100644 index 000000000..d7bf89b24 --- /dev/null +++ b/webapp/apps/js/d3/data/D3LineData.js @@ -0,0 +1,544 @@ + +import { D3LineOptions } from '../options/D3LineOptions.js'; +import { D3LineSeriesData } from './D3LineSeriesData.js'; +import { D3LineSubView } from '../view/D3LineSubView.js'; +import { D3Utils } from '../D3Utils.js'; +import { D3XYPair } from './D3XYPair.js'; + +import { Preconditions } from '../../error/Preconditions.js'; + +/** + * @fileoverview Create the data series for line plots. + * + * Use D3LineData.Builder to build a D3LineData instance. + * See D3LineData.builder() + * See D3LineDataBuilder + * + * @class D3LineData + * @author Brandon Clayton + */ +export class D3LineData { + + /** + * @private + * Must use D3LineData.builder() + * + * @param {D3LineDataBuilder} builder The builder + */ + constructor(builder) { + Preconditions.checkArgumentInstanceOf(builder, D3LineDataBuilder); + + /** + * The color scheme for plotting. + * The color scheme will rotate through the colors once the + * length of data is greater than color scheme array. + * Default: d3.schemeCategory10 + * @type {Array<String>} + */ + this.colorScheme = builder._colorScheme; + + /** + * The series XY values and line options. + * @type {Array<D3LineSeriesData>} + */ + this.series = builder._series; + + /** + * Which line sub view to plot. + * @type {D3LineSubView} + */ + this.subView = builder._subView; + + /** + * The lower and upper limit of the X axis. + * Default: 'auto' + * @type {Array<Number>} + */ + this.xLimit = builder._xLimit; + + /** + * The lower and upper limit of the Y axis. + * Default: 'auto' + * @type {Array<Number>} + */ + this.yLimit = builder._yLimit; + + this._updateLineOptions(); + + /* Make immutable */ + Object.freeze(this); + } + + /** + * Return a new D3LineDataBuilder. + * See D3LineDataBuilder + * + * @return {D3LineDataBuilder} new D3LineDataBuilder + */ + static builder() { + return new D3LineDataBuilder(); + } + + /** + * Create a new D3LineData from multiple D3LineData. + * + * @param {...D3LineData} lineData + */ + static of(...lineData) { + let builder = D3LineData.builder().of(...lineData); + return builder.build(); + } + + /** + * Combine two D3LineData using the D3LineData.series.lineOptions.id + * field to find matching D3LineSeries. + * + * @param {D3LineData} lineData The line data to combine + */ + concat(lineData) { + Preconditions.checkArgumentInstanceOf(lineData, D3LineData); + + let builder = D3LineData.builder() + .subView(this.subView); + + for (let series of this.series) { + let matchingSeriesArray = lineData.series.filter((seriesConcat) => { + return series.lineOptions.id == seriesConcat.lineOptions.id; + }); + + let xValues = series.xValues; + let yValues = series.yValues; + let xStrings = series.xStrings; + let yStrings = series.yStrings; + + for (let matchingSeries of matchingSeriesArray) { + xValues = xValues.concat(matchingSeries.xValues); + yValues = yValues.concat(matchingSeries.yValues); + + xStrings = xStrings.concat(matchingSeries.xStrings); + yStrings = yStrings.concat(matchingSeries.yStrings); + } + + builder.data( + xValues, + yValues, + series.lineOptions, + xStrings, + yStrings); + } + + return builder.build(); + } + + /** + * Get all XY values. + * + * @returns {Array<Array<D3XYPair>>} Array of XY values + */ + getData() { + let data = []; + for (let d of this.series) { + data.push(d.data); + } + + return data; + } + + /** + * Get all the line options associated with the XY values. + * + * @returns {Array<D3LineOptions>} Array of line options. + */ + getLineOptions() { + let options = []; + for (let d of this.series) { + options.push(d.lineOptions); + } + + return options; + } + + /** + * Get the X limits for the X axis, either from the set xLimit in + * the builder or from the min and max values in the data. + * + * @returns {Array<Number>} The [ min, max ] X values + */ + getXLimit() { + if (this.xLimit) return this.xLimit; + + let max = this._getXLimitMax(); + let min = this._getXLimitMin(); + + return [min, max]; + } + + /** + * Get the Y limits for the Y axis, either from the set yLimit in + * the builder or from the min and max values in the data. + * + * @returns {Array<Number>} The [ min, max ] Y values + */ + getYLimit() { + if (this.yLimit) return this.yLimit; + + let max = this._getYLimitMax(); + let min = this._getYLimitMin(); + + return [min, max]; + } + + /** + * Convert a D3LineSeriesData into an + * Array<D3LineSeriesData> where each D3LineSeriesData is a single + * XY data point. + * + * @param {D3LineSeriesData} series The series to expand + * @returns {Array<D3LineSeriesData>} The new array of D3LineSeriesData + */ + toMarkerSeries(series) { + Preconditions.checkArgumentInstanceOf(series, D3LineSeriesData); + + let markerSeries = []; + for (let i in series.data) { + let data = series.data[i]; + + markerSeries.push(new D3LineSeriesData( + [data.x], + [data.y], + series.lineOptions, + [series.xStrings[i]], + [series.yStrings[i]])); + } + + return markerSeries; + } + + /** + * Return an Array<D3LineSeriesData> with only + * D3LineOptions.showLegend as true. + * + * @returns {Array<D3LineSeriesData>} + */ + toLegendSeries() { + let legendSeries = []; + for (let series of this.series) { + if (!series.lineOptions.showInLegend) continue; + legendSeries.push(series); + } + + return legendSeries; + } + + /** + * @private + * Get the max X value. + */ + _getXLimitMax() { + let max = d3.max(this.series, (/** @type {D3LineSeriesData} */ series) => { + return d3.max(series.data, (/** @type {D3XYPair */ data) => { + return data.x; + }); + }); + + return max; + } + + /** + * @private + * Get the min X value. + */ + _getXLimitMin() { + let min = d3.min(this.series, (/** @type {D3LineSeriesData} */ series) => { + return d3.min(series.data, (/** @type {D3XYPair} */ data) => { + return data.x; + }); + }); + + return min; + } + + /** + * @private + * Get the max Y value. + */ + _getYLimitMax() { + let max = d3.max(this.series, (/** @type {D3LineSeriesData} */ series) => { + return d3.max(series.data, (/** @type {D3XYPair} */ data) => { + return data.y; + }); + }); + + return max; + } + + /** + * @private + * Get the min Y value. + */ + _getYLimitMin() { + let min = d3.min(this.series, (/** @type {D3LineSeriesData} */ series) => { + return d3.min(series.data, (/** @type {D3XYPair} */ data) => { + return data.y; + }); + }); + + return min; + } + + /** @private */ + _updateLineOptions() { + let index = -1; + let colorIndex = -1; + + for (let data of this.series) { + index++; + let color = data.lineOptions.color || this.colorScheme[++colorIndex]; + let id = data.lineOptions.id || `id${index}`; + let label = data.lineOptions.label || `Line ${index}`; + let markerColor = data.lineOptions.markerColor || this.colorScheme[colorIndex]; + markerColor = markerColor == undefined ? color : markerColor; + let markerEdgeColor = data.lineOptions.markerEdgeColor || + this.colorScheme[colorIndex]; + markerEdgeColor = markerEdgeColor == undefined ? color : markerEdgeColor; + + data.lineOptions = D3LineOptions.builder().fromCopy(data.lineOptions) + .color(color) + .id(id) + .label(label) + .markerColor(markerColor) + .markerEdgeColor(markerEdgeColor) + .build(); + } + + } + +} + +/** + * @fileoverview Builder for D3LineData + * + * Use D3LineData.builder() for new instance of D3LineDataBuilder + * + * @class D3LineDataBuilder + * @author Brandon Clayton + */ +export class D3LineDataBuilder { + + /** @private */ + constructor() { + /** @type {Array<String>} */ + this._colorScheme = undefined; + + /** @type {Boolean} */ + this._removeSmallValues = false; + + /** @type {Array<D3LineSeriesData>} */ + this._series = []; + + /** @type {D3LineSubView} */ + this._subView = undefined; + + /** @type {Array<Number>} */ + this._xLimit = undefined; + + /** @type {Array<Number>} */ + this._yLimit = undefined; + + /** @type {Number} */ + this._yMinCutOff = undefined; + } + + /** + * Return a new D3Data instance. + * + * @return {D3LineData} new D3Data + */ + build() { + Preconditions.checkNotNull(this._subView, 'Must set subView'); + Preconditions.checkNotUndefined(this._subView, 'Must set subView'); + + this._colorScheme = this._updateColorScheme(); + + this._series = this._series.filter((series) => { + return !series.checkXValuesNull(); + }); + + this._series = this._series.filter((series) => { + return !series.checkYValuesNull(); + }); + + if (this._removeSmallValues) { + for (let series of this._series) { + series.removeSmallValues(this._yMinCutOff); + } + } + + return new D3LineData(this); + } + + /** + * Set the color scheme. + * The color scheme will rotate through the colors once the + * length of data is greater than color scheme array. + * + * @param {Array<String>} scheme Array of colors + */ + colorScheme(scheme) { + Preconditions.checkArgumentArrayOf(scheme, 'string'); + this._colorScheme = scheme; + return this; + } + + /** + * Set x-values, y-values, and line options. + * + * @param {Array<Number>} xValues The X values of the data + * @param {Array<Number>} yValues The Y values of the data + * @param {D3LineOptions} [lineOptions = D3LineOptions.withDefaults()] + * The line options for the data + * @param {Array<String>} xStrings + * @param {Array<String>} yStrings + */ + data( + xValues, + yValues, + lineOptions = D3LineOptions.withDefaults(), + xStrings = undefined, + yStrings = undefined) { + Preconditions.checkArgumentArray(xValues); + Preconditions.checkArgumentArray(yValues); + + Preconditions.checkArgument( + xValues.length == yValues.length, + 'Arrays must have same length'); + + Preconditions.checkArgumentInstanceOf(lineOptions, D3LineOptions); + + D3Utils.checkArrayIsNumberOrNull(xValues); + D3Utils.checkArrayIsNumberOrNull(yValues); + + let seriesData = new D3LineSeriesData( + xValues, + yValues, + lineOptions, + xStrings, + yStrings); + + this._series.push(seriesData); + return this; + } + + /** + * Create a D3LineDataBuilder from multiple D3LineData. + * + * @param {...D3LineData} lineData + */ + of(...lineData) { + let color = []; + let xLims = []; + let yLims = []; + let subViewType = lineData[0].subView.options.subViewType; + + for (let data of lineData) { + Preconditions.checkArgumentInstanceOf(data, D3LineData); + Preconditions.checkState( + data.subView.options.subViewType == subViewType, + 'Must all be same sub view type'); + + this._series.push(...data.series); + color = color.concat(data.colorScheme); + xLims.push(data.getXLimit()); + yLims.push(data.getYLimit()); + } + + let xMin = d3.min(xLims, (x) => { return x[0]; }); + let xMax = d3.max(xLims, (x) => { return x[1]; }); + + let yMin = d3.min(yLims, (y) => { return y[0]; }); + let yMax = d3.max(yLims, (y) => { return y[1]; }); + + if (color && color.length > 0) { + this.colorScheme(color); + } + + this.subView(lineData[0].subView); + this.xLimit([xMin, xMax]); + this.yLimit([yMin, yMax]); + this._removeSmallValues = false; + + return this; + } + + /** + * Remove all values under a cut off Y value. + * + * @param {Number} yMinCutOff The cut off value + */ + removeSmallValues(yMinCutOff) { + Preconditions.checkArgumentNumber(yMinCutOff); + this._yMinCutOff = yMinCutOff; + this._removeSmallValues = true; + return this; + } + + /** + * Set the line sub view for which to plot the data. + * + * @param {D3LineSubView} view The sub view to plot the data + */ + subView(view) { + Preconditions.checkArgumentInstanceOf(view, D3LineSubView); + this._subView = view; + return this; + } + + /** + * Set the X limits for the X axis. + * Default: 'auto' + * + * @param {Array<Number>} lim The X axis limits: [ xMin, xMax ] + */ + xLimit(lim) { + Preconditions.checkArgumentArrayLength(lim, 2); + Preconditions.checkArgumentArrayOf(lim, 'number'); + Preconditions.checkArgument(lim[1] >= lim[0], 'xMax must be greater than xMin'); + + this._xLimit = lim; + return this; + } + + /** + * Set the Y limits for the Y axis. + * Default: 'auto' + * + * @param {Array<Number>} lim The Y axis limits: [ yMin, yMax ] + */ + yLimit(lim) { + Preconditions.checkArgumentArrayLength(lim, 2); + Preconditions.checkArgumentArrayOf(lim, 'number'); + Preconditions.checkArgument(lim[1] >= lim[0], 'yMax must be greater than yMin'); + + this._yLimit = lim; + return this; + } + + /** @private */ + _updateColorScheme() { + let nSeries = this._series.length; + + let colors = this._colorScheme ? + this._colorScheme : d3.schemeCategory10; + + let nColors = colors.length; + + let nCat = Math.ceil(nSeries / nColors); + + for (let index = 0; index < nCat; index++) { + colors = colors.concat(colors); + } + + return colors.length > nSeries ? colors.slice(0, nSeries) : colors; + } + +} diff --git a/webapp/apps/js/d3/data/D3LineSeriesData.js b/webapp/apps/js/d3/data/D3LineSeriesData.js new file mode 100644 index 000000000..6bdc9b9cf --- /dev/null +++ b/webapp/apps/js/d3/data/D3LineSeriesData.js @@ -0,0 +1,171 @@ + +import { D3LineOptions } from '../options/D3LineOptions.js'; +import { D3Utils } from '../D3Utils.js'; +import { D3XYPair } from './D3XYPair.js'; + +import { Preconditions } from '../../error/Preconditions.js'; + +/** + * @fileoverview Container class to hold XY values and assoiciated + * D3LineOptions + * + * @class D3LineSeriesData + * @author Brandon Clayton + */ +export class D3LineSeriesData { + + /** + * @param {Array<Number>} xValues The X values + * @param {Array<Number>} yValues The Y values + * @param {D3LineOptions} options The line options + */ + constructor(xValues, yValues, options, xStrings = undefined, yStrings = undefined) { + Preconditions.checkArgumentArray(xValues); + Preconditions.checkArgumentArray(yValues); + Preconditions.checkArgumentInstanceOf(options, D3LineOptions); + + Preconditions.checkArgument( + xValues.length == yValues.length, + 'Arrays must have same length'); + + D3Utils.checkArrayIsNumberOrNull(xValues); + D3Utils.checkArrayIsNumberOrNull(yValues); + + if (xStrings != undefined) { + Preconditions.checkArgumentArrayOf(xStrings, 'string'); + Preconditions.checkArgumentArrayLength(xStrings, xValues.length); + } else { + xStrings = new Array(xValues.length).fill(''); + } + + if (yStrings != undefined) { + Preconditions.checkArgumentArrayOf(yStrings, 'string'); + Preconditions.checkArgumentArrayLength(yStrings, yValues.length); + } else { + yStrings = new Array(xValues.length).fill(''); + } + + /** + * The X values + * @type {Array<Number>} + */ + this.xValues = xValues; + + /** + * The Y values + * @type {Array<Number>} + */ + this.yValues = yValues; + + /** + * Custom X value strings to be shown when viewing the data value + * @type {Array<String>} + */ + this.xStrings = xStrings; + + /** + * Custom Y value strings to be shown when viewing the data value + * @type {Array<String>} + */ + this.yStrings = yStrings; + + /** + * The D3LineOptions associated with XY values + * @type {D3LineOptions} + */ + this.lineOptions = options; + + /** + * The array of XY pair + * @type {Array<D3XYPair>} + */ + this.data = []; + + for (let xy of d3.zip(xValues, yValues, xStrings, yStrings)) { + this.data.push(new D3XYPair(xy[0], xy[1], xy[2], xy[3])); + } + + /** + * The D3 symbol generator. + */ + this.d3Symbol = d3.symbol().type(options.d3Symbol).size(options.d3SymbolSize); + } + + /** + * Given two D3LineSeriesData, find the common X values and return + * an array of XY pairs that were in common. + * + * @param {D3LineSeriesData} seriesA First series + * @param {D3LineSeriesData} seriesB Second series + * @return {Array<D3XYPair>} The array of common XY pairs + */ + static intersectionX(seriesA, seriesB) { + Preconditions.checkArgumentInstanceOf(seriesA, D3LineSeriesData); + Preconditions.checkArgumentInstanceOf(seriesB, D3LineSeriesData); + + let data = seriesA.data.filter((dataA) => { + let index = seriesB.data.findIndex((dataB) => { + return dataA.x == dataB.x; + }); + + return index != -1; + }); + + return data; + } + + /** + * Given two D3LineSeriesData, find the common Y values and return + * an array of XY pairs that were in common. + * + * @param {D3LineSeriesData} seriesA First series + * @param {D3LineSeriesData} seriesB Second series + * @return {Array<D3XYPair>} The array of common XY pairs + */ + static intersectionY(seriesA, seriesB) { + Preconditions.checkArgumentInstanceOf(seriesA, D3LineSeriesData); + Preconditions.checkArgumentInstanceOf(seriesB, D3LineSeriesData); + + let data = seriesA.data.filter((dataA) => { + let index = seriesB.data.findIndex((dataB) => { + return dataA.y == dataB.y; + }); + + return index != -1; + }); + + return data; + } + + /** + * Check to see if all X values are null + */ + checkXValuesNull() { + return this.data.every((xyPair) => { + return xyPair.x == null; + }); + } + + /** + * Check to see if all Y values are null + */ + checkYValuesNull() { + return this.data.every((xyPair) => { + return xyPair.y == null; + }); + } + + /** + * Remove all values under a cut off Y value. + * + * @param {Number} yMinCuttOff The cut off value + */ + removeSmallValues(yMinCuttOff) { + Preconditions.checkArgumentNumber(yMinCuttOff); + + this.data = this.data.filter((xyPair) => { + return xyPair.y > yMinCuttOff; + }); + } + +} diff --git a/webapp/apps/js/d3/data/D3XYPair.js b/webapp/apps/js/d3/data/D3XYPair.js new file mode 100644 index 000000000..61280fde2 --- /dev/null +++ b/webapp/apps/js/d3/data/D3XYPair.js @@ -0,0 +1,43 @@ + +import { Preconditions } from '../../error/Preconditions.js'; + +/** + * @fileoverview Container class to hold a X and Y pair + * + * @class D3LineSeriesData + * @author Brandon Clayton + */ +export class D3XYPair { + + /** + * @param {Number} x The X value + * @param {Number} y The Y value + * @param {String} xString Optional string representation + * @param {String} yString Optional string representation + */ + constructor(x, y, xString = '', yString = '') { + Preconditions.checkArgument( + typeof x == 'number' || x === null, + `Value [${x}] must be a number or null`); + + Preconditions.checkArgument( + typeof y == 'number' || y === null, + `Value [${y}] must be a number or null`); + + Preconditions.checkArgumentString(xString); + Preconditions.checkArgumentString(yString); + + /** @type {Number} The X value */ + this.x = x; + + /** @type {Number} The Y value */ + this.y = y; + + /** @type {String} The X value string representation */ + this.xString = xString; + + /** @type {String} The Y value string representation */ + this.yString = yString; + } + +} diff --git a/webapp/apps/js/d3/legend/D3LineLegend.js b/webapp/apps/js/d3/legend/D3LineLegend.js new file mode 100644 index 000000000..b0282d1d7 --- /dev/null +++ b/webapp/apps/js/d3/legend/D3LineLegend.js @@ -0,0 +1,636 @@ + +import { D3LineData } from '../data/D3LineData.js'; +import { D3LineLegendOptions } from '../options/D3LineLegendOptions.js'; +import { D3LinePlot } from '../D3LinePlot.js'; +import { D3LineSeriesData } from '../data/D3LineSeriesData.js'; +import { D3LineSubView } from '../view/D3LineSubView.js'; +import { D3Utils } from '../D3Utils.js'; +import { D3XYPair } from '../data/D3XYPair.js'; + +import NshmpError from '../../error/NshmpError.js'; +import { Preconditions } from '../../error/Preconditions.js'; + +/** + * @fileoverview Create a legend for a D3LinePlot. + * + * @class D3LineLegend + * @author Brandon Clayton + */ +export class D3LineLegend { + + /** + * New instance of D3LineLegend + * + * @param {D3LinePlot} linePlot + */ + constructor(linePlot) { + Preconditions.checkArgumentInstanceOf(linePlot, D3LinePlot); + this.linePlot = linePlot; + } + + /** + * Create a legend on a sub view. + * + * @param {D3LineData} lineData The line data to show in the legend + */ + create(lineData) { + Preconditions.checkArgumentInstanceOf(lineData, D3LineData); + + if (!lineData.subView.options.showLegend) return; + + this.remove(lineData.subView); + this.show(lineData.subView); + this._createLegendTable(lineData); + this._legendSelectionListener(lineData); + } + + /** + * Hide the legend for specific sub view. + * + * @param {D3LineSubView} subView The sub view + */ + hide(subView) { + Preconditions.checkArgumentInstanceOf(subView, D3LineSubView); + subView.svg.legendEl.classList.add('hidden'); + } + + /** + * Hide legend on all sub views. + */ + hideAll() { + this.hide(this.linePlot.view.upperSubView); + + if (this.linePlot.view.addLowerSubView) { + this.hide(this.linePlot.view.lowerSubView); + } + } + + /** + * Remove the legend from the sub view. + * + * @param {D3LineSubView} subView + */ + remove(subView) { + Preconditions.checkArgumentInstanceOf(subView, D3LineSubView); + + this.hide(subView); + + d3.select(subView.svg.legendForeignObjectEl) + .attr('height', 0) + .attr('width', 0); + + d3.select(subView.svg.legendTableEl) + .selectAll('*') + .remove(); + } + + /** + * Highlight a legend entry given an id of the line, + * D3LineSeries.lineOptions.id, by increasing the line width, + * marker size, and marker edge size based on + * D3LineSeries.lineOptions.selectionMultiplier. + * + * @param {String} id The id of the line series + * @param {...D3LineData} lineDatas The line datas + */ + selectLegendEntry(id, ...lineDatas) { + Preconditions.checkArgumentString(id); + Preconditions.checkArgumentArrayInstanceOf(lineDatas, D3LineData); + + for (let lineData of lineDatas) { + this._resetLegendSelection(lineData); + + d3.select(lineData.subView.svg.legendEl) + .selectAll(`#${id}`) + .each(( + /** @type {D3LineSeriesData} */ series, + /** @type {Number} */ i, + /** @type {NodeList} */ els) => { + Preconditions.checkStateInstanceOf(series, D3LineSeriesData); + Preconditions.checkStateInstanceOf(els, NodeList); + if (!series.lineOptions.showInLegend) return; + this._legendSelection(lineData, series, els[i]); + }); + } + } + + /** + * Show the legend on specific sub view. + * + * @param {D3LineSubView} subView The sub view + */ + show(subView) { + Preconditions.checkArgumentInstanceOf(subView, D3LineSubView); + subView.svg.legendEl.classList.remove('hidden'); + } + + /** + * Show legends on all sub views. + */ + showAll() { + this.show(this.linePlot.view.upperSubView); + + if (this.linePlot.view.addLowerSubView) { + this.show(this.linePlot.view.lowerSubView); + } + } + + syncSubViews() { + for (let lineData of [this.linePlot.upperLineData, this.linePlot.lowerLineData]) { + d3.select(lineData.subView.svg.legendEl) + .selectAll('.legend-entry') + .on('click', (/** @type {D3LineSeriesData} */ series) => { + Preconditions.checkStateInstanceOf(series, D3LineSeriesData); + this.linePlot.selectLine( + series.lineOptions.id, + this.linePlot.upperLineData, + this.linePlot.lowerLineData); + }); + } + } + + + /** + * @private + * Add lines representing the data. + * + * @param {SVGElement} tableSvgEl The SVG table element + * @param {D3LineSeriesData} series The data + * @param {D3LineLegendOptions} legendOptions The legend options + */ + _addLegendLines(tableSvgEl, series, legendOptions) { + Preconditions.checkArgumentInstanceOfSVGElement(tableSvgEl); + Preconditions.checkArgumentInstanceOf(series, D3LineSeriesData); + Preconditions.checkArgumentInstanceOf(legendOptions, D3LineLegendOptions); + + d3.select(tableSvgEl) + .append('line') + .attr('class', 'legend-line') + .attr('x2', legendOptions.lineLength) + .attr('stroke-width', series.lineOptions.lineWidth) + .attr('stroke-dasharray', series.lineOptions.svgDashArray) + .attr('stroke', series.lineOptions.color) + .style('shape-rendering', 'geometricPrecision') + .attr('fill', 'none'); + } + + /** + * @private + * Add the symbols representing the data. + * + * @param {SVGElement} tableSvgEl The SVG table element + * @param {D3LineSeriesData} series The data + * @param {D3LineLegendOptions} legendOptions The legend options + */ + _addLegendSymbols(tableSvgEl, series, legendOptions) { + Preconditions.checkArgumentInstanceOfSVGElement(tableSvgEl); + Preconditions.checkArgumentInstanceOf(series, D3LineSeriesData); + Preconditions.checkArgumentInstanceOf(legendOptions, D3LineLegendOptions); + + let size = series.lineOptions.d3SymbolSize; + let symbol = d3.symbol().type(series.lineOptions.d3Symbol).size(size)(); + let rotate = series.lineOptions.d3SymbolRotate; + let transform = `translate(${legendOptions.lineLength / 2}, 0) rotate(${rotate})`; + + d3.select(tableSvgEl) + .append('path') + .attr('class', 'legend-symbol') + .attr('d', symbol) + .attr('transform', transform) + .attr('fill', series.lineOptions.markerColor) + .attr('stroke', series.lineOptions.markerEdgeColor) + .attr('stroke-width', series.lineOptions.markerEdgeWidth) + .style('shape-rendering', 'geometricPrecision') + } + + /** + * @private + * Add the legend text representing the data. + * + * @param {HTMLElement} tableRowEl The HTML table row element + * @param {D3LineSeriesData} series The data + * @param {D3LineLegendOptions} legendOptions The legend options + */ + _addLegendText(tableRowEl, series, legendOptions) { + Preconditions.checkArgumentInstanceOfHTMLElement(tableRowEl); + Preconditions.checkArgumentInstanceOf(series, D3LineSeriesData); + Preconditions.checkArgumentInstanceOf(legendOptions, D3LineLegendOptions); + + d3.select(tableRowEl) + .append('td') + .attr('class', 'legend-text') + .style('padding', '0 5px') + .style('font-size', `${legendOptions.fontSize}px`) + .attr('nowrap', true) + .text(series.lineOptions.label); + } + + /** + * @private + * Add each D3LineSeriesData to the legend. + * + * @param {HTMLElement} tableRowEl The HTML table row element + * @param {D3LineSeriesData} series The data + * @param {D3LineLegendOptions} legendOptions The legend options + */ + _addSeriesToLegend(tableRowEl, series, legendOptions) { + Preconditions.checkArgumentInstanceOfHTMLElement(tableRowEl); + Preconditions.checkArgumentInstanceOf(series, D3LineSeriesData); + Preconditions.checkArgumentInstanceOf(legendOptions, D3LineLegendOptions); + + d3.select(tableRowEl) + .attr('id', series.lineOptions.id) + .attr('class', 'legend-entry') + .datum(series); + + let tableSvgEl = this._addTableSVG(tableRowEl, series, legendOptions); + this._addLegendLines(tableSvgEl, series, legendOptions); + this._addLegendSymbols(tableSvgEl, series, legendOptions); + this._addLegendText(tableRowEl, series, legendOptions); + } + + /** + * @private + * Add the SVG element to the lengend table row. + * + * @param {HTMLElement} tableRowEl The table row element + * @param {D3LineSeriesData} series The data series + * @param {D3LineLegendOptions} legendOptions The legend options + * @returns {SVGElement} The SVG element + */ + _addTableSVG(tableRowEl, series, legendOptions) { + Preconditions.checkArgumentInstanceOfHTMLElement(tableRowEl); + Preconditions.checkArgumentInstanceOf(series, D3LineSeriesData); + Preconditions.checkArgumentInstanceOf(legendOptions, D3LineLegendOptions); + + let lineOptions = series.lineOptions; + let markerSize = 2 * lineOptions.markerSize; + let lineWidth = lineOptions.lineWidth; + + let rowWidth = legendOptions.lineLength; + let rowHeight = legendOptions.fontSize > markerSize && + legendOptions.fontSize > lineWidth ? legendOptions.fontSize : + markerSize > lineWidth ? markerSize : lineWidth; + + let tableSvgD3 = d3.select(tableRowEl) + .append('td') + .attr('class', 'legend-svg') + .style('padding', '0 5px') + .style('height', `${rowHeight}px`) + .style('width', `${rowWidth}px`) + .style('line-height', 0) + .append('svg') + .attr('version', 1.1) + .attr('xmlns', 'http://www.w3.org/2000/svg') + .attr('height', rowHeight) + .attr('width', rowWidth) + .append('g') + .attr('transform', `translate(0, ${rowHeight / 2})`); + + let svgEl = tableSvgD3.node(); + Preconditions.checkArgumentInstanceOfSVGElement(svgEl); + + return svgEl; + } + + /** + * @private + * Add all legend entries as table row. + * + * @param {D3LineData} lineData The line data + * @param {Array<Array<D3LineSeriesData>>} tableRowData The data + * @param {D3LineLegendOptions} legendOptions The legend options + */ + _addTableRows(lineData, tableRowData, legendOptions) { + Preconditions.checkArgumentInstanceOf(lineData, D3LineData); + for (let row of tableRowData) { + Preconditions.checkArgumentArrayInstanceOf(row, D3LineSeriesData); + } + Preconditions.checkArgumentInstanceOf(legendOptions, D3LineLegendOptions); + + let showExtraEntries = tableRowData.length > legendOptions.maxRows; + tableRowData = showExtraEntries ? + tableRowData.slice(0, legendOptions.maxRows) : tableRowData; + + let tableEl = lineData.subView.svg.legendTableEl; + d3.select(tableEl) + .selectAll('tr') + .data(tableRowData) + .enter() + .append('tr') + .style('cursor', 'pointer') + .each(( + /** @type {Array<D3LineSeriesData>} */ data, + /** @type {Number}*/ i, + /** @type {Array<HTMLElement>}*/ els) => { + Preconditions.checkStateArrayInstanceOf(data, D3LineSeriesData); + + for (let series of data) { + this._addSeriesToLegend(els[i], series, legendOptions); + } + }); + + if (showExtraEntries) { + let nSeries = lineData.toLegendSeries().length; + let extraEntries = nSeries - + ( legendOptions.maxRows * legendOptions.numberOfColumns ); + + d3.select(tableEl) + .append('tr') + .append('td') + .attr('colspan', legendOptions.numberOfColumns * 2) + .style('text-align', 'center') + .text(`... and ${extraEntries} more ...`); + } + + } + + /** + * @private + * Add the table styling from D3LineLegendOptions. + * + * @param {D3LineData} lineData The line data + */ + _addTableStyling(lineData) { + Preconditions.checkStateInstanceOf(lineData, D3LineData); + let legendOptions = lineData.subView.options.legendOptions; + + let padding = `${legendOptions.paddingTop}px ${legendOptions.paddingRight}px ` + + `${legendOptions.paddingBottom}px ${legendOptions.paddingLeft}px`; + + let borderStyle = `${legendOptions.borderLineWidth}px ` + + `${legendOptions.borderStyle} ${legendOptions.borderColor}`; + + d3.select(lineData.subView.svg.legendTableEl) + .style('font-size', `${legendOptions.fontSize}px`) + .style('border-collapse', 'separate') + .style('border', borderStyle) + .style('border-radius', `${legendOptions.borderRadius}px`) + .style('box-shadow', '0 1px 1px rgba(0, 0, 0, 0.05)') + .style('padding', padding) + .style('background', legendOptions.backgroundColor) + .style('cursor', 'move') + .style('border-spacing', '0') + .style('line-height', 'inherit'); + } + + /** + * @private + * Create the legend table for all legend entries. + * + * @param {D3LineData} lineData The line data + */ + _createLegendTable(lineData) { + Preconditions.checkArgumentInstanceOf(lineData, D3LineData); + + d3.select(lineData.subView.svg.legendForeignObjectEl) + .attr('height', '100%') + .attr('width', '100%'); + + let legendOptions = lineData.subView.options.legendOptions; + let legendLineSeries = lineData.toLegendSeries(); + let tableRowData = this._getTableRowData(legendLineSeries, legendOptions); + + this._addTableStyling(lineData); + this._addTableRows(lineData, tableRowData, legendOptions); + + let tableEl = lineData.subView.svg.legendTableEl; + let legendHeight = parseFloat(d3.select(tableEl).style('height')); + let legendWidth = parseFloat(d3.select(tableEl).style('width')); + + d3.select(lineData.subView.svg.legendEl) + .call(this._legendDrag(lineData.subView, legendHeight, legendWidth)); + + let loc = this._legendLocation(lineData.subView, legendHeight, legendWidth); + + d3.select(lineData.subView.svg.legendForeignObjectEl) + .style('height', `${ legendHeight }px`) + .style('width', `${ legendWidth }px`) + .style('overflow', 'visible') + .attr('x', loc.x) + .attr('y', loc.y); + } + + /** + * @private + * Split up the D3LineSeriesData array when using multiple + * columns in a legend; + * + * @param {Array<D3LineSeriesData>} legendLineSeries The line data + * @param {D3LineLegendOptions} legendOptions The legend options + * @returns {Array<Array<D3LineSeriesData>>} + */ + _getTableRowData(legendLineSeries, legendOptions) { + Preconditions.checkArgumentArrayInstanceOf(legendLineSeries, D3LineSeriesData); + Preconditions.checkArgumentInstanceOf(legendOptions, D3LineLegendOptions); + + let data = []; + let nSeries = legendLineSeries.length; + let nRows = Math.ceil( nSeries / legendOptions.numberOfColumns ); + + for (let row = 0; row < nRows; row++) { + let splitStart = row * legendOptions.numberOfColumns; + let splitEnd = ( row + 1 ) * legendOptions.numberOfColumns; + let series = legendLineSeries.slice(splitStart, splitEnd); + data.push(series); + } + + return data; + } + + /** + * @private + * Create a d3 drag function. + * + * @param {D3LineSubView} subView The sub view + * @param {Number} legendHeight The legend height + * @param {Number} legendWidth The legend width + */ + _legendDrag(subView, legendHeight, legendWidth) { + Preconditions.checkArgumentInstanceOf(subView, D3LineSubView); + Preconditions.checkArgumentNumber(legendHeight); + Preconditions.checkArgumentNumber(legendWidth); + + let drag = d3.drag() + .filter(() => { + return d3.event.target == subView.svg.legendTableEl; + }) + .on('drag', () => { + this._onLegendDrag(subView, legendHeight, legendWidth); + }); + + return drag; + } + + /** + * @private + * Calculate the X and Y location of where the legend should be placed. + * + * @param {D3LineSubView} subView The sub view + * @param {Number} legendHeight The legend height + * @param {Number} legendWidth The legend width + * @returns {D3XYPair} + */ + _legendLocation(subView, legendHeight, legendWidth) { + Preconditions.checkArgumentInstanceOf(subView, D3LineSubView); + Preconditions.checkArgumentNumber(legendHeight); + Preconditions.checkArgumentNumber(legendWidth); + + let x = 0; + let y = 0; + let plotHeight = subView.plotHeight; + let plotWidth = subView.plotWidth; + let legendOptions = subView.options.legendOptions; + + let xRight = plotWidth - legendWidth - legendOptions.marginRight + let xLeft = legendOptions.marginLeft; + let yTop = legendOptions.marginTop; + let yBottom = plotHeight - legendHeight - legendOptions.marginBottom; + + switch(legendOptions.location) { + case 'top-right': + x = xRight + y = yTop; + break; + case 'top-left': + x = xLeft; + y = yTop; + break; + case 'bottom-right': + x = xRight; + y = yBottom; + break; + case 'bottom-left': + x = xLeft; + y = yBottom; + break; + default: + NshmpError.throwError(`Cannot set [${legendOptions.location}] legend location`); + } + + return new D3XYPair(x, y); + } + + /** + * @private + * Add a on click event to the legend entries + * + * @param {D3LineData} lineData The line data + */ + _legendSelectionListener(lineData) { + Preconditions.checkArgumentInstanceOf(lineData, D3LineData); + + d3.select(lineData.subView.svg.legendEl) + .selectAll('.legend-entry') + .on('click', (/** @type {D3LineSeriesData */ series) => { + Preconditions.checkStateInstanceOf(series, D3LineSeriesData); + this.linePlot.selectLine(series.lineOptions.id, lineData); + }); + } + + /** + * @private + * Handle the legend entry highlighting. + * + * @param {D3LineData} lineData The line data + * @param {D3LineSeriesData} series The data series + * @param {SVGElement} tableRowEl The table row element + */ + _legendSelection(lineData, series, tableRowEl) { + Preconditions.checkArgumentInstanceOf(lineData, D3LineData); + Preconditions.checkArgumentInstanceOf(series, D3LineSeriesData); + Preconditions.checkArgumentInstanceOfHTMLElement(tableRowEl); + + let isActive = !tableRowEl.classList.contains('active'); + let lineEls = tableRowEl.querySelectorAll('.legend-line'); + let symbolEls = tableRowEl.querySelectorAll('.legend-symbol'); + + d3.select(lineData.subView.svg.legendEl) + .selectAll('.legend-entry') + .classed('active', false); + + tableRowEl.classList.toggle('active', isActive); + let legendOptions = lineData.subView.options.legendOptions; + let fontWeight = isActive ? 'bold' : 'normal'; + let fontSize = legendOptions.fontSize; + + d3.select(tableRowEl) + .select('.legend-text') + .style('font-weight', fontWeight) + .style('font-size', `${fontSize}px`); + + D3Utils.linePlotSelection(series, lineEls, symbolEls, isActive); + } + + /** + * @private + * Handle the legend drag event. + * + * @param {D3LineSubView} subView The sub view + */ + _onLegendDrag(subView, legendHeight, legendWidth) { + Preconditions.checkArgumentInstanceOf(subView, D3LineSubView); + d3.event.sourceEvent.stopPropagation(); + + let x = parseFloat(subView.svg.legendForeignObjectEl.getAttribute('x')); + x += d3.event.dx; + + let y = parseFloat(subView.svg.legendForeignObjectEl.getAttribute('y')); + y += d3.event.dy; + + let plotHeight = subView.plotHeight; + let plotWidth = subView.plotWidth; + let legendOptions = subView.options.legendOptions; + + let checkLeft = legendOptions.marginLeft; + let checkRight = plotWidth - legendWidth - legendOptions.marginRight; + let checkTop = legendOptions.marginTop; + let checkBottom = plotHeight - legendHeight - legendOptions.marginBottom; + + x = x < checkLeft ? checkLeft : + x > checkRight ? checkRight : x; + + y = y < checkTop ? checkTop : + y > checkBottom ? checkBottom : y; + + d3.select(subView.svg.legendForeignObjectEl) + .attr('x', x) + .attr('y', y); + } + + /** + * @private + * Reset any legend entry selections. + * + * @param {D3LineData} lineData The line data + */ + _resetLegendSelection(lineData) { + Preconditions.checkArgumentInstanceOf(lineData, D3LineData); + + d3.select(lineData.subView.svg.legendEl) + .selectAll('.legend-line') + .attr('stroke-width', (/** @type {D3LineSeriesData} */ series) => { + Preconditions.checkStateInstanceOf(series, D3LineSeriesData); + return series.lineOptions.lineWidth; + }); + + d3.select(lineData.subView.svg.legendEl) + .selectAll('.legend-symbol') + .attr('d', (/** @type {D3LineSeriesData}*/ series) => { + Preconditions.checkStateInstanceOf(series, D3LineSeriesData); + return series.d3Symbol.size(series.lineOptions.d3SymbolSize)(); + }) + .attr('stroke-width', (/** @type {D3LineSeriesData} */ series) => { + Preconditions.checkStateInstanceOf(series, D3LineSeriesData); + return series.lineOptions.markerEdgeWidth; + }); + + let legendOptions = lineData.subView.options.legendOptions; + + d3.select(lineData.subView.svg.legendEl) + .selectAll('.legend-text') + .style('font-size', `${legendOptions.fontSize}px`) + .style('font-weight', 'normal'); + } + +} diff --git a/webapp/apps/js/d3/options/D3BaseSubViewOptions.js b/webapp/apps/js/d3/options/D3BaseSubViewOptions.js new file mode 100644 index 000000000..063e81b75 --- /dev/null +++ b/webapp/apps/js/d3/options/D3BaseSubViewOptions.js @@ -0,0 +1,459 @@ + +import { D3SaveFigureOptions } from './D3SaveFigureOptions.js'; +import { D3TooltipOptions } from './D3TooltipOptions.js'; + +import { Preconditions } from '../../error/Preconditions.js'; + +/** + * @fileoverview Create options for D3BaseSubView. + * + * Use D3BaseSubViewOptions.lowerBuilder or + * D3BaseSubViewOptions.upperBuilder to customize options + * for lower and upper sub view or use + * D3BaseSubViewOptions.upperWithDefaults() or + * D3BaseSubViewOptions.lowerWithDefaults() for default options. + * + * Note: The only difference between upperWithDefaults() and + * lowerWithDefault() is the plot height. The lower view defaults with + * 224 px for plot height while the upper is 504 px. + * + * @class D3BaseSubViewOptions + * @author Brandon Clayton + */ +export class D3BaseSubViewOptions { + + /** + * @private + * Must use D3BaseSubViewOptions.lowerBuilder() or + * D3BaseSubViewOptions.upperBuilder() + * + * @param {D3BaseSubViewOptionsBuilder} builder The builder + */ + constructor(builder) { + Preconditions.checkArgumentInstanceOf(builder, D3BaseSubViewOptionsBuilder); + + /** + * The filename for downloading + * Default: 'file' + * @type {String} + */ + this.filename = builder._filename; + + /** + * The label for the sub view + * Default: 'upper' || 'lower' + * @type {String} + */ + this.label = builder._label; + + /** + * Margin bottom for the SVG plot in px. + * Default: 15 + * @type {Number} + */ + this.marginBottom = builder._marginBottom; + + /** + * Margin left for the SVG plot in px. + * Default: 20 + * @type {Number} + */ + this.marginLeft = builder._marginLeft; + + /** + * Margin right for the SVG plot in px. + * Default: 10 + * @type {Number} + */ + this.marginRight = builder._marginRight; + + /** + * Margin top for the SVG plot in px. + * Default: 10 + * @type {Number} + */ + this.marginTop = builder._marginTop; + + /** + * Padding bottom for the SVG plot in px. + * Default: 35 + * @type {Number} + */ + this.paddingBottom = builder._paddingBottom; + + /** + * Padding left for the SVG plot in px. + * Default: 40 + * @type {Number} + */ + this.paddingLeft = builder._paddingLeft; + + /** + * Padding right for the SVG plot in px. + * Default: 20 + * @type {Number} + */ + this.paddingRight = builder._paddingRight; + + /** + * Padding top for the SVG plot in px. + * Default: 10 + * @type {Number} + */ + this.paddingTop = builder._paddingTop; + + /** + * SVG plot height for SVG view box in px. + * Default: 504 (upper) || 224 (lower) + * @type {Number} + */ + this.plotHeight = builder._plotHeight; + + /** + * SVG plot width for SVG view box in px. + * Default: 896 + * @type {Number} + */ + this.plotWidth = builder._plotWidth; + + /** + * The sub view type: 'lower' || 'upper' + * @type {String} + */ + this.subViewType = builder._subViewType; + + /** + * The save figure options. + * Default: D3SaveFigureOptions.withDefaults() + * @type {D3SaveFigureOptions} + */ + this.saveFigureOptions = builder._saveFigureOptions; + + /** + * The tooltip options. + * Default: D3TooltipOptions.withDefaults() + * @type {D3TooltipOptions} + */ + this.tooltipOptions = builder._tooltipOptions; + + /* Make immutable */ + if (new.target == D3BaseSubViewOptions) Object.freeze(this); + } + + /** + * Return new D3BaseSubViewOptions.Builder for lower sub view + * + * @returns {D3BaseSubViewOptionsBuilder} The lower base sub view + * options builder + */ + static lowerBuilder() { + const LOWER_PLOT_HEIGHT = 224; + return new D3BaseSubViewOptionsBuilder() + ._type('lower') + .plotHeight(LOWER_PLOT_HEIGHT); + } + + /** + * Return new D3BaseSubViewOptions for lower sub view + * + * @returns {D3BaseSubViewOptions} The lower base sub view options + */ + static lowerWithDefaults() { + return D3BaseSubViewOptions.lowerBuilder().build(); + } + + /** + * Return new D3BaseSubViewOptions.Builder for upper sub view + * + * @returns {D3BaseSubViewOptionsBuilder} The upper base sub view + * options builder + */ + static upperBuilder() { + return new D3BaseSubViewOptionsBuilder()._type('upper'); + } + + /** + * Return new D3BaseSubViewOptions for upper sub view + * + * @returns {D3BaseSubViewOptions} The upper base sub view options + */ + static upperWithDefaults() { + return D3BaseSubViewOptions.upperBuilder().build(); + } + +} + +/** + * @fileoverview Builder for D3BaseSubViewOptions + * + * Use D3BaseSubViewOptions.lowerBuilder() or + * D3BaseSubViewOptions.upperBuilder() to get new instance of builder. + * + * @class D3SubViewOptionsBuilder + * @author Brandon Clayton + */ +export class D3BaseSubViewOptionsBuilder { + + /** @private */ + constructor() { + /** @type {String} */ + this._filename = 'file'; + + /** @type {String} */ + this._label = ''; + + /** @type {Number} */ + this._marginBottom = 15; + + /** @type {Number} */ + this._marginLeft = 20; + + /** @type {Number} */ + this._marginRight = 10; + + /** @type {Number} */ + this._marginTop = 10; + + /** @type {Number} */ + this._paddingBottom = 35; + + /** @type {Number} */ + this._paddingLeft = 40; + + /** @type {Number} */ + this._paddingRight = 20; + + /** @type {Number} */ + this._paddingTop = 10; + + /** @type {Number} */ + this._plotHeight = 504; + + /** @type {Number} */ + this._plotWidth = 896; + + /** @type {D3SaveFigureOptions} */ + this._saveFigureOptions = D3SaveFigureOptions.withDefaults(); + + /** @type {D3TooltipOptions} */ + this._tooltipOptions = D3TooltipOptions.withDefaults(); + + /** @type {String} */ + this._subViewType = 'upper'; + } + + /** + * Return new D3BaseSubViewOptions + */ + build() { + this._checkHeight(); + this._checkWidth(); + return new D3BaseSubViewOptions(this); + } + + /** + * Set the filename for downloading. + * Default: 'file' + * + * @param {String} name The filename + */ + filename(name) { + Preconditions.checkArgumentString(name); + this._filename = name; + return this; + } + + /** + * Set the label for the sub view. + * Default: '' + * + * @param {String} label The label + */ + label(label) { + Preconditions.checkArgumentString(label); + this._label = label; + return this; + } + + /** + * Set the bottom margin for the SVG plot in px. + * Default: 15 + * + * @param {Number} margin The bottom margin + */ + marginBottom(margin) { + Preconditions.checkArgumentInteger(margin); + this._marginBottom = margin; + return this; + } + + /** + * Set the left margin for the SVG plot in px. + * Default: 20 + * + * @param {Number} margin The left margin + */ + marginLeft(margin) { + Preconditions.checkArgumentInteger(margin); + this._marginLeft = margin; + return this; + } + + /** + * Set the right margin for the SVG plot in px. + * Default: 10 + * + * @param {Number} margin The right margin + */ + marginRight(margin) { + Preconditions.checkArgumentInteger(margin); + this._marginRight = margin; + return this; + } + + /** + * Set the top margin for the SVG plot in px. + * Default: 10 + * + * @param {Number} margin The top margin + */ + marginTop(margin) { + Preconditions.checkArgumentInteger(margin); + this._marginTop = margin; + return this; + } + + /** + * Set the bottom padding for the SVG plot in px. + * Default: 35 + * + * @param {Number} margin The bottom margin + */ + paddingBottom(padding) { + Preconditions.checkArgumentInteger(padding); + this._paddingBottom = padding; + return this; + } + + /** + * Set the left padding for the SVG plot in px. + * Default: 40 + * + * @param {Number} margin The left margin + */ + paddingLeft(padding) { + Preconditions.checkArgumentInteger(padding); + this._paddingLeft = padding; + return this; + } + + /** + * Set the right padding for the SVG plot in px. + * Default: 20 + * + * @param {Number} margin The right margin + */ + paddingRight(padding) { + Preconditions.checkArgumentInteger(padding); + this._paddingRight = padding; + return this; + } + + /** + * Set the top padding for the SVG plot in px. + * Default: 10 + * + * @param {Number} margin The top margin + */ + paddingTop(padding) { + Preconditions.checkArgumentInteger(padding); + this._paddingTop = padding; + return this; + } + + /** + * Set the SVG plot height in px. + * Default: 504 (upper) || 224 (lower) + * + * @param {number} height The plot height + */ + plotHeight(height) { + Preconditions.checkArgumentInteger(height); + this._plotHeight = height; + return this; + } + + /** + * Set the SVG plot width in px. + * Default: 896 + * + * @param {number} width The plot width + */ + plotWidth(width) { + Preconditions.checkArgumentInteger(width); + this._plotWidth = width; + return this; + } + + /** + * Set the save figure options. + * Default: D3SaveFigureOptions.withDefaults() + * + * @param {D3SaveFigureOptions} options The save figure options + */ + saveFigureOptions(options) { + Preconditions.checkArgumentInstanceOf(options, D3SaveFigureOptions); + this._saveFigureOptions = options; + return this; + } + + /** + * Set the tooltip options. + * Default: D3TooltipOptions.withDefaults() + * + * @param {D3TooltipOptions} options The tooltip options + */ + tooltipOptions(options) { + Preconditions.checkArgumentInstanceOf(options, D3TooltipOptions); + this._tooltipOptions = options; + return this; + } + + /** + * Check if plot height is good. + */ + _checkHeight() { + let heightCheck = this._plotHeight - + this._marginBottom - this._marginTop; + + Preconditions.checkState( + heightCheck > 0, + 'Height must be greater than (marginTop + marginBottom)'); + } + + /** + * Check if plot width is good + */ + _checkWidth() { + let widthCheck = this._plotWidth - + this._marginLeft - this._marginRight; + + Preconditions.checkState( + widthCheck > 0, + 'Width must be greater than (marginLeft + marginRight)'); + } + + /** + * @param {String} type + */ + _type(type) { + type = type.toLowerCase(); + Preconditions.checkArgument( + type == 'lower' || type == 'upper', + `Sub view type [${type}] not supported`); + + this._subViewType = type; + return this; + } + +} diff --git a/webapp/apps/js/d3/options/D3BaseViewOptions.js b/webapp/apps/js/d3/options/D3BaseViewOptions.js new file mode 100644 index 000000000..9a603bfc6 --- /dev/null +++ b/webapp/apps/js/d3/options/D3BaseViewOptions.js @@ -0,0 +1,173 @@ + +import { Preconditions } from '../../error/Preconditions.js'; + +/** + * @fileoverview Create options for D3BaseView. + * + * Use D3BaseViewOptions.builder() to customize the options or use + * D3BaseViewOptions.withDefault() for default options. + * + * @class D3BaseViewOptions + * @author Brandon Clayton + */ +export class D3BaseViewOptions { + + /** + * @private + * Must use D3BaseViewOptions.builder() + * + * @param {D3BaseViewOptionsBuilder} builder The builder + */ + constructor(builder) { + Preconditions.checkArgumentInstanceOf(builder, D3BaseViewOptionsBuilder); + + /** + * The title font size in the view's header in px. + * Default: 18 + * @type {Number} + */ + this.titleFontSize = builder._titleFontSize; + + /** + * The D3BaseView view size to start with, either: + * 'min' || 'minCenter' || 'max' + * + * Default value: 'max' + * @type {String} + */ + this.viewSizeDefault = builder._viewSizeDefault; + + /** + * The Bootstrap column size when viewSizeDefault is 'max' + * @type {String} + */ + this.viewSizeMax = builder._viewSizeMax; + + /** + * The Bootstrap column size when viewSizeDefault is 'min' + * @type {String} + */ + this.viewSizeMin = builder._viewSizeMin; + + /** + * The Bootstrap column size when viewSizeDefault is 'minCenter' + * @type {String} + */ + this.viewSizeMinCenter = builder._viewSizeMinCenter; + + /* Make immutable */ + if (new.target == D3BaseViewOptions) Object.freeze(this); + } + + /** + * Return a new D3BaseViewOptions instance with default options + */ + static withDefaults() { + return D3BaseViewOptions.builder().build(); + } + + /** + * Return a new D3BaseViewOptions.Builder + */ + static builder() { + return new D3BaseViewOptionsBuilder(); + } + +} + +/** + * @fileoverview Builder for D3BaseViewOptions + * + * Use D3BaseViewOptions.builder() for new instance of builder. + * + * @class D3BaseViewOptionsBuilder + * @author Brandon Clayton + */ +export class D3BaseViewOptionsBuilder { + + /** @private */ + constructor() { + this._titleFontSize = 18; + + this._viewSizeMin = 'col-sm-12 col-md-6'; + + this._viewSizeMinCenter = 'col-sm-offset-1 col-sm-10 ' + + 'col-xl-offset-2 col-xl-8 col-xxl-offset-3 col-xxl-6'; + + this._viewSizeMax = 'col-sm-12 col-xl-offset-1 col-xl-10 ' + + 'col-xxl-offset-2 col-xxl-8'; + + this._viewSizeDefault = 'max'; + } + + /** + * Return new D3BaseViewOptions instance + */ + build() { + return new D3BaseViewOptions(this); + } + + /** + * Set the title font size in px. + * Default: 18 + * + * @param {Number} fontSize The title font size + */ + titleFontSize(fontSize) { + Preconditions.checkArgumentInteger(fontSize); + this._titleFontSize = fontSize; + return this; + } + + /** + * Set the D3BaseView view size + * + * @param {String} size The view size, either: + * 'min' || 'minCenter' || 'max' + */ + viewSize(size) { + Preconditions.checkArgument( + size == 'min' || size == 'minCenter' || size == 'max', + `View size [${size}] not supported`); + + this._viewSizeDefault = size; + return this; + } + + /** + * Set the Bootstrap column size when viewSize is'min' + * + * @param {String} size The Bootstrap column size with + * viewSize is 'min' + */ + viewSizeMin(size) { + Preconditions.checkArgumentString(size); + this._viewSizeMin = size; + return this; + } + + /** + * Set the Bootstrap column size when viewSize is'minCenter' + * + * @param {String} size The Bootstrap column size with + * viewSize is 'minCenter' + */ + viewSizeMinCenter(size) { + Preconditions.checkArgumentString(size); + this._viewSizeMinCenter = size; + return this; + } + + /** + * Set the Bootstrap column size when viewSize is'max' + * + * @param {String} size The Bootstrap column size with + * viewSize is 'max' + */ + viewSizeMax(size) { + Preconditions.checkArgumentString(size); + this._viewSizeMax = size; + return this; + } + +} diff --git a/webapp/apps/js/d3/options/D3LineLegendOptions.js b/webapp/apps/js/d3/options/D3LineLegendOptions.js new file mode 100644 index 000000000..beb155aae --- /dev/null +++ b/webapp/apps/js/d3/options/D3LineLegendOptions.js @@ -0,0 +1,509 @@ + +import { Preconditions } from '../../error/Preconditions.js'; + +/** + * @fileoverview The options for D3LineLegend + * + * Use D3LineLegendOptions.builder() to customize legend options or + * D3LineLegendOptions.withDefaults() for default legend options. + * + * @class D3LineLegendOptions + * @author Brandon Clayton + */ +export class D3LineLegendOptions { + + /** + * + * @param {D3LineLegendOptionsBuilder} builder + */ + constructor(builder) { + Preconditions.checkArgumentInstanceOf(builder, D3LineLegendOptionsBuilder); + + /** + * The legend background color; 'none' for no color. + * Default: 'white' + * @type {String} + */ + this.backgroundColor = builder._backgroundColor; + + /** + * The legend border color. + * Default: 'gray' + * @type {String} + */ + this.borderColor = builder._borderColor; + + /** + * The legend border width in px. + * Default: 1 + * @type {Number} width The border width in px + */ + this.borderLineWidth = builder._borderLineWidth; + + /** + * The legend border radius in px. + * Default: 4 + * @type {Number} radius The border radius + */ + this.borderRadius = builder._borderRadius; + + /** + * The legend CSS border style. + * Default: 'solid' + * @type {String} + */ + this.borderStyle = builder._borderStyle; + + /** + * The legend font size + * Default: 12 + * @type {Number} + */ + this.fontSize = builder._fontSize; + + /** + * The line length of the line shown in the legend + * Default: 40 + * @type {Number} + */ + this.lineLength = builder._lineLength; + + /** + * The legend location: + * - 'bottom-left' + * - 'bottom-right' + * - 'top-left' + * - 'top-right' + * Default: 'top-right' + * @type {String} + */ + this.location = builder._location; + + /** + * The bottom margin of the legend + * Default: 10 + * @type {Number} + */ + this.marginBottom = builder._marginBottom; + + /** + * The left margin of the legend + * Default: 10 + * @type {Number} + */ + this.marginLeft = builder._marginLeft; + + /** + * The right margin of the legend + * Default: 10 + * @type {Number} + */ + this.marginRight = builder._marginRight; + + /** + * The top margin of the legend + * Default: 10 + * @type {Number} + */ + this.marginTop = builder._marginTop; + + /** + * The number of maximum rows a legend can have. If a legend + * has more rows then maxRows a '... and X more ...' is + * added to the legend. + * Default: 20 (upper sub view) || 4 (lower sub view) + * @type {Number} + */ + this.maxRows = builder._maxRows; + + /** + * The number of columns for the legend to have. + * Default: 1 + * @type {Number} + */ + this.numberOfColumns = builder._numberOfColumns; + + /** + * The bottom padding in the legend + * Default: 10 + * @type {Number} + */ + this.paddingBottom = builder._paddingBottom; + + /** + * The left padding in the legend. + * Default: 10 + * @type {Number} + */ + this.paddingLeft = builder._paddingLeft; + + /** + * The right padding in the legend. + * Default: 10 + * @type {Number} + */ + this.paddingRight = builder._paddingRight; + + /** + * The top padding in the legend. + * Default: 10 + * @type {Number} + */ + this.paddingTop = builder._paddingTop; + + /* Make immutable */ + Object.freeze(this); + } + + /** + * Return new D3LineLegendOptionsBuilder for lower sub view. + * Only difference between lowerBuilder and upperBuilder is + * maxRows is set to 4 for lowerBuilder and 20 for upperBuilder. + * + * @returns {D3LineLegendOptionsBuilder} New options builder + */ + static lowerBuilder() { + const LOWER_PLOT_MAX_ROWS = 4; + return new D3LineLegendOptionsBuilder().maxRows(LOWER_PLOT_MAX_ROWS); + } + + /** + * Return new D3LineLegendOptionsBuilder for upper sub view. + * Only difference between lowerBuilder and upperBuilder is + * maxRows is set to 6 for lowerBuilder and 22 for upperBuilder. + * + * @returns {D3LineLegendOptionsBuilder} New options builder + */ + static upperBuilder() { + return new D3LineLegendOptionsBuilder(); + } + + /** + * Return new D3LineLegendOptions with defaults for lower sub view. + * Only difference between lowerBuilder and upperBuilder is + * maxRows is set to 6 for lowerBuilder and 22 for upperBuilder. + * + * @returns {D3LineLegendOptions} New options with defaults + */ + static lowerWithDefaults() { + return D3LineLegendOptions.lowerBuilder().build(); + } + + /** + * Return new D3LineLegendOptions with defaults for upper sub view. + * Only difference between lowerBuilder and upperBuilder is + * maxRows is set to 6 for lowerBuilder and 22 for upperBuilder. + * + * @returns {D3LineLegendOptions} New options with defaults + */ + static upperWithDefaults() { + return D3LineLegendOptions.upperBuilder().build(); + } + +} + +/** + * @fileoverview Builder for D3LineLegendOptions + * + * Use D3LineLegendOptions.builder() to get new instance of builder. + * + * @class D3LineLegendOptionsBuilder + * @author Brandon Clayton + */ +export class D3LineLegendOptionsBuilder { + + /** @private */ + constructor() { + /** @type {String} */ + this._backgroundColor = 'white'; + + /** @type {String} */ + this._borderColor = 'gray'; + + /** @type {Number} */ + this._borderLineWidth = 1; + + /** @type {Number} */ + this._borderRadius = 4; + + /** @type {String} */ + this._borderStyle = 'solid'; + + /** @type {Number} */ + this._fontSize = 12; + + /** @type {Number} */ + this._lineLength = 40; + + /** @type {String} */ + this._location = 'top-right'; + + /** @type {Number} */ + this._marginBottom = 10; + + /** @type {Number} */ + this._marginLeft = 10; + + /** @type {Number} */ + this._marginRight = 10; + + /** @type {Number} */ + this._marginTop = 10; + + /** @type {Number} */ + this._maxRows = 20; + + /** @type {Number} */ + this._numberOfColumns = 1; + + /** @type {Number} */ + this._paddingBottom = 10; + + /** @type {Number} */ + this._paddingLeft = 10; + + /** @type {Number} */ + this._paddingRight = 10; + + /** @type {Number} */ + this._paddingTop = 10; + } + + /** + * Return new D3LineLegendOptions + * @returns {D3LineLegendOptions} New legend options + */ + build() { + return new D3LineLegendOptions(this); + } + + /** + * Set the legend background color; 'none' for no color. + * Default: 'white' + * + * @param {String} color The background color + */ + backgroundColor(color) { + Preconditions.checkArgumentString(color); + this._backgroundColor = color; + return this; + } + + /** + * Set the legend border color. + * Default: 'gray' + * + * @param {String} color The border color + */ + borderColor(color) { + Preconditions.checkArgumentString(color); + this._borderColor = color; + return this; + } + + /** + * Set the legend border width in px. + * Default: 1 + * + * @param {Number} width The border width in px + */ + borderLineWidth(width) { + Preconditions.checkArgumentInteger(width); + this._borderLineWidth = width; + return this; + } + + /** + * Set the legend border radius in px. + * Default: 4 + * + * @param {Number} radius The border radius + */ + borderRadius(radius) { + Preconditions.checkArgumentInteger(radius); + this._borderRadius = radius; + return this; + } + + /** + * Set the lgeend CSS border style. + * Default: 'solid' + * + * @param {String} style The border style + */ + borderStyle(style) { + Preconditions.checkArgumentString(style); + this._borderStyle = style; + return this; + } + + /** + * Set the tooltip font size. + * Default: 12 + * + * @param {Number} size The font size + */ + fontSize(size) { + Preconditions.checkArgumentInteger(size); + this._fontSize = size; + return this; + } + + /** + * Set the line length for the line shown in the legend. + * Default: 40 + * @param {Number} length + */ + lineLength(length) { + Preconditions.checkArgumentNumber(length); + this._lineLength = length; + return this; + } + + /** + * Set the legend location: + * - 'bottom-left' + * - 'bottom-right' + * - 'top-left' + * - 'top-right' + * Default: 'top-right' + * + * @param {String} loc Legend location + */ + location(loc) { + Preconditions.checkArgumentString(loc); + loc = loc.toLowerCase(); + Preconditions.checkArgument( + loc == 'bottom-left' || + loc == 'bottom-right' || + loc == 'top-left' || + loc == 'top-right', + `Legend location [${loc}] not supported`); + + this._location = loc; + return this; + } + + /** + * Set the bottom margin of the legend. + * Default: 10 + * + * @param {Number} margin The bottom margin in px + */ + marginBottom(margin) { + Preconditions.checkArgumentInteger(margin); + this._marginBottom = margin; + return this; + } + + /** + * Set the left margin of the legend. + * Default: 10 + * + * @param {Number} margin The left margin in px + */ + marginLeft(margin) { + Preconditions.checkArgumentInteger(margin); + this._marginLeft = margin; + return this; + } + + /** + * Set the right margin of the legend. + * Default: 10 + * + * @param {Number} margin The right margin in px + */ + marginRight(margin) { + Preconditions.checkArgumentInteger(margin); + this._marginRight = margin; + return this; + } + + /** + * Set the top margin of the legend. + * Default: 10 + * + * @param {Number} margin The top margin in px + */ + marginTop(margin) { + Preconditions.checkArgumentInteger(margin); + this._marginTop = margin; + return this; + } + + /** + * Set the number of maximum rows a legend can have. If a legend + * has more rows then maxRows a '... and X more ...' is + * added to the legend. + * Default: 10 + * + * @param {Number} rows The max rows + */ + maxRows(rows) { + Preconditions.checkArgumentInteger(rows); + this._maxRows = rows; + return this; + } + + /** + * Set the number of columns for the legend to have. + * Default: 1 + * + * @param {Number} col The number of columns in the legend + */ + numberOfColumns(col) { + Preconditions.checkArgumentInteger(col); + this._numberOfColumns = col; + return this; + } + + /** + * Set the bottom padding in the tooltip. + * Default: 10 + * + * @param {Number} pad The bottom padding in px + */ + paddingBottom(pad) { + Preconditions.checkArgumentInteger(pad); + this._paddingBottom = pad; + return this; + } + + /** + * Set the left padding in the tooltip. + * Default: 10 + * + * @param {Number} pad The left padding in px + */ + paddingLeft(pad) { + Preconditions.checkArgumentInteger(pad); + this._paddingLeft = pad; + return this; + } + + /** + * Set the right padding in the tooltip. + * Default: 10 + * + * @param {Number} pad The right padding in px + */ + paddingRight(pad) { + Preconditions.checkArgumentInteger(pad); + this._paddingRight = pad; + return this; + } + + /** + * Set the top padding in the tooltip. + * Default: 10 + * + * @param {Number} pad The top padding in px + */ + paddingTop(pad) { + Preconditions.checkArgumentInteger(pad); + this._paddingTop = pad; + return this; + } + +} diff --git a/webapp/apps/js/d3/options/D3LineOptions.js b/webapp/apps/js/d3/options/D3LineOptions.js new file mode 100644 index 000000000..6b9b46cce --- /dev/null +++ b/webapp/apps/js/d3/options/D3LineOptions.js @@ -0,0 +1,541 @@ + +import NshmpError from '../../error/NshmpError.js'; +import { Preconditions } from '../../error/Preconditions.js'; + +/** + * @fileoverview Options for customizing a line in a line plot. + * + * Use D3LineOptions.builder() to get new instance of D3LineOptionsBuilder. + * See D3LineOptions.builder() + * See D3LineOptionsBuilder + * + * @class D3LineOptions + * @author Brandon Clayton + */ +export class D3LineOptions { + + /** + * @private + * Must use D3LineOptions.builder() + * + * @param {D3LineOptionsBuilder} builder The builder + */ + constructor(builder) { + Preconditions.checkArgumentInstanceOf(builder, D3LineOptionsBuilder); + + /** + * The line color. + * The default color is set based on the current color scheme + * in D3LineData.colorScheme + * @type {String} + */ + this.color = builder._color; + + /** + * The id of the line, should have no spaces. + * @type {String} + */ + this.id = builder._id; + + /** + * The label of the line to show in the tooltip and legend + * @type {String} + */ + this.label = builder._label; + + /** + * The line style: + * - '-' || 'solid': Solid line + * - '--' || 'dashed': Dashed line + * - ':' || 'dotted': Dotted line + * - '-:' || 'dash-dot': Dahsed-dotted + * - 'none': No line + * Default: 'solid' + * @type {String} + */ + this.lineStyle = builder._lineStyle; + + /** + * The line width. + * Default: 2.5 + * @type {Number} + */ + this.lineWidth = builder._lineWidth; + + /** + * The marker color. + * The default color is set based on the current color scheme + * in D3LineData.colorScheme + * @type {String} + */ + this.markerColor = builder._markerColor; + + /** + * The marker edge color. + * The default color is set based on the current color scheme + * in D3LineData.colorScheme + * @type {String} + */ + this.markerEdgeColor = builder._markerEdgeColor; + + /** + * The marker edge width. + * Default: 1.0 + * @type {Number} + */ + this.markerEdgeWidth = builder._markerEdgeWidth; + + /** + * The marker size. + * Default: 6 + * @type {Number} + */ + this.markerSize = builder._markerSize; + + /** + * The marker style: + * - 's' || 'square': Square markers + * - 'o' || 'circle': Circle markers + * - '+' || 'plus-sign': Plus sign markers + * - 'x' || 'cross': Cross sign markers + * - '^' || 'up-triangle': Up-pointing triangle + * - 'v' || 'down-triangle': Down-pointing triangle + * - '<' || 'left-triangle': Left-pointing triangle + * - '>' || 'right-triangle': Right-pointing triangle + * - 'd' || 'diamond': Diamond markers + * - '*' || 'star': Star markers + * Default: 'circle' + * @type {String} + */ + this.markerStyle = builder._markerStyle; + + /** + * Whether the data is selectable in the plot. + * Default: true + * @type {Boolean} + */ + this.selectable = builder._selectable; + + /** + * The plot selection multiplier to be applied to the + * line width, marker size, and marker edge size, when a line + * or marker is selected. + * Default: 2.0 + * @type{Number} + */ + this.selectionMultiplier = builder._selectionMultiplier; + + /** + * Whether to show the data in the legend. + * Default: true + * @type {Boolean} + */ + this.showInLegend = builder._showInLegend; + + /** + * The SVG dash array based on the lineStyle + * @type {String} + */ + this.svgDashArray = this._getDashArray(); + + /** + * The D3 symbol associated with the marker style. + * @type {Object} + */ + this.d3Symbol = this._getD3Symbol(); + + /** + * The D3 symbol sizes are are, square pixels. + * @type {Number} + */ + this.d3SymbolSize = Math.pow(this.markerSize, 2); + + /** + * The D3 symbol rotate. + * @type {Number} + */ + this.d3SymbolRotate = this._getD3SymbolRotate(); + + /* Make immutable */ + Object.freeze(this); + } + + /** + * Create a new D3LineOptions with default options. + * @returns {D3LineOptions} New D3LineOptions instance + */ + static withDefaults() { + return D3LineOptions.builder().build(); + } + + /** + * Create line options for reference lines. + */ + static withRefLineDefaults() { + return D3LineOptions.builder().color('black').build(); + } + + /** + * Returns a new D3LineOptionsBuilder + * @returns {D3LineOptionsBuilder} New builder + */ + static builder() { + return new D3LineOptionsBuilder(); + } + + /** + * @private + */ + _getDashArray() { + let dashArray; + + switch(this.lineStyle) { + case '-' || 'solid': + dashArray = ''; + break; + case '--' || 'dashed': + dashArray = '8, 8'; + break; + case ':' || 'dotted': + dashArray = '2, 5'; + break; + case '-.' || 'dash-dot': + dashArray = '8, 5, 2, 5'; + break; + case 'none': + dashArray = '0, 1'; + break; + default: + NshmpError.throwError(`Line style [${this.lineStyle}] not supported`); + } + + return dashArray; + } + + /** + * @private + */ + _getD3Symbol() { + let symbol; + + switch(this.markerStyle) { + case '+' || 'plus-sign': + case 'x' || 'cross': + symbol = d3.symbolCross; + break; + case 'd' || 'diamond': + symbol = d3.symbolDiamond; + break; + case '*' || 'star': + symbol = d3.symbolStar; + break; + case '^' || 'up-triangle': + case 'v' || 'down-triangle': + case '<' || 'left-triangle': + case '>' || 'right-triangle': + symbol = d3.symbolTriangle; + break; + case 'o' || 'circle': + symbol = d3.symbolCircle; + break; + case 's' || 'square': + symbol = d3.symbolSquare; + break; + case 'none': + symbol = null; + break; + default: + NshmpError.throwError(`Marker [${this.markerStyle}] not supported`); + } + + Preconditions.checkNotUndefined(symbol, 'D3 symbol not found'); + + return symbol; + } + + /** + * @private + */ + _getD3SymbolRotate() { + let rotate; + + switch(this.markerStyle) { + case 'x' || 'cross': + rotate = 45; + break; + case 'v' || 'down-triangle': + rotate = 180; + break; + case '<' || 'left-triangle': + rotate = -90; + break; + case '>' || 'right-triangle': + rotate = 90; + break; + default: + rotate = 0; + } + + return rotate; + } + +} + +/** + * @fileoverview Builder for D3LineOptions + * + * Use D3LineOptions.builder() for new instance of D3LineOptionsBuilder + * + * @class D3LineOptionsBuilder + * @author Brandon Clayton + */ +export class D3LineOptionsBuilder { + + /** @private */ + constructor() { + /** @type {String} */ + this._color = undefined; + + /** @type {String} */ + this._id = undefined; + + /** @type {String} */ + this._label = undefined; + + /** @type {String} */ + this._lineStyle = '-'; + + /** @type {Number} */ + this._lineWidth = 2.5; + + /** @type {String} */ + this._markerStyle = 'o'; + + /** @type {String} */ + this._markerColor = undefined; + + /** @type {String} */ + this._markerEdgeColor = undefined; + + /** @type {Number} */ + this._markerEdgeWidth = 1.0; + + /** @type {Number} */ + this._markerSize = 6.0; + + /** @type {Boolean} */ + this._selectable = true; + + /** @type {Number} */ + this._selectionMultiplier = 2; + + /** @type {Boolean} */ + this._showInLegend = true; + } + + /** + * Returns new D3LineOptions + * + * @returns {D3LineOptions} new D3LineOptions + */ + build() { + return new D3LineOptions(this); + } + + /** + * Copy D3LineOptions into the builder. + * + * @param {D3LineOptions} options The options to copy + */ + fromCopy(options) { + Preconditions.checkArgumentInstanceOf(options, D3LineOptions); + + this._color = options.color; + this._id = options.id; + this._label = options.label; + this._lineStyle = options.lineStyle; + this._lineWidth = options.lineWidth; + this._markerColor = options.markerColor; + this._markerEdgeColor = options.markerEdgeColor; + this._markerEdgeWidth = options.markerEdgeWidth; + this._markerStyle = options.markerStyle; + this._markerSize = options.markerSize; + this._selectable = options.selectable; + this._selectionMultiplier = options.selectionMultiplier; + this._showInLegend = options.showInLegend; + + return this; + } + + /** + * Set the line color. + * The default color is set based on the current color scheme + * in D3LineData.colorScheme. + * + * @param {String} color The line color + */ + color(color) { + Preconditions.checkArgumentString(color); + this._color = color; + return this; + } + + /** + * Set the id of the line. + * + * @param {String} id The id of the line + */ + id(id) { + Preconditions.checkArgumentString(id); + this._id = id; + return this; + } + + /** + * Set the label for the line. Shown in tooltip and legend. + * + * @param {String} label The label for the line + */ + label(label) { + Preconditions.checkArgumentString(label); + this._label = label; + return this; + } + + /** + * Set the line style: + * - '-' || 'solid': Solid line + * - '--' || 'dashed': Dashed line + * - ':' || 'dotted': Dotted line + * - '-:' || 'dash-dot': Dahsed-dotted + * - 'none': No line + * Default: 'solid' + * + * @param {String} style + */ + lineStyle(style) { + Preconditions.checkArgumentString(style); + this._lineStyle = style.toLowerCase(); + return this; + } + + /** + * Set the line width. + * Default: 2.5 + * + * @param {Number} width The line width + */ + lineWidth(width) { + Preconditions.checkArgumentNumber(width); + this._lineWidth = width; + return this; + } + + /** + * Set the marker color. + * The default color is set based on the current color scheme + * in D3LineData.colorScheme + * + * @param {String} color + */ + markerColor(color) { + Preconditions.checkArgumentString(color); + this._markerColor = color; + return this; + } + + /** + * Set the marker edge color. + * The default color is set based on the current color scheme + * in D3LineData.colorScheme + * + * @param {String} color The marker edge color + */ + markerEdgeColor(color) { + Preconditions.checkArgumentString(color); + this._markerEdgeColor = color; + return this; + } + + /** + * Set the marker edge width. + * Default: 1.0 + * + * @param {Number} width The marker edge width + */ + markerEdgeWidth(width) { + Preconditions.checkArgumentNumber(width); + this._markerEdgeWidth = width; + return this; + } + + /** + * The marker size. + * Default: 6 + * @type {Number} + */ + markerSize(size) { + Preconditions.checkArgumentNumber(size); + this._markerSize = size; + return this; + } + + /** + * Set the marker style: + * - 's' || 'square': Square markers + * - 'o' || 'circle': Circle markers + * - '+' || 'plus-sign': Plus sign markers + * - 'x' || 'cross': Cross sign markers + * - '^' || 'up-triangle': Up-pointing triangle + * - 'v' || 'down-triangle': Down-pointing triangle + * - '<' || 'left-triangle': Left-pointing triangle + * - '>' || 'right-triangle': Right-pointing triangle + * - 'd' || 'diamond': Diamond markers + * - '*' || 'star': Star markers + * Default: 'circle' + * + * @param {String} marker + */ + markerStyle(marker) { + Preconditions.checkArgumentString(marker); + this._markerStyle = marker.toLowerCase(); + return this; + } + + /** + * Set whether the data can be selected in the plot. + * + * @param {Boolean} selectable Whether data is selectable + */ + selectable(selectable) { + Preconditions.checkArgumentBoolean(selectable); + this._selectable = selectable; + return this; + } + + /** + * Set the plot selection multiplier to be applied to the + * line width, marker size, and marker edge size, when a line + * or marker is selected. + * Default: 2.0 + * + * @param {Number} mult The multiplier + */ + selectionMultiplier(mult) { + Preconditions.checkArgumentNumber(mult); + this._selectionMultiplier = mult; + return this; + } + + /** + * Whether to show the data in the legend. + * Default: true + * @type {Boolean} + */ + showInLegend(bool) { + Preconditions.checkArgumentBoolean(bool); + this._showInLegend = bool; + return this; + } + +} diff --git a/webapp/apps/js/d3/options/D3LineSubViewOptions.js b/webapp/apps/js/d3/options/D3LineSubViewOptions.js new file mode 100644 index 000000000..9857c942c --- /dev/null +++ b/webapp/apps/js/d3/options/D3LineSubViewOptions.js @@ -0,0 +1,811 @@ + +import { D3BaseSubViewOptions } from './D3BaseSubViewOptions.js'; +import { D3BaseSubViewOptionsBuilder } from './D3BaseSubViewOptions.js'; +import { D3LineLegendOptions } from './D3LineLegendOptions.js'; + +import { Preconditions } from '../../error/Preconditions.js'; + +/** + * @fileoverview Create options for D3LineSubView. + * + * Use D3LineSubViewOptions.lowerBuilder() or + * D3LineSubViewOptions.upperBuilder() to customize options + * for lower and upper sub view or use + * D3LineSubViewOptions.upperWithDefaults() or + * D3LineSubViewOptions.lowerWithDefaults() for default options. + * + * Note: The only difference between upperWithDefaults() and + * lowerWithDefault() is the plot height. The lower view defaults with + * 224 px for plot height while the upper is 504 px. + * + * @class D3LineSubViewOptions + * @extends D3BaseSubViewOptions + * @author Brandon Clayton + */ +export class D3LineSubViewOptions extends D3BaseSubViewOptions { + + /** + * @private + * Must use D3LineSubViewOptions.builder() + * + * @param {D3LineSubViewOptionsBuilder} builder The builder + */ + constructor(builder) { + super(builder); + + /** + * The font weight for the X and Y axis labels. + * Default: 500 + * @type {Number} + */ + this.axisLabelFontWeight = builder._axisLabelFontWeight; + + /** + * The default X limit when the D3LineView is shown with no data. + * Default: [ 0.01, 10 ] + * @type {Array<Number>} + */ + this.defaultXLimit = builder._defaultXLimit; + + /** + * The default X limit when the D3LineView is shown with no data. + * Default: [ 0.01, 1 ] + * @type {Array<Number>} + */ + this.defaultYLimit = builder._defaultYLimit; + + /** + * Snap lines that are being dragged to the nearest specified value. + * Default: 0.01 + * @type {Number} + */ + this.dragLineSnapTo = builder._dragLineSnapTo; + + /** + * Color of axes grid lines. + * Default: '#E0E0E0' + * @type {String} + */ + this.gridLineColor = builder._gridLineColor; + + /** + * Width of axes grid lines. + * Default: 0.75 + * @type {Number} + */ + this.gridLineWidth = builder._gridLineWidth; + + /** + * Axes label font size in px. + * Default: 16 + * @type {Number} + */ + this.labelFontSize = builder._labelFontSize; + + /** + * The legend options. + * Default: D3LineLegendOptions.withDefaults() + * @type {D3LineLegendOptions} + */ + this.legendOptions = builder._legendOptions; + + /** + * A label to represent what the data represents. + * Default: 'Line' + * @type {String} + */ + this.lineLabel = builder._lineLabel; + + /** + * Color of a reference line. + * Default: 'gray' + * @type {String} + */ + this.referenceLineColor = builder._referenceLineColor; + + /** + * Line width of the reference line. + * Default: 1.5 + * @type {Number} + */ + this.referenceLineWidth = builder._referenceLineWidth; + + /** + * Whether to show the legend regardless of using the legend toggle. + * Default: true + * @type {Boolean} + */ + this.showLegend = builder._showLegend; + + /** + * The font size of the exponents on the tick mark values in px. + * Only when in log space. + * Default: 8 + * @type {Number} + */ + this.tickExponentFontSize = builder._tickExponentFontSize; + + /** + * The tick mark values font size in px. + * Default: 12 + * @type {Number} + */ + this.tickFontSize = builder._tickFontSize; + + /** + * The duration for any plot animations in milliseconds. + * e.g. switching between log and linear scales. + * Default: 500 + * @type {Number} + */ + this.translationDuration = builder._translationDuration; + + /** + * The X axis location: 'top' || 'bottom' + * Default: 'bottom' + * @type {String} + */ + this.xAxisLocation = builder._xAxisLocation; + + /** + * Extend the domain to start and end on nice round numbers. + * Default: true + * @type {Boolean} + */ + this.xAxisNice = builder._xAxisNice; + + /** + * The X axis scale: 'log' || 'linear' + * Default: 'linear' + * @type {String} + */ + this.xAxisScale = builder._xAxisScale; + + /** + * The number of digits after decimal place when + * xValueToExponent is set to true. + * Default: 4 + * @type {Number} digits The number of digits after decimal point + */ + this.xExponentFractionDigits = builder._xExponentFractionDigits; + + /** + * The X axis label; can be an HTML string. + * Default: 'X' + * @type {String} + */ + this.xLabel = builder._xLabel; + + /** + * Padding around the X label in px. + * Default: 8 + * @type {Number} + */ + this.xLabelPadding = builder._xLabelPadding; + + /** + * The number of tick marks for the X axis. + * The specified count is only a hint; the scale may return more or + * fewer values depending on the domain. + * Default: 8 + * @type {Number} + */ + this.xTickMarks = builder._xTickMarks; + + /** + * Whether to format the X value in exponential form when X value + * is shown on tooltip, in data view, and when saving data. + * Default: false + * @type{Boolean} + */ + this.xValueToExponent = builder._xValueToExponent; + + /** + * The Y axis location: 'left' || 'right' + * Default: 'left' + * @type {String} + */ + this.yAxisLocation = builder._yAxisLocation; + + /** + * Extend the domain to start and end on nice round numbers. + * Default: true + * @type {Boolean} + */ + this.yAxisNice = builder._yAxisNice; + + /** + * Whether to reverse the Y axis direction. + * Default: false + * @type {Boolean} + */ + this.yAxisReverse = builder._yAxisReverse; + + /** + * The Y axis scale: 'log' || 'linear' + * Default: 'linear' + * @type {String} + */ + this.yAxisScale = builder._yAxisScale; + + /** + * The number of digits after decimal place when + * yValueToExponent is set to true. + * Default: 4 + * @type {Number} digits The number of digits after decimal point + */ + this.yExponentFractionDigits = builder._xExponentFractionDigits; + + /** + * The Y axis label; can be an HTML string. + * Default: 'Y' + * @type {String} + */ + this.yLabel = builder._yLabel; + + /** + * Padding around the Y label in px. + * Default: 10 + * @type {Number} + */ + this.yLabelPadding = builder._yLabelPadding; + + /** + * The number of tick marks for the Y axis. + * The specified count is only a hint; the scale may return more or + * fewer values depending on the domain. + * Default: 6 + * @type {Number} + */ + this.yTickMarks = builder._yTickMarks; + + /** + * Whether to format the Y value in exponential form when Y value + * is shown on tooltip, in data view, and when saving data. + * Default: false + * @type{Boolean} + */ + this.yValueToExponent = builder._yValueToExponent; + + /* Make immutable */ + if (new.target == D3LineSubViewOptions) Object.freeze(this); + } + + /** + * Return new D3LineSubViewOptionsBuilder for lower sub view + */ + static lowerBuilder() { + const LOWER_PLOT_HEIGHT = 224; + return new D3LineSubViewOptionsBuilder() + ._type('lower') + .plotHeight(LOWER_PLOT_HEIGHT) + .legendOptions(D3LineLegendOptions.lowerWithDefaults()); + } + + /** + * Return new D3LineSubViewOptions for lower sub view + */ + static lowerWithDefaults() { + return D3LineSubViewOptions.lowerBuilder().build(); + } + + /** + * Return new D3LineSubViewOptionsBuilder for upper sub view + */ + static upperBuilder() { + return new D3LineSubViewOptionsBuilder() + ._type('upper'); + } + + /** + * Return new D3LineSubViewOptions for upper sub view + */ + static upperWithDefaults() { + return D3LineSubViewOptions.upperBuilder().build(); + } + +} + +/** + * @fileoverview Builder for D3LineSubViewOptions. + * + * Use D3LineSubViewOptions.lowerBuilder() or + * D3LineSubViewOptions.upperBuilder() for new instance of builder. + * + * @class D3LineSubViewOptionsBuilder + * @extends D3BaseSubViewOptionsBuilder + * @author Brandon Clayton + */ +export class D3LineSubViewOptionsBuilder + extends D3BaseSubViewOptionsBuilder { + + /** @private */ + constructor() { + super(); + + /** @type {Number} */ + this._axisLabelFontWeight = 500; + + /** @type {Array<Number>} */ + this._defaultXLimit = [ 0.01, 10 ]; + + /** @type {Array<Number>} */ + this._defaultYLimit = [ 0.01, 1 ]; + + /** @type {Number} */ + this._dragLineSnapTo = 0.01; + + /** @type {String} */ + this._gridLineColor = '#E0E0E0'; + + /** @type {Number} */ + this._gridLineWidth = 0.75; + + /** @type {Number} */ + this._labelFontSize = 16; + + /** @type {D3LineLegendOptions} */ + this._legendOptions = D3LineLegendOptions.upperWithDefaults(); + + /** @type {String} */ + this._lineLabel = 'Line'; + + /** @type {String} */ + this._referenceLineColor = 'gray'; + + /** @type {Number} */ + this._referenceLineWidth = 1.5; + + /** @type {Boolean} */ + this._showLegend = true; + + /** @type {Number} */ + this._tickExponentFontSize = 8; + + /** @type {Number} */ + this._tickFontSize = 12 + + /** @type {Number} */ + this._translationDuration = 500; + + /** @type {String} */ + this._xAxisLocation = 'bottom'; + + /** @type {Boolean} */ + this._xAxisNice = true; + + /** @type {String} */ + this._xAxisScale = 'linear'; + + /** @type {Number} */ + this._xExponentFractionDigits = 4; + + /** @type {String} */ + this._xLabel = 'X'; + + /** @type {Number} */ + this._xLabelPadding = 8; + + /** @type {Number} */ + this._xTickMarks = 8; + + /** @type {Boolean} */ + this._xValueToExponent = false; + + /** @type {String} */ + this._yAxisLocation = 'left'; + + /** @type {Boolean} */ + this._yAxisNice = true; + + /** @type {Boolean} */ + this._yAxisReverse = false; + + /** @type {String} */ + this._yAxisScale = 'linear'; + + /** @type {Number} */ + this._yExponentFractionDigits = 4; + + /** @type {String} */ + this._yLabel = 'Y'; + + /** @type {Number} */ + this._yLabelPadding = 10; + + /** @type {Number} */ + this._yTickMarks = 6; + + /** @type {Boolean} */ + this._yValueToExponent = false; + } + + /** + * Return new D3LineSubViewOptions + * @returns {D3LineSubViewOptions} Sub view options + */ + build() { + this._checkHeight(); + this._checkWidth(); + return new D3LineSubViewOptions(this); + } + + /** + * Set the font weight for the X and Y axis labels. + * Default: 500 + * @param {Number} weight The font weight + */ + axisLabelFontWeight(weight) { + Preconditions.checkArgumentInteger(weight); + this._axisLabelFontWeight = weight; + return this; + } + + /** + * Set the default X limit when the D3LineView is shown with no data. + * Default: [ 0.01, 10 ] + * @param {Array<Number>} xLimit The [ min, max] for the X axis + */ + defaultXLimit(xLimit) { + Preconditions.checkArgumentArrayLength(xLimit, 2); + Preconditions.checkArgumentArrayOf(xLimit, 'number'); + this._defaultXLimit = xLimit; + return this; + } + + /** + * Set the default Y limit when the D3LineView is shown with no data. + * Default: [ 0.01, 1 ] + * @param {Array<Number>} yLimit The [ min, max ] for the Y axis + */ + defaultYLimit(yLimit) { + Preconditions.checkArgumentArrayLength(yLimit, 2); + Preconditions.checkArgumentArrayOf(yLimit, 'number'); + this._defaultYLimit = yLimit; + return this; + } + + /** + * Snap a line to the nearest value when dragging. + * Default: 0.01 + * + * @param {Number} snapTo Snap to value + */ + dragLineSnapTo(snapTo) { + Preconditions.checkArgumentNumber(snapTo); + this._dragLineSnapTo = snapTo; + return this; + } + + /** + * Set the grid line color in HEX, rgb, or string name. + * Default: 'E0E0E0' + * @param {String} color The grid line color + */ + gridLineColor(color) { + Preconditions.checkArgumentString(color); + this._gridLineColor = color; + return this; + } + + /** + * Set the grid line width. + * Default: 0.75 + * @param {Number} width The grid line width + */ + gridLineWidth(width) { + Preconditions.checkArgumentNumber(width); + this._gridLineWidth = width; + return this; + } + + /** + * Set the legend options. + * Default: D3LineLegendOptions.withDefaults() + * + * @param {D3LineLegendOptions} options The legend options + */ + legendOptions(options) { + Preconditions.checkArgumentInstanceOf(options, D3LineLegendOptions); + this._legendOptions = options; + return this; + } + + /** + * A label representing what the line data is. + * Default: '' + * + * @param {String} label The line label + */ + lineLabel(label) { + Preconditions.checkArgumentString(label); + this._lineLabel = label; + return this; + } + + /** + * Set the reference line color in HEX, RGB, or string name. + * Default: '#9E9E9E' + * @param {String} color The color + */ + referenceLineColor(color) { + Preconditions.checkArgumentString(color); + this._referenceLineColor = color; + return this; + } + + /** + * Set the reference line width. + * Default: 1.5 + * @param {Number} width The width + */ + referenceLineWidth(width) { + Preconditions.checkArgumentNumber(width); + this._referenceLineWidth = width; + return this; + } + + /** + * Whether to show the legend regardless of using the legend toggle. + * Default: true + * + * @param {Boolean} show the legend + */ + showLegend(show) { + Preconditions.checkArgumentBoolean(show); + this._showLegend = show; + return this; + } + + /** + * Set the font size of the exponents on the axes tick marks. + * Only in log scale. + * Default: 6 + * @param {Number} size The font size + */ + tickExponentFontSize(size) { + Preconditions.checkArgumentInteger(size); + this._tickExponentFontSize = size; + return this; + } + + /** + * Set the axes tick mark font size. + * Default: 12 + * @param {Number} size + */ + tickFontSize(size) { + Preconditions.checkArgumentInteger(size); + this._tickFontSize = size; + return this; + } + + /** + * Set the transition duration in milliseconds. Used when switching + * between log and linear scale. + * Default: 500 + * @param {Number} time The duration + */ + translationDuration(time) { + Preconditions.checkArgumentInteger(time); + this._translationDuration = time; + return this; + } + + /** + * Set the X axis location: 'top' || 'bottom' + * Default: 'bottom' + * @param {String} loc The location + */ + xAxisLocation(loc) { + loc = loc.toLowerCase(); + Preconditions.checkArgument( + loc == 'bottom' || loc == 'top', + `X axis location [${loc}] not supported`); + + this._xAxisLocation = loc; + return this; + } + + /** + * Whether to extend the X domain to nice round numbers. + * Default: true + * @param {Boolean} bool Whether to have a nice domain + */ + xAxisNice(bool) { + Preconditions.checkArgumentBoolean(bool); + this._xAxisNice = bool; + return this; + } + + /** + * Set the X axis scale: 'log' || 'linear' + * Default: 'linear' + * @param {String} scale The X axis scale + */ + xAxisScale(scale) { + scale = scale.toLowerCase(); + Preconditions.checkArgument( + scale == 'log' || scale == 'linear', + `X axis scale [${scale}] not supported`); + + this._xAxisScale = scale; + return this; + } + + /** + * Set the number of digits after decimal place when + * xValueToExponent is set to true. + * Default: 4 + * + * @param {Number} digits The number of digits after decimal point + */ + xExponentFractionDigits(digits) { + Preconditions.checkArgumentInteger(digits); + this._xExponentFractionDigits = digits; + return this; + } + + /** + * Set the X axis label; can be an HTML string. + * Default: '' + * @param {String} label The X axis label + */ + xLabel(label) { + Preconditions.checkArgumentString(label); + this._xLabel = label; + return this; + } + + /** + * Set the X label padding in px. + * Default: 8 + * @param {Number} pad The padding + */ + xLabelPadding(pad) { + Preconditions.checkArgumentInteger(pad); + this._xLabelPadding = pad; + return this; + } + + /** + * Set the number of X axis tick marks. + * The specified count is only a hint; the scale may return more or + * fewer values depending on the domain. + * Default: 8 + * @param {Number} count Number of tick marks + */ + xTickMarks(count) { + Preconditions.checkArgumentInteger(count); + this._xTickMarks = count; + return this; + } + + /** + * Whether to format the X value in exponential form when X value + * is shown on tooltip, in data view, and when saving data. + * Default: false + * + * @param {Boolean} toExponenet Whether to format in exponential form + */ + xValueToExponent(toExponenet) { + Preconditions.checkArgumentBoolean(toExponenet); + this._xValueToExponent = toExponenet; + return this; + } + + /** + * Set the Y axis location: 'left' || 'right' + * Default: 'left' + * @param {String} loc The location + */ + yAxisLocation(loc) { + loc = loc.toLowerCase(); + Preconditions.checkArgument( + loc == 'left' || loc == 'right', + `Y axis location [${loc}] not supported`); + + this._yAxisLocation = loc; + return this; + } + + /** + * Whether to extend the Y domain to nice round numbers. + * Default: true + * @param {Boolean} bool Whether to have a nice domain + */ + yAxisNice(bool) { + Preconditions.checkArgumentBoolean(bool); + this._yAxisNice = bool; + return this; + } + + /** + * Whether to reverse the Y axis direction. + * Default: false + * + * @param {Boolean} bool To reverse Y axis + */ + yAxisReverse(bool) { + Preconditions.checkArgumentBoolean(bool); + this._yAxisReverse = bool; + return this; + } + + /** + * Set the Y axis scale: 'log' || 'linear' + * Default: 'linear' + * @param {String} scale The Y axis scale + */ + yAxisScale(scale) { + scale = scale.toLowerCase(); + Preconditions.checkArgument( + scale == 'log' || scale == 'linear', + `Y axis scale [${scale}] not supported`); + + this._yAxisScale = scale; + return this; + } + + /** + * Set the number of digits after decimal place when + * yValueToExponent is set to true. + * Default: 4 + * + * @param {Number} digits The number of digits after decimal point + */ + yExponentFractionDigits(digits) { + Preconditions.checkArgumentInteger(digits); + this._yExponentFractionDigits = digits; + return this; + } + + /** + * Set the Y axis label; can be an HTML string. + * Default: '' + * @param {String} label The Y axis label + */ + yLabel(label) { + Preconditions.checkArgumentString(label); + this._yLabel = label; + return this; + } + + /** + * Set the Y label padding in px. + * Default: 10 + * @param {Number} pad The padding + */ + yLabelPadding(pad) { + Preconditions.checkArgumentInteger(pad); + this._yLabelPadding = pad; + return this; + } + + /** + * Set the number of Y axis tick marks. + * The specified count is only a hint; the scale may return more or + * fewer values depending on the domain. + * Default: 6 + * @param {Number} count Number of tick marks + */ + yTickMarks(count) { + Preconditions.checkArgumentInteger(count); + this._yTickMarks = count; + return this; + } + + /** + * Whether to format the Y value in exponential form when Y value + * is shown on tooltip, in data view, and when saving data. + * Default: false + * + * @param {Boolean} toExponenet Whether to format in exponential form + */ + yValueToExponent(toExponenet) { + Preconditions.checkArgumentBoolean(toExponenet); + this._yValueToExponent = toExponenet; + return this; + } + +} diff --git a/webapp/apps/js/d3/options/D3LineViewOptions.js b/webapp/apps/js/d3/options/D3LineViewOptions.js new file mode 100644 index 000000000..6ba7ed709 --- /dev/null +++ b/webapp/apps/js/d3/options/D3LineViewOptions.js @@ -0,0 +1,237 @@ + +import { D3BaseViewOptions } from './D3BaseViewOptions.js'; +import { D3BaseViewOptionsBuilder } from './D3BaseViewOptions.js'; + +import { Preconditions } from '../../error/Preconditions.js'; + +/** + * @fileoverview Create options for D3LineView + * + * Use Builder to customize the options or use + * D3LineViewOptions.withDefaults() + * + * @class D3LineViewOptions + * @extends D3BaseViewOptions + * @author Brandon Clayton + */ +export class D3LineViewOptions extends D3BaseViewOptions { + + /** + * @private + * Use D3LineViewOptions.builder() + * + * @param {D3LineViewOptionsBuilder} builder The builder + */ + constructor(builder) { + super(builder); + + /** + * Whether to disable the X axis buttons on the view's footer. + * Default: false + * @type {Boolean} + */ + this.disableXAxisBtns = builder._disableXAxisBtns; + + /** + * Whether to disable the Y axis buttons on the view's footer. + */ + this.disableYAxisBtns = builder._disableYAxisBtns; + + /** + * Whether to sync the plot selections between the the upper and + * lower sub views. + * Default: false + * @type {Boolean} + */ + this.syncSubViewsSelections = builder._syncSubViewsSelections; + + /** + * Whether to sync the upper and + * lower sub views Y axis scale, 'log' or 'linear', when toggling + * the X axis buttons in the view's footer. + * Default: false + * @type {Boolean} + */ + this.syncXAxisScale = builder._syncXAxisScale; + + /** + * Whether to sync the upper and + * lower sub views Y axis scale, 'log' or 'linear', when toggling + * the Y axis buttons in the view's footer. + * Default: false + * @type {Boolean} + */ + this.syncYAxisScale = builder._syncYAxisScale; + + /** + * The X axis scale: 'log' || 'linear' + * NOTE: Overriden by D3LineSubViewOptions.xAxisScale if + * syncXAxisScale is false. + * Default: 'linear' + * @type {String} + */ + this.xAxisScale = builder._xAxisScale; + + /** + * The Y axis scale: 'log' || 'linear' + * NOTE: Overriden by D3LineSubViewOptions.yAxisScale if + * syncYAxisScale is false. + * Default: 'linear' + * @type {String} + */ + this.yAxisScale = builder._yAxisScale; + + /* Make immutable */ + if (new.target == D3LineViewOptions) Object.freeze(this); + } + + /** + * @override + * Return a new D3LineViewOptions instance with default options + */ + static withDefaults() { + return D3LineViewOptions.builder().build(); + } + + /** + * @override + * Return a new D3LineViewOptionsBuilder + */ + static builder() { + return new D3LineViewOptionsBuilder(); + } + +} + +/** + * @fileoverview Builder for D3LineViewOptions. + * + * Use D3LineViewOptions.builder() for new instance of builder. + * + * @class D3LineViewOptionsBuilder + * @extends D3BaseViewOptionsBuilder + * @author Brandon Clayton + */ +export class D3LineViewOptionsBuilder extends D3BaseViewOptionsBuilder { + + /** @private */ + constructor() { + super(); + + /** @type {Boolean} */ + this._disableXAxisBtns = false; + + /** @type {Boolean} */ + this._disableYAxisBtns= false; + + /** @type {Boolean} */ + this._syncSubViewsSelections = false; + + /** @type {Boolean} */ + this._syncXAxisScale = false; + + /** @type {Boolean} */ + this._syncYAxisScale = false; + + /** @type {String} */ + this._xAxisScale = 'linear'; + + /** @type {String} */ + this._yAxisScale = 'linear'; + } + + /** + * Return new D3LineViewOptions instance + */ + build() { + return new D3LineViewOptions(this); + } + + /** + * Whether to disable the X axis buttons on the view's footer. + * Default: false + * + * @param {Boolean} bool Whether to disable X axis buttons + */ + disableXAxisBtns(bool) { + Preconditions.checkArgumentBoolean(bool); + this._disableXAxisBtns = bool; + return this; + } + + /** + * Whether to disable the Y axis buttons on the view's footer. + * Default: false + * + * @param {Boolean} bool Whether to disable Y axis buttons + */ + disableYAxisBtns(bool) { + Preconditions.checkArgumentBoolean(bool); + this._disableYAxisBtns = bool; + return this; + } + + /** + * Whether to sync selection between the two sub views. + * Note: The data IDs for the upper and lower sub view must be the + * same to sync. + * + * Default: false + * + * @param {Boolean} bool Whether to sync sub views selections + */ + syncSubViewsSelections(bool) { + Preconditions.checkArgumentBoolean(bool); + this._syncSubViewsSelections = bool; + return this; + } + + /** + * Choose to sync the X axis scale between the two sub views starting + * with a specified scale. + * + * @param {Boolean} bool Whether to sync the X axis scale + * @param {String} scale What X axis scale to start with + */ + syncXAxisScale(bool, scale) { + Preconditions.checkArgumentBoolean(bool); + this._syncXAxisScale = bool; + + if (bool) { + Preconditions.checkArgumentString(scale); + scale = scale.toLowerCase(); + Preconditions.checkArgument( + scale == 'log' || scale == 'linear', + `X axis scale [${scale}] not supported`); + + this._xAxisScale = scale; + } + + return this; + } + + /** + * Choose to sync the Y axis scale between the two sub views starting + * with a specified scale. + * + * @param {Boolean} bool Whether to sync the Y axis scale + * @param {String} scale What Y axis scale to start with + */ + syncYAxisScale(bool, scale) { + Preconditions.checkArgumentBoolean(bool); + this._syncYAxisScale = bool; + + if (bool) { + Preconditions.checkArgumentString(scale); + scale = scale.toLowerCase(); + Preconditions.checkArgument( + scale == 'log' || scale == 'linear', + `Y axis scale [${scale}] not supported`); + + this._yAxisScale = scale; + } + + return this; + } + +} diff --git a/webapp/apps/js/d3/options/D3SaveFigureOptions.js b/webapp/apps/js/d3/options/D3SaveFigureOptions.js new file mode 100644 index 000000000..28c91e792 --- /dev/null +++ b/webapp/apps/js/d3/options/D3SaveFigureOptions.js @@ -0,0 +1,458 @@ + +import { Preconditions } from '../../error/Preconditions.js'; + +/** + * @fileoverview The options for D3SaveFigure + * + * Use D3SaveFigureOptions.builder() to get new instance of D3SaveFigureOptionsBuilder + * See D3SaveFigureOptions.builder() + * See D3SaveFigureOptionsBuilder + * + * @class D3SaveFigureOptions + * @author Brandon Clayton + */ +export class D3SaveFigureOptions { + + /** + * + * @param {D3SaveFigureOptionsBuilder} builder + */ + constructor(builder) { + Preconditions.checkArgumentInstanceOf(builder, D3SaveFigureOptionsBuilder); + + /** + * Whether to add a footer containing the URL and date to the plot page. + * Default: true + * @type {Boolean} + */ + this.addFooter = builder._addFooter; + + /** + * Whether to add a table of the metadata. + * Note: Metadata must be set using D3BaseView.setMetadata() + * Default: true + * @type {Boolean} + */ + this.addMetadata = builder._addMetadata; + + /** + * Whether to add the plot title. + * Default: true + * @type {Boolean} + */ + this.addTitle = builder._addTitle; + + /** + * The DPI to save the figure at. + * Default: 300 + * @type {Number} + */ + this.dpi = builder._dpi; + + /** + * The font size of the footer. + * Default: 10 + * @type {Number} + */ + this.footerFontSize = builder._footerFontSize; + + /** + * The footer line break distance in pixels. + * Deafult: 14 + * @type {Number} + */ + this.footerLineBreak = builder._footerLineBreak; + + /** + * The padding around the footer. + * Default: 10 + * @type {Number} + */ + this.footerPadding = builder._footerPadding; + + /** + * The left margin for the figure on the page. Can be either + * 'center' or number in inches. + * Default: 'center' + * @type {String | Number} + */ + this.marginLeft = builder._marginLeft; + + /** + * The top margin for the figure on the page in inches. + * Default: 0.5 + * @type {Number} + */ + this.marginTop = builder._marginTop; + + /** + * The number of columns allowed in the metadata table. + * Default: 3 + * @type {Number} + */ + this.metadataColumns = builder._metadataColumns; + + /** + * The metadata table font size in pixels. + * Default: 10 + * @type {Number} + */ + this.metadataFontSize = builder._metadataFontSize; + + /** + * The top margin in inches from the bottom of the figure + * to the metadata table. + * Default: 0.5 + * @type {Number} + */ + this.metadataMarginTop = builder._metadataMarginTop; + + /** + * The maximum number of values in a column in the metadata table. + * Once the values is greater than this a '... and X more ...' is added. + * Default: 5 + * @type {Number} + */ + this.metadataMaxColumnValues = builder._metadataMaxColumnValues; + + /** + * The page height in inches. + * Default: 8.5 + * @type {Number} + */ + this.pageHeight = builder._pageHeight; + + /** + * The page width in inches. + * Default: 11.0 + * @type {Number} + */ + this.pageWidth = builder._pageWidth; + + /** + * The title font size. + * Default: 14 + * @type {Number} + */ + this.titleFontSize = builder._titleFontSize; + + /** + * The title location, either: 'center' || 'left' + * Default: 'center' + * @type {String} + */ + this.titleLocation = builder._titleLocation; + + /* Make immutable */ + Object.freeze(this); + } + + /** + * Returns a new D3SaveFigureOptionsBuilder + */ + static builder() { + return new D3SaveFigureOptionsBuilder(); + } + + /** + * Returns new D3SaveFigureOptions with default values + */ + static withDefaults() { + return D3SaveFigureOptions.builder().build(); + } + +} + +/** + * @fileoverview The D3SaveFigureOptions builder + * + * Use D3SaveFigureOptions.builder() for new instance of builder + * + * @class D3SaveFigureOptionsBuilder + * @author Brandon Clayton + */ +export class D3SaveFigureOptionsBuilder { + + constructor() { + /** @type {Boolean} */ + this._addFooter = true; + + /** @type {Boolean} */ + this._addMetadata = true; + + /** @type {Boolean} */ + this._addTitle = true; + + /** @type {Number} */ + this._dpi = 300; + + /** @type {Number} */ + this._footerFontSize = 10; + + /** @type {Number} */ + this._footerLineBreak = 14; + + /** @type {Number} */ + this._footerPadding = 10; + + /** @type {String} */ + this._marginLeft = 'center'; + + /** @type {Number} */ + this._marginTop = 0.5; + + /** @type {Number} */ + this._metadataColumns = 3; + + /** @type {Number} */ + this._metadataFontSize = 10; + + /** @type {Number} */ + this._metadataMarginTop = 0.5; + + /** @type {Number} */ + this._metadataMaxColumnValues = 5; + + /** @type {Number} */ + this._pageHeight = 8.5; + + /** @type {Number} */ + this._pageWidth = 11; + + /** @type {Number} */ + this._titleFontSize = 14; + + /** @type {String} */ + this._titleLocation = 'center'; + } + + /** + * Return new instance of D3SaveFigureOptions + */ + build() { + return new D3SaveFigureOptions(this); + } + + /** + * Set whether to add a footer containing the URL and date to the plot page. + * Default: true + * + * @param {Boolean} addFooter Whether to add footer + */ + addFooter(addFooter) { + Preconditions.checkArgumentBoolean(addFooter); + this._addFooter = addFooter; + return this; + } + + /** + * Set whether to add a table of the metadata. + * Note: Metadata must be set using D3BaseView.setMetadata() + * Default: true + * + * @param {Boolean} addMetadata Whether to add metadata table + */ + addMetadata(addMetadata) { + Preconditions.checkArgumentBoolean(addMetadata); + this._addMetadata = addMetadata; + return this; + } + + /** + * Set whether to add the plot title. + * Default: true + * + * @param {Boolean} addTitle Whether to add title + */ + addTitle(addTitle) { + Preconditions.checkArgumentBoolean(addTitle); + this._addTitle = addTitle; + return this; + } + + /** + * Set the DPI to save the figure at. + * Default: 300 + * + * @param {Number} dpi The plot DPI to save at + */ + dpi(dpi) { + Preconditions.checkArgumentInteger(dpi); + this._dpi = dpi; + return this; + } + + /** + * Set the font size of the footer. + * Default: 10 + * + * @param {Number} fontSize The footer font size + */ + footerFontSize(fontSize) { + Preconditions.checkArgumentInteger(fontSize); + this._footerFontSize = fontSize; + return this; + } + + /** + * Set the footer line break distance in pixels. + * Deafult: 14 + * + * @param {Number} lineBreak The footer line break + */ + footerLineBreak(lineBreak) { + Preconditions.checkArgumentNumber(lineBreak); + this._footerLineBreak = lineBreak; + return this; + } + + /** + * Set the padding around the footer. + * Default: 10 + * + * @param {Number} padding The padding around the footer + */ + footerPadding(padding) { + Preconditions.checkArgumentInteger(padding); + this._footerPadding = padding; + return this; + } + + /** + * Set the left margin for the figure on the page. Can be either + * 'center' or number in inches. + * Default: 'center' + * + * @param {String | Number} marginLeft The left margin of the figure + */ + marginLeft(marginLeft) { + Preconditions.checkArgument( + typeof marginLeft == 'number' || typeof marginLeft == 'string', + `Wrong type [${typeof marginLeft}]`); + + if (typeof marginLeft == 'string') { + marginLeft.toLowerCase(); + Preconditions.checkArgument( + marginLeft == 'center', + `margin type [${marginLeft}] not supported`); + } + + return this; + } + + /** + * Set the top margin for the figure on the page in inches. + * Default: 0.5 + * + * @param {Number} marginTop The top margin of the plot + */ + marginTop(marginTop) { + Preconditions.checkArgumentInteger(marginTop); + this._marginTop = marginTop; + return this; + } + + /** + * Set the number of columns allowed in the metadata table. + * Default: 3 + * + * @param {Number} columns The number of metadata table columns + */ + metadataColumns(columns) { + Preconditions.checkArgumentInteger(columns); + this._metadataColumns = columns; + return this; + } + + /** + * Set the metadata table font size in pixels. + * Default: 10 + * + * @param {Number} fontSize The metadata table font size + */ + metadataFontSize(fontSize) { + Preconditions.checkArgumentInteger(fontSize); + this._metadataFontSize = fontSize; + return this; + } + + /** + * Set the top margin in inches from the bottom of the figure + * to the metadata table. + * Default: 0.5 + * + * @param {Number} marginTop The metadata top margin + */ + metadataMarginTop(marginTop) { + Preconditions.checkArgumentNumber(marginTop); + this._metadataMarginTop = marginTop; + return this; + } + + /** + * Set the maximum number of values in a column in the metadata table. + * Once the values is greater than this a '... and X more ...' is added. + * Default: 5 + * + * @param {Number} max The max column values + */ + metadataMaxColumnValues(max) { + Preconditions.checkArgumentInteger(max); + this._metadataMaxColumnValues = max; + return this; + } + + + /** + * Set the page height in inches. + * Default: 8.5 + * + * @param {Number} height The page height in inches + */ + pageHeight(height) { + Preconditions.checkArgumentNumber(height); + this._pageHeight = height; + return this; + } + + /** + * Set the page width in inches. + * Default: 11.0 + * + * @param {Number} width The page width in inches + */ + pageWidth(width) { + Preconditions.checkArgumentNumber(width); + this._pageWidth = width; + return this; + } + + /** + * Set the title font size. + * Default: 14 + * + * @param {Number} fontSize The title font size + */ + titleFontSize(fontSize) { + Preconditions.checkArgumentInteger(fontSize); + this._titleFontSize = fontSize; + return this; + } + + /** + * Set the title location, either: 'center' || 'left' + * Default: 'center' + * + * @param {String} loc The title location + */ + titleLocation(loc) { + Preconditions.checkArgumentString(loc); + loc = loc.toLowerCase(); + Preconditions.checkArgument( + loc == 'center' || loc == 'left', + `Title location [${loc}] not supported`); + + return this; + } + +} diff --git a/webapp/apps/js/d3/options/D3TextOptions.js b/webapp/apps/js/d3/options/D3TextOptions.js new file mode 100644 index 000000000..928dbbce3 --- /dev/null +++ b/webapp/apps/js/d3/options/D3TextOptions.js @@ -0,0 +1,282 @@ + +import { Preconditions } from '../../error/Preconditions.js'; + +/** + * @fileoverview Text options for plot when adding text to a plot. + * + * Use D3TextOptions.builder() to customize options. + * + * @class D3TextOptions + * @author Brandon Clayton + */ +export class D3TextOptions { + + /** + * @private + * + * @param {D3TextOptionsBuilder} builder The builder + */ + constructor(builder) { + Preconditions.checkArgumentInstanceOf(builder, D3TextOptionsBuilder); + + /** + * The alignment baseline. See CSS property alignment-baseline + * Default: 'baseline' + * @type {String} + */ + this.alignmentBaseline = builder._alignmentBaseline; + + /** + * The text color. + * Default: 'black' + * @type {String} + */ + this.color = builder._color; + + /** + * The horizontal shift in the text from the X position. + * Default: 0 + * @type {Number} + */ + this.dx = builder._dx; + + /** + * The vertical shift in the text from the Y position. + * Default: 0 + * @type {Number} + */ + this.dy = builder._dy; + + /** + * The text font size in pixels. + * Default: 14 + * @type {Number} + */ + this.fontSize = builder._fontSize; + + /** + * The font weight. + * Default: 400 + * @type {Number} + */ + this.fontWeight = builder._fontWeight; + + /** + * The text rotation. + * Default: 0 + * @type {Number} + */ + this.rotate = builder._rotate; + + /** + * The stroke color. + * Default: 'black' + * @type {String} + */ + this.stroke = builder._stroke; + + /** + * The stroke width. + * Default: 0 + * @type {Number} + */ + this.strokeWidth = builder._strokeWidth; + + /** + * The text anchor. See CSS property text-anchor. + * Default: 'start' + * @type {String} + */ + this.textAnchor = builder._textAnchor; + + /* Make immutable */ + Object.freeze(this); + } + + /** + * Return new builder. + */ + static builder() { + return new D3TextOptionsBuilder(); + } + + /** + * New instance of D3TextOptions with default options. + */ + static withDefaults() { + return D3TextOptions.builder().build(); + } + +} + +/** + * @fileoverview Builder for D3TextOptions + * + * @class D3TextOptionsBuilder + * @author Brandon Clayton + */ +export class D3TextOptionsBuilder { + + /** + * @private + */ + constructor() { + /** @type {String} */ + this._alignmentBaseline = 'baseline'; + + /** @type {String} */ + this._color = 'black'; + + /** @type {Number} */ + this._dx = 0; + + /** @type {Number} */ + this._dy = 0; + + /** @type {Number} */ + this._fontSize = 14; + + /** @type {Number} */ + this._fontWeight = 400; + + /** @type {Number} */ + this._rotate = 0; + + /** @type {String} */ + this._stroke = 'black'; + + /** @type {Number} */ + this._strokeWidth = 0; + + /** @type {String} */ + this._textAnchor = 'start'; + } + + /** + * Return new instance of D3TextOptions + */ + build() { + return new D3TextOptions(this); + } + + /** + * Set the alignment baseline. + * Default: 'baseline' + * + * @param {String} alignment The baseline + */ + alignmentBaseline(alignment) { + Preconditions.checkArgumentString(alignment); + this._alignmentBaseline = alignment; + return this; + } + + /** + * Set the text color. + * Default: 'black' + * + * @param {String} color The text color. + */ + color(color) { + Preconditions.checkArgumentString(color); + this._color = color; + return this; + } + + /** + * Set the shift in X. + * Default: 0 + * + * @param {Number} dx The horizontal shift + */ + dx(dx) { + Preconditions.checkArgumentNumber(dx); + this._dx = dx; + return this; + } + + /** + * Set the shift in Y. + * Default: 0 + * + * @param {Number} dy The vertical shift + */ + dy(dy) { + Preconditions.checkArgumentNumber(dy); + this._dy = dy; + return this; + } + + /** + * Set the text font size. + * Default: 14 + * + * @param {Number} fontSize The font size + */ + fontSize(fontSize) { + Preconditions.checkArgumentNumber(fontSize); + this._fontSize = fontSize; + return this; + } + + /** + * Set the font weight. + * Default: 400 + * + * @param {Number} fontWeight The weight + */ + fontWeight(fontWeight) { + Preconditions.checkArgumentInteger(fontWeight); + this._fontWeight = fontWeight; + return this; + } + +/** + * Set the text rotation. + * Default: 0 + * + * @param {Number} rotate The rotation + */ + rotate(rotate) { + Preconditions.checkArgumentNumber(rotate); + this._rotate = rotate; + return this; + } + + /** + * Set the stroke color. + * Default: 'black; + * + * @param {String} stroke The stroke + */ + stroke(stroke) { + Preconditions.checkArgumentString(stroke); + this._stroke = stroke; + return this; + } + + /** + * Set the stroke width. + * Default: 0 + * + * @param {Number} strokeWidth The width + */ + strokeWidth(strokeWidth) { + Preconditions.checkArgumentNumber(strokeWidth); + this._strokeWidth = strokeWidth; + return this; + } + + /** + * Set the text anchor. + * Default: 'start' + * + * @param {String} textAnchor The text anchor + */ + textAnchor(textAnchor) { + Preconditions.checkArgumentString(textAnchor); + this._textAnchor = textAnchor; + return this; + } + +} diff --git a/webapp/apps/js/d3/options/D3TooltipOptions.js b/webapp/apps/js/d3/options/D3TooltipOptions.js new file mode 100644 index 000000000..8b5a61ed5 --- /dev/null +++ b/webapp/apps/js/d3/options/D3TooltipOptions.js @@ -0,0 +1,329 @@ + +import { Preconditions } from '../../error/Preconditions.js'; + +/** + * @fileoverview The options for D3Tooltip + * + * Use D3TooltipOptions.builder() to customize tooltip options or + * D3TooltipOptions.withDefaults() for default tooltip options. + * + * @class D3TooltipOptions + * @author Brandon Clayton + */ +export class D3TooltipOptions { + + /** + * + * @param {D3TooltipOptionsBuilder} builder + */ + constructor(builder) { + Preconditions.checkArgumentInstanceOf(builder, D3TooltipOptionsBuilder); + + /** + * The tooltip background color; 'none' for no color. + * Default: 'white' + * @type {String} + */ + this.backgroundColor = builder._backgroundColor; + + /** + * The tooltip border color. + * Default: 'gray' + * @type {String} + */ + this.borderColor = builder._borderColor; + + /** + * The tooltip border width in px. + * Default: 1 + * @type {Number} width The border width in px + */ + this.borderLineWidth = builder._borderLineWidth; + + /** + * The tooltip border radius in px. + * Default: 4 + * @type {Number} radius The border radius + */ + this.borderRadius = builder._borderRadius; + + /** + * The tooltip CSS border style. + * Default: 'solid' + * @type {String} + */ + this.borderStyle = builder._borderStyle; + + /** + * The tooltip font size + * Default: 12 + * @type {Number} + */ + this.fontSize = builder._fontSize; + + /** + * The X offset of the tooltip from the data point + * Default: 2 + * @type {Number} + */ + this.offsetX = builder._offsetX; + + /** + * The Y offset of the tooltip from the data point + * Default: 10 + * @type {Number} + */ + this.offsetY = builder._offsetY; + + /** + * The bottom padding in the tooltip + * Default: 10 + * @type {Number} + */ + this.paddingBottom = builder._paddingBottom; + + /** + * The left padding in the tooltip + * Default: 10 + * @type {Number} + */ + this.paddingLeft = builder._paddingLeft; + + /** + * The right padding in the tooltip + * Default: 10 + * @type {Number} + */ + this.paddingRight = builder._paddingRight; + + /** + * The top padding in the tooltip + * Default: 10 + * @type {Number} + */ + this.paddingTop = builder._paddingTop; + + /* Make immutable */ + Object.freeze(this); + } + + /** + * Return new D3TooltipOptionsBuilder + * @returns {D3TooltipOptionsBuilder} New options builder + */ + static builder() { + return new D3TooltipOptionsBuilder(); + } + + /** + * Return new D3TooltipOptions with defaults + * @returns {D3TooltipOptions} New options with defaults + */ + static withDefaults() { + return D3TooltipOptions.builder().build(); + } + +} + +/** + * @fileoverview Builder for D3TooltipOptions + * + * Use D3TooltipOptions.builder() to get new instance of builder. + * + * @class D3TooltipOptionsBuilder + * @author Brandon Clayton + */ +export class D3TooltipOptionsBuilder { + + /** @private */ + constructor() { + /** @type {String} */ + this._backgroundColor = 'white'; + + /** @type {String} */ + this._borderColor = 'gray'; + + /** @type {Number} */ + this._borderLineWidth = 1; + + /** @type {Number} */ + this._borderRadius = 4; + + /** @type {String} */ + this._borderStyle = 'solid'; + + /** @type {Number} */ + this._fontSize = 12; + + /** @type {Number} */ + this._offsetX = 2; + + /** @type {Number} */ + this._offsetY = 8; + + /** @type {Number} */ + this._paddingBottom = 10; + + /** @type {Number} */ + this._paddingLeft = 10; + + /** @type {Number} */ + this._paddingRight = 10; + + /** @type {Number} */ + this._paddingTop = 10; + } + + /** + * Return new D3TooltipOptions + * @returns {D3TooltipOptions} New tooltip options + */ + build() { + return new D3TooltipOptions(this); + } + + /** + * Set the tooltip background color; 'none' for no color. + * Default: 'white' + * + * @param {String} color The background color + */ + backgroundColor(color) { + Preconditions.checkArgumentString(color); + this._backgroundColor = color; + return this; + } + + /** + * Set the tooltip border color. + * Default: 'gray' + * + * @param {String} color The border color + */ + borderColor(color) { + Preconditions.checkArgumentString(color); + this._borderColor = color; + return this; + } + + /** + * Set the tooltip border width in px. + * Default: 1 + * + * @param {Number} width The border width in px + */ + borderLineWidth(width) { + Preconditions.checkArgumentInteger(width); + this._borderLineWidth = width; + return this; + } + + /** + * Set the tooltip border radius in px. + * Default: 4 + * + * @param {Number} radius The border radius + */ + borderRadius(radius) { + Preconditions.checkArgumentInteger(radius); + this._borderRadius = radius; + return this; + } + + /** + * Set the tooltip CSS border style. + * Default: 'solid' + * + * @param {String} style The border style + */ + borderStyle(style) { + Preconditions.checkArgumentString(style); + this._borderStyle = style; + return this; + } + + /** + * Set the tooltip font size. + * Default: 12 + * + * @param {Number} size The font size + */ + fontSize(size) { + Preconditions.checkArgumentInteger(size); + this._fontSize = size; + return this; + } + + /** + * Set the X offset of the tooltip from the data point. + * Default: 2 + * + * @param {Number} offset The X offset + */ + offsetX(offset) { + Preconditions.checkArgumentNumber(offset); + this._offsetX = offset; + return this; + } + + /** + * Set the Y offset of the tooltip from the data point. + * Default: 10 + * + * @param {Number} offset The Y offset + */ + offsetY(offset) { + Preconditions.checkArgumentNumber(offset); + this._offsetY = offset; + return this; + } + + /** + * Set the bottom padding in the tooltip. + * Default: 10 + * + * @param {Number} pad The bottom padding in px + */ + paddingBottom(pad) { + Preconditions.checkArgumentInteger(pad); + this._paddingBottom = pad; + return this; + } + + /** + * Set the left padding in the tooltip. + * Default: 10 + * + * @param {Number} pad The left padding in px + */ + paddingLeft(pad) { + Preconditions.checkArgumentInteger(pad); + this._paddingLeft = pad; + return this; + } + + /** + * Set the right padding in the tooltip. + * Default: 10 + * + * @param {Number} pad The right padding in px + */ + paddingRight(pad) { + Preconditions.checkArgumentInteger(pad); + this._paddingRight = pad; + return this; + } + + /** + * Set the top padding in the tooltip. + * Default: 10 + * + * @param {Number} pad The top padding in px + */ + paddingTop(pad) { + Preconditions.checkArgumentInteger(pad); + this._paddingTop = pad; + return this; + } + +} diff --git a/webapp/apps/js/d3/view/D3BaseSubView.js b/webapp/apps/js/d3/view/D3BaseSubView.js new file mode 100644 index 000000000..ec5da57bf --- /dev/null +++ b/webapp/apps/js/d3/view/D3BaseSubView.js @@ -0,0 +1,186 @@ + +import { D3BaseSubViewOptions } from '../options/D3BaseSubViewOptions.js'; + +import { Preconditions } from '../../error/Preconditions.js'; + +/** + * @package + * @fileoverview Create a base sub view for D3BaseView. Adds + * basic SVG structure for a plot. + * + * @class D3BaseSubView + * @author Brandon Clayton + */ +export class D3BaseSubView { + + /** + * Create new sub view. + * + * @param {HTMLElement} containerEl Container element to append sub view + * @param {D3BaseSubViewOptions} options The sub view options + */ + constructor(containerEl, options) { + Preconditions.checkArgumentInstanceOfHTMLElement(containerEl); + Preconditions.checkArgumentInstanceOf(options, D3BaseSubViewOptions); + + /** @type {HTMLElement} Container element to append sub view */ + this.containerEl = containerEl; + + /** @type {D3BaseSubViewOptions} Sub view options */ + this.options = options; + + /** @type {Number} The SVG view box height in px */ + this.svgHeight = this.options.plotHeight - + this.options.marginTop - this.options.marginBottom; + + /** @type {Number} The SVG view box width in px */ + this.svgWidth = this.options.plotWidth - + this.options.marginLeft - this.options.marginRight; + + /** @type {Number} Plot height in px */ + this.plotHeight = this.svgHeight - + this.options.paddingBottom - this.options.paddingTop; + + /** @type {Number} Plot width in px */ + this.plotWidth = this.svgWidth - + this.options.paddingLeft - this.options.paddingRight; + + /** @type {HTMLElement} The sub view element */ + this.subViewBodyEl = this._createSubView(); + + /** @type {D3BaseSubViewSVGElements} SVG elements */ + this.svg = this._createSVGStructure(); + } + + /** + * @package + * Create the SVG structure for the sub view. + * + * @returns {D3BaseSubViewSVGElements} The SVG elements. + */ + _createSVGStructure() { + let svgD3 = d3.select(this.subViewBodyEl) + .append('svg') + .attr('version', 1.1) + .attr('xmlns', 'http://www.w3.org/2000/svg') + .attr('preserveAspectRatio', 'xMinYMin meet') + .attr('viewBox', `0 0 ` + + `${this.options.plotWidth} ${this.options.plotHeight}`); + + let outerPlotD3 = svgD3.append('g') + .attr('class', 'outer-plot') + .attr('transform', `translate(` + + `${this.options.marginLeft}, ${this.options.marginTop})`); + + let outerFrameD3 = outerPlotD3.append('rect') + .attr('class', 'outer-frame') + .attr('height', this.svgHeight) + .attr('width', this.svgWidth) + .attr('fill', 'none'); + + let innerPlotD3 = outerPlotD3.append('g') + .attr('class', 'inner-plot') + .attr('transform', `translate(` + + `${this.options.paddingLeft}, ${this.options.paddingTop})`); + + let innerFrameD3 = innerPlotD3.append('rect') + .attr('class', 'inner-frame') + .attr('height', this.plotHeight) + .attr('width', this.plotWidth) + .attr('fill', 'none'); + + /* Tooltip Group */ + let tooltipD3 = innerPlotD3.append('g') + .attr('class', 'd3-tooltip'); + + let tooltipForeignObjectD3 = tooltipD3.append('foreignObject'); + let tooltipTableD3 = tooltipForeignObjectD3.append('xhtml:table') + .attr('xmlns', 'http://www.w3.org/1999/xhtml'); + + let els = new D3BaseSubViewSVGElements(); + els.innerFrameEl = innerFrameD3.node(); + els.innerPlotEl = innerPlotD3.node(); + els.outerFrameEl = outerFrameD3.node(); + els.outerPlotEl = outerPlotD3.node(); + els.svgEl = svgD3.node(); + els.tooltipEl = tooltipD3.node(); + els.tooltipForeignObjectEl = tooltipForeignObjectD3.node(); + els.tooltipTableEl = tooltipTableD3.node(); + + return els.checkElements(); + } + + /** + * @package + * Create the sub view. + * + * @returns {HTMLElement} The sub view element + */ + _createSubView() { + let subViewD3 = d3.select(this.containerEl) + .append('div') + .style('line-height', '1.2') + .attr('class', 'panel-body'); + + return subViewD3.node(); + } + +} + +/** + * @fileoverview Container class for the D3BaseSubView SVG elements + * + * @class D3BaseSubViewSVGElements + * @author Brandon Clayton + */ +export class D3BaseSubViewSVGElements { + + constructor() { + /** @type {SVGElement} The inner plot frame element */ + this.innerFrameEl = undefined; + + /** @type {SVGElement} The inner plot group element */ + this.innerPlotEl = undefined; + + /** @type {SVGElement} The outer plot frame element */ + this.outerFrameEl = undefined; + + /** @type {SVGElement} The outer plot group element */ + this.outerPlotEl = undefined; + + /** @type {SVGElement} The main SVG element */ + this.svgEl = undefined; + + /** @type {SVGElement} The tooltip group element */ + this.tooltipEl = undefined; + + /** @type {SVGElement} The tooltip foreign object element */ + this.tooltipForeignObjectEl = undefined; + + /** @type {HTMLElement} The tooltip table element */ + this.tooltipTableEl = undefined; + } + + /** + * Check that all elements are set. + * + * @returns {D3BaseSubViewSVGElements} The elements + */ + checkElements() { + for (let value of Object.values(this)) { + Preconditions.checkNotUndefined(value); + } + + Preconditions.checkStateInstanceOfSVGElement(this.innerFrameEl); + Preconditions.checkStateInstanceOfSVGElement(this.innerPlotEl); + Preconditions.checkStateInstanceOfSVGElement(this.outerFrameEl); + Preconditions.checkStateInstanceOfSVGElement(this.outerPlotEl); + Preconditions.checkStateInstanceOfSVGElement(this.svgEl); + Preconditions.checkStateInstanceOfSVGElement(this.tooltipEl); + Preconditions.checkStateInstanceOfSVGElement(this.tooltipForeignObjectEl); + Preconditions.checkStateInstanceOfHTMLElement(this.tooltipTableEl); + + return this; + } + +} diff --git a/webapp/apps/js/d3/view/D3BaseView.js b/webapp/apps/js/d3/view/D3BaseView.js new file mode 100644 index 000000000..89a41bde0 --- /dev/null +++ b/webapp/apps/js/d3/view/D3BaseView.js @@ -0,0 +1,946 @@ + +import { D3BaseSubView } from './D3BaseSubView.js'; +import { D3BaseSubViewOptions } from '../options/D3BaseSubViewOptions.js'; +import { D3BaseViewOptions } from '../options/D3BaseViewOptions.js'; + +import NshmpError from '../../error/NshmpError.js'; +import { Preconditions } from '../../error/Preconditions.js'; + +/** + * @fileoverview Create a base view for plots to reside. The view + * can contain an upper and lower D3BaseSubView for multiple SVG + * plots in a single D3BaseView. + * + * Must use D3BaseView.builder() to create a D3BaseView. + * See D3BaseViewBuilder. + * + * @class D3BaseView + * @author Brandon Clayton + */ +export class D3BaseView { + + /** + * @private + * Must use D3BaseView.builder() to create new instance of D3BaseView. + * + * @param {D3BaseViewBuilder} builder The builder + */ + constructor(builder) { + Preconditions.checkArgumentInstanceOf(builder, D3BaseViewBuilder); + + /** @type {Boolean} Whether to add a grid line toogle to the header */ + this.addGridLineToggle = builder._addGridLineToggle; + + /** @type {Boolean} Whether to add a legend toggle to the header */ + this.addLegendToggle = builder._addLegendToggle; + + /** @type {Boolean} Whether to add a lower sub view */ + this.addLowerSubView = builder._addLowerSubView; + + /** @type {HTMLElement} Container element to append view */ + this.containerEl = builder._containerEl; + + /** @type {D3BaseViewOptions} View options */ + this.viewOptions = builder._viewOptions; + + /** @type {String} Track the size: 'min' || 'minCenter' || 'max' */ + this._currentViewSize = this.viewOptions.viewSizeDefault; + + /** @type {String} */ + this.resizeFullIcon = 'resize glyphicon glyphicon-resize-full'; + + /** @type {String} */ + this.resizeSmallIcon = 'resize glyphicon glyphicon-resize-small'; + + /** @type {String} The plot title text */ + this._titleText = ''; + + /** @type {Map<String, Array<String | Number>>} The metadata */ + this._metadata = new Map(); + + let viewEls = this._createView(); + + /** @type {HTMLElement} Data view, data table element */ + this.dataTableEl = viewEls.dataTableEl; + + /** @type {HTMLElement} Metadata view element */ + this.metadataTableEl = viewEls.metadataTableEl; + + /** @type {HTMLElement} Bootstrap panel body element */ + this.viewPanelBodyEl = viewEls.viewPanelBodyEl; + + /** @type {HTMLElement} Bootstrap panel element */ + this.viewPanelEl = viewEls.viewPanelEl; + + /** @type {HTMLElement} Main view element */ + this.viewEl = viewEls.viewEl; + + /** @type {D3BaseSubView} Upper sub view */ + this.upperSubView = this._createSubView( + this.viewPanelBodyEl, + builder._upperSubViewOptions); + + /** @type {D3BaseSubView} Lower sub view */ + this.lowerSubView = undefined; + if (this.addLowerSubView) { + this.lowerSubView = this._createSubView( + this.viewPanelBodyEl, + builder._lowerSubViewOptions); + } + + /** @type {D3BaseViewHeaderElements} Elements of the view header */ + this.viewHeader = this._createViewHeader(); + + /** @type {D3BaseViewFooterElements} Elements of the view footer */ + this.viewFooter = this._createViewFooter(); + + /** @type {SVGElement} The SVG element in the data table view */ + this.dataTableSVGEl = this._updateDataMetadata(this.dataTableEl); + + /** @type {SVGElement} The SVG element in the metadata view */ + this.metadataTableSVGEl = this._updateDataMetadata(this.metadataTableEl); + + this._addEventListeners(); + } + + /** + * Return a new D3BaseViewBuilder + * + * @return {D3BaseViewBuilder} new Builder + */ + static builder() { + return new D3BaseViewBuilder(); + } + + /** + * Create the metadata table in the 'Metadata' view. + * + * @param {Map<String, Array<String | Number>>} metadata The metadata + */ + createMetadataTable() { + let metadata = this.getMetadata(); + this.viewFooter.metadataBtnEl.removeAttribute('disabled'); + + d3.select(this.metadataTableSVGEl) + .selectAll('*') + .remove(); + + let foreignObjectD3 = d3.select(this.metadataTableSVGEl) + .append('foreignObject') + .attr('height', '100%') + .attr('width', '100%') + .style('overflow', 'scroll'); + + let tableRowsD3 = foreignObjectD3.append('xhtml:table') + .attr('class', 'table table-bordered table-hover') + .append('tbody') + .selectAll('tr') + .data([ ...metadata.keys() ]) + .enter() + .append('tr'); + + tableRowsD3.append('th') + .html((/** @type {String} */ key) => { + return key; + }); + + tableRowsD3.append('td') + .selectAll('p') + .data((/** @type {String} */ key) => { return metadata.get(key); }) + .enter() + .append('p') + .text((/** @type {String | Number} */ val) => { return val; }); + } + + /** + * Return the metadata. + * + * @returns {Map<String, Array<String | Number>>} + */ + getMetadata() { + return new Map(this._metadata); + } + + /** + * Return the plot title HTML text + */ + getTitle() { + return new String(this._titleText); + } + + /** + * Hide the view. + */ + hide() { + this.viewEl.classList.add('hidden'); + } + + /** + * Remove the view. + */ + remove() { + this.viewEl.remove(); + } + + /** + * Show the view. + */ + show() { + this.viewEl.classList.remove('hidden'); + } + + /** + * + * @param {Map<String, Array<String | Number>>} metadata + */ + setMetadata(metadata) { + Preconditions.checkArgumentInstanceOfMap(metadata); + for (let [key, value] of metadata) { + Preconditions.checkArgumentString(key); + Preconditions.checkArgumentArray(value); + } + + this._metadata = metadata; + } + + /** + * Set the plot title. Shows the title in the view header if present. + * + * @param {String} title The plot title + */ + setTitle(title) { + Preconditions.checkArgumentString(title); + this._titleText = title; + this.viewHeader.titleEl.innerHTML = title; + } + + /** + * Update the view size. + * + * @param {String} viewSize The view size: 'min' || 'minCenter' || 'max' + */ + updateViewSize(viewSize) { + d3.select(this.viewEl) + .classed(this.viewOptions.viewSizeMax, false) + .classed(this.viewOptions.viewSizeMin, false) + .classed(this.viewOptions.viewSizeMinCenter, false) + + switch(viewSize) { + case 'minCenter': + this._currentViewSize = 'minCenter'; + d3.select(this.viewEl).classed(this.viewOptions.viewSizeMinCenter, true); + d3.select(this.viewHeader.viewResizeEl) + .attr('class', this.resizeFullIcon); + break; + case 'min': + this._currentViewSize = 'min'; + d3.select(this.viewEl).classed(this.viewOptions.viewSizeMin, true); + d3.select(this.viewHeader.viewResizeEl) + .attr('class', this.resizeFullIcon); + break; + case 'max': + this._currentViewSize = 'max'; + d3.select(this.viewEl).classed(this.viewOptions.viewSizeMax, true); + d3.select(this.viewHeader.viewResizeEl) + .attr('class', this.resizeSmallIcon); + break; + default: + NshmpError.throwError(`View size [${viewSize}] not supported`); + } + } + + /** + * @package + * Add the D3BaseView event listeners. + */ + _addEventListeners() { + this.viewFooter.viewSwitchBtnEl + .addEventListener('click', () => { this._onPlotViewSwitch(); }); + + this.viewHeader.viewResizeEl + .addEventListener('click', () => { this._onViewResize(); }); + + this.viewHeader.titleEl + .addEventListener('input', () => { this._onTitleEntry(); }); + } + + /** + * @package + * Create a lower or upper sub view. + * + * @param {HTMLElement} el Container element to put sub view + * @param {D3BaseSubViewOptions} options Sub view options + */ + _createSubView(el, options) { + return new D3BaseSubView(el, options); + } + + /** + * @package + * Create the D3BaseView footer with 'Plot', 'Data', and + * 'Metadata' buttons and the save menu. + * + * @returns {D3BaseViewFooterElements} The HTMLElements associated with + * the footer + */ + _createViewFooter() { + let viewFooterD3 = d3.select(this.viewPanelEl) + .append('div') + .attr('class', 'panel-footer'); + + let footerToolbarD3 = viewFooterD3.append('div') + .attr('class', 'btn-toolbar footer-btn-toolbar') + .attr('role', 'toolbar'); + + let footerBtnsD3 = footerToolbarD3.selectAll('div') + .data(this._viewFooterButtons()) + .enter() + .append('div') + .attr('class', (d) => { + return `${d.footerBtnGroupColSize} footer-btn-group`; + }) + .append('div') + .attr('class', (d) => { + return `btn-group btn-group-xs btn-group-justified ${d.class}`; + }) + .attr('data-toggle', 'buttons') + .attr('role', 'group'); + + footerBtnsD3.selectAll('label') + .data((d) => { return d.btns; }) + .enter() + .append('label') + .attr('class',(d, i) => { + return `btn btn-xs btn-default footer-button ${d.class}`; + }) + .attr('value', (d) => { return d.value; }) + .attr('for', (d) => { return d.name; }) + .html((d, i) => { + return `<input type='radio' name='${d.name}'` + + ` value='${d.value}' class='check-box'/> ${d.text}`; + }) + .each((d, i, els) => { + if (d.disabled) { + els[i].setAttribute('disabled', 'true'); + } + }) + + let saveMenuEl = this._createSaveMenu(viewFooterD3.node()); + let imageOnlyEl = saveMenuEl.querySelector('#image-only'); + let toolbarEl = footerToolbarD3.node(); + let plotBtnEl = toolbarEl.querySelector('.plot-btn'); + let dataBtnEl = toolbarEl.querySelector('.data-btn'); + let metadataBtnEl = toolbarEl.querySelector('.metadata-btn'); + + let els = new D3BaseViewFooterElements(); + els.btnToolbarEl = toolbarEl; + els.dataBtnEl = dataBtnEl; + els.footerEl = viewFooterD3.node(); + els.imageOnlyEl = imageOnlyEl; + els.metadataBtnEl = metadataBtnEl; + els.plotBtnEl = plotBtnEl; + els.saveMenuEl = saveMenuEl; + els.viewSwitchBtnEl = toolbarEl.querySelector('.plot-data-btns'); + + d3.select(plotBtnEl).classed('active', true); + + return els.checkElements(); + } + + /** + * @package + * Create the D3BaseView header with plot title, legend toggle, + * grid lines toggle, and resize toggle. + * + * @returns {D3BaseViewHeaderElements} The HTMLElements associated with + * the header + */ + _createViewHeader() { + let viewHeaderD3 = d3.select(this.viewPanelEl) + .append('div') + .attr('class', 'panel-heading') + .lower(); + + let viewTitleD3 = viewHeaderD3.append('h2') + .attr('class', 'panel-title') + .style('font-size', `${this.viewOptions.titleFontSize}px`); + + let viewTitleWidth = this.addLegendToggle && + this.addGridLineToggle ? 'calc(100% - 8em)' : + this.addLegendToggle || + this.addGridLineToggle ? 'calc(100% - 5em)' : + 'calc(100% - 2em)'; + + let plotTitleD3 = viewTitleD3.append('div') + .attr('class', 'plot-title') + .attr('contenteditable', true) + .style('width', viewTitleWidth); + + let iconsD3 = viewHeaderD3.append('span') + .attr('class', 'icon'); + + let gridLinesCheckD3 = undefined; + if (this.addGridLineToggle) { + gridLinesCheckD3 = iconsD3.append('div') + .attr('class', 'grid-line-check glyphicon glyphicon-th') + .attr('data-toggle', 'tooltip') + .attr('title', 'Click to toggle grid lines') + .property('checked', true) + .style('margin-right', '2em'); + + $(gridLinesCheckD3.node()).tooltip({container: 'body'}); + gridLinesCheckD3.node().setAttribute('checked', true); + } + + let legendCheckD3 = undefined; + if (this.addLegendToggle) { + legendCheckD3 = iconsD3.append('div') + .attr('class', 'legend-check glyphicon glyphicon-th-list') + .attr('data-toggle', 'tooltip') + .attr('title', 'Click to toggle legend') + .property('checked', true) + .style('margin-right', '2em'); + + $(legendCheckD3.node()).tooltip({container: 'body'}); + legendCheckD3.node().setAttribute('checked', true); + } + + let viewResizeD3 = iconsD3.append('div') + .attr('class',() => { + return this._currentViewSize == 'min' + ? this.resizeFullIcon : this.resizeSmallIcon; + }) + .attr('data-toggle', 'tooltip') + .attr('title', 'Click to resize'); + + $(viewResizeD3.node()).tooltip({container: 'body'}); + + let gridLinesCheckEl = gridLinesCheckD3 != undefined ? + gridLinesCheckD3.node() : undefined; + + let legendCheckEl = legendCheckD3 != undefined ? + legendCheckD3.node() : undefined; + + let els = new D3BaseViewHeaderElements(); + els.gridLinesCheckEl = gridLinesCheckEl; + els.headerEl = viewHeaderD3.node(); + els.iconsEl = iconsD3.node(); + els.legendCheckEl = legendCheckEl; + els.titleContainerEl = viewTitleD3.node(); + els.titleEl = plotTitleD3.node(); + els.viewResizeEl = viewResizeD3.node(); + + return els.checkElements(); + } + + /** + * @package + * Create the main D3BaseView. + * + * @returns {Object<HTMLElement>} The elements associated with + * the view. + */ + _createView() { + let sizeDefault = this.viewOptions.viewSizeDefault; + let viewSize = sizeDefault == 'min' ? this.viewOptions.viewSizeMin : + sizeDefault == 'minCenter' ? this.viewOptions.viewSizeMinCenter : + this.viewOptions.viewSizeMax; + + let containerD3 = d3.select(this.containerEl); + + let elD3 = containerD3.append('div') + .attr('class', 'D3View') + .classed(viewSize, true); + + let plotViewD3 = elD3.append('div') + .attr('class', 'panel panel-default'); + + let viewBodyD3 = plotViewD3.append('div') + .attr('class', 'panel-body panel-outer'); + + let dataTableD3 = viewBodyD3.append('div') + .attr('class', 'data-table panel-table hidden'); + + let metadataTableD3 = viewBodyD3.append('div') + .attr('class', 'metadata-table panel-table hidden'); + + let els = { + viewEl: elD3.node(), + viewPanelEl: plotViewD3.node(), + viewPanelBodyEl: viewBodyD3.node(), + dataTableEl: dataTableD3.node(), + metadataTableEl: metadataTableD3.node(), + }; + + return els; + } + + /** + * @package + * Create the save menu on the D3BaseView footer. + * + * @param {HTMLElement} viewFooterEl The view footer element + */ + _createSaveMenu(viewFooterEl) { + let saveAsD3 = d3.select(viewFooterEl) + .append('span') + .attr('class', 'dropup icon'); + + saveAsD3.append('div') + .attr('class', 'glyphicon glyphicon-save' + + ' footer-button dropdown-toggle') + .attr('data-toggle', 'dropdown') + .attr('aria-hashpop', true) + .attr('aria-expanded', true); + + let saveListD3 = saveAsD3.append('ul') + .attr('class', 'dropdown-menu dropdown-menu-right save-as-menu') + .attr('aria-labelledby', 'save-as-menu') + .style('min-width', 'auto'); + + let saveDataEnter = saveListD3.selectAll('li') + .data(this._saveMenuButtons()) + .enter() + .append('li'); + + saveDataEnter.filter((d) => { return d.format == 'dropdown-header'}) + .text((d) => { return d.label; }) + .attr('class', (d) => { return d.format; }) + .style('cursor', 'initial'); + + saveDataEnter.filter((d) => { return d.format != 'dropdown-header'}) + .style('padding-left', '10px') + .html((d) => { + return `<a data-format=${d.format} data-type=${d.type}> ${d.label} </a>`; + }) + .style('cursor', 'pointer'); + + saveListD3.append('li') + .attr('role', 'seperator') + .attr('class', 'divider'); + + saveListD3.append('li') + .attr('class', 'dropdown-header') + .attr('data-type', 'image-only') + .html('Save/Preview Image Only: ' + + '<input type="checkbox" name="image-only" id="image-only">') + + let saveListEl = saveListD3.node(); + Preconditions.checkStateInstanceOfHTMLElement(saveListEl); + + return saveListD3.node(); + } + + /** + * @package + * Switch between the Plot, Data, and Metadata view. + */ + _onPlotViewSwitch() { + if (event.target.hasAttribute('disabled')) return; + + let selectedView = event.target.getAttribute('value'); + + Preconditions.checkState( + selectedView == 'data' || + selectedView == 'metadata' || + selectedView == 'plot', + `Selected view [${selectedView}] is not supported`); + + this.dataTableEl.classList.toggle( + 'hidden', + selectedView != 'data'); + + this.metadataTableEl.classList.toggle( + 'hidden', + selectedView != 'metadata'); + + this.upperSubView.subViewBodyEl.classList.toggle( + 'hidden', + selectedView != 'plot'); + + if (this.addLowerSubView) { + this.lowerSubView.subViewBodyEl.classList.toggle( + 'hidden', + selectedView != 'plot'); + } + } + + /** + * @package + * Update the title text on input. + */ + _onTitleEntry() { + this._titleText = this.viewHeader.titleEl.innerHTML; + } + + /** + * @package + * Update the view size when the resize toggle is clicked. + */ + _onViewResize() { + this.viewFooter.plotBtnEl.click(); + + let nViews = d3.selectAll('.D3View') + .filter((d, i, els) => { + return !d3.select(els[i]).classed('hidden'); + }).size(); + + switch(this._currentViewSize) { + case 'max': + this._currentViewSize = nViews == 1 ? 'minCenter' : 'min'; + this.updateViewSize(this._currentViewSize); + break; + case 'min': + case 'minCenter': + this._currentViewSize = 'max'; + this.updateViewSize(this._currentViewSize); + break; + default: + NshmpError.throwError(`View size [${this._currentViewSize}] not supported`); + } + } + + /** + * @private + * + * The save menu buttons. + * + * @returns {Array<Object>} The save menu buttons + */ + _saveMenuButtons() { + let buttons = [ + { label: 'Preview Figure as:', format: 'dropdown-header', type: 'preview-figure' }, + { label: 'JPEG', format: 'jpeg', type: 'preview-figure' }, + { label: 'PNG', format: 'png', type: 'preview-figure' }, + { label: 'SVG', format: 'svg', type: 'preview-figure' }, + { label: 'Save Figure As:', format: 'dropdown-header', type: 'save-figure' }, + { label: 'JPEG', format: 'jpeg', type: 'save-figure' }, + { label: 'PNG', format: 'png', type: 'save-figure' }, + { label: 'SVG', format: 'svg', type: 'save-figure' }, + { label: 'Save Data As:', format: 'dropdown-header', type: 'save-data' }, + { label: 'CSV', format: 'csv', type: 'save-data' }, + ]; + + return buttons; + } + + /** + * @private + * Add SVG element to the data and metadata view to match the + * SVG element in the plot view. + * + * @param {HTMLElement} el The data or metadata element + */ + _updateDataMetadata(el) { + Preconditions.checkArgumentInstanceOfHTMLElement(el); + + let plotHeight = this.addLowerSubView ? + this.upperSubView.options.plotHeight + this.lowerSubView.options.plotHeight + 1: + this.upperSubView.options.plotHeight; + + let plotWidth = this.upperSubView.options.plotWidth; + + let svgD3 = d3.select(el) + .append('svg') + .attr('version', 1.1) + .attr('xmlns', 'http://www.w3.org/2000/svg') + .attr('preserveAspectRatio', 'xMinYMin meet') + .attr('viewBox', `0 0 ` + + `${plotWidth} ${plotHeight}`); + + let svgEl = svgD3.node(); + Preconditions.checkStateInstanceOfSVGElement(svgEl); + return svgEl; + } + + /** + * @package + * The D3BaseView footer buttons: Plot, Data, and Metadata. + * + * @returns {Array<Object>} The buttons + */ + _viewFooterButtons() { + let buttons = [ + { + class: 'plot-data-btns', + footerBtnGroupColSize: 'col-xs-offset-3 col-xs-6', + btnGroupColSize: 'col-xs-12', + btns: [ + { + name: 'plot', + value: 'plot', + text: 'Plot', + class: 'plot-btn', + disabled: false, + }, { + name: 'data', + value: 'data', + text: 'Data', + class: 'data-btn', + disabled: true, + }, { + name: 'metadata', + value: 'metadata', + text: 'Metadata', + class: 'metadata-btn', + disabled: true, + } + ] + } + ]; + + return buttons; + } + +} + +/** + * @fileoverview Builder for D3BaseView. + * + * Use D3BaseView.builder() for new instance of builder. + * + * @class D3BaseViewBuilder + * @author Brandon Clayton + */ +export class D3BaseViewBuilder { + + /** @private */ + constructor() { + this._setDefaultViewOptions(); + + /** @type {Boolean} */ + this._addGridLineToggle = true; + + /** @type {Boolean} */ + this._addLegendToggle = true; + + /** @type {Boolean} */ + this._addLowerSubView = false; + + /** @type {HTMLElement} */ + this._containerEl = undefined; + } + + /** + * Return a new D3BaseView + */ + build() { + Preconditions.checkNotUndefined( + this._containerEl, + 'Container element not set'); + return new D3BaseView(this); + } + + /** + * Whether to add a grid line toogle in the view's header. + * Default: true + * + * @param {Boolean} bool Whether to add the grid line toogle + */ + addGridLineToogle(bool) { + Preconditions.checkArgumentBoolean(bool); + this._addGridLineToggle = bool; + return this; + } + + /** + * Whether to add a legend toogle in the view's header. + * Default: true + * + * @param {Boolean} bool Whether to add the legend toogle + */ + addLegendToggle(bool) { + Preconditions.checkArgumentBoolean(bool); + this._addLegendToggle = bool; + return this; + } + + /** + * Whether to add a lower sub view; + * adds the ability to have an upper and lower plot in a single view. + * + * Default D3BaseSubViewOptions are applied from + * D3BaseSubViewOptions.lowerWithDefaults(). + * + * Use Builder.setLowerSubViewOptions to set custom settings. + * + * Default: false + */ + addLowerSubView(bool) { + Preconditions.checkArgumentBoolean(bool); + this._addLowerSubView = bool; + return this; + } + + /** + * Set the container element, where the view will be appended to. + * + * @param {HTMLElement} el The container element to put the view. + */ + containerEl(el) { + Preconditions.checkArgumentInstanceOfHTMLElement(el); + this._containerEl = el; + return this; + } + + /** + * Set the lower sub view options. + * + * @param {D3BaseSubViewOptions} options The lower sub view options. + */ + lowerSubViewOptions(options) { + Preconditions.checkArgumentInstanceOf(options, D3BaseSubViewOptions); + this._lowerSubViewOptions = options; + return this; + } + + /** + * Set the upper sub view options. + * + * @param {D3BaseSubViewOptions} options The upper sub view options. + */ + upperSubViewOptions(options) { + Preconditions.checkArgumentInstanceOf(options, D3BaseSubViewOptions); + this._upperSubViewOptions = options; + return this; + } + + /** + * Set the view options. + * + * @param {D3BaseViewOptions} options The view options. + */ + viewOptions(options) { + Preconditions.checkArgumentInstanceOf(options, D3BaseViewOptions); + this._viewOptions = options; + return this; + } + + /** + * @private + * Set the default view options + */ + _setDefaultViewOptions() { + /** @type {D3BaseViewOptions} */ + this._viewOptions = D3BaseViewOptions.withDefaults(); + + /** @type {D3BaseSubViewOptions} */ + this._upperSubViewOptions = D3BaseSubViewOptions.upperWithDefaults(); + + /** @type {D3BaseSubViewOptions} */ + this._lowerSubViewOptions = D3BaseSubViewOptions.lowerWithDefaults(); + } + +} + +/** + * @fileoverview Container class for D3BaseView footer elements. + * + * @class D3BaseViewFooterElements + * @author Brandon Clayton + */ +export class D3BaseViewFooterElements { + + constructor() { + /** @type {HTMLElement} The footer button toolbar element */ + this.btnToolbarEl = undefined; + + /** @type {HTMLElement} The 'Data' button element on the button toolbar */ + this.dataBtnEl = undefined; + + /** @type {HTMLElement} The footer element */ + this.footerEl = undefined; + + /** @type {HTMLElement} The image only check box element in the save menu */ + this.imageOnlyEl = undefined; + + /** @type {HTMLElement} The 'Metadata' button element on the button toolbar */ + this.metadataBtnEl = undefined; + + /** @type {HTMLElement} The 'Plot' button element on the button toolbar */ + this.plotBtnEl = undefined; + + /** @type {HTMLElement} The save menu element */ + this.saveMenuEl = undefined; + + /** @type {HTMLElement} The plot, data, and metadata container element */ + this.viewSwitchBtnEl = undefined; + } + + /** + * Check that all elements are set. + * + * @returns {D3BaseViewFooterElements} The elements + */ + checkElements() { + for (let value of Object.values(this)) { + Preconditions.checkNotUndefined(value); + } + + Preconditions.checkStateInstanceOfHTMLElement(this.btnToolbarEl); + Preconditions.checkStateInstanceOfHTMLElement(this.dataBtnEl); + Preconditions.checkStateInstanceOfHTMLElement(this.footerEl); + Preconditions.checkStateInstanceOfHTMLElement(this.imageOnlyEl); + Preconditions.checkStateInstanceOfHTMLElement(this.metadataBtnEl); + Preconditions.checkStateInstanceOfHTMLElement(this.plotBtnEl); + Preconditions.checkStateInstanceOfHTMLElement(this.saveMenuEl); + Preconditions.checkStateInstanceOfHTMLElement(this.viewSwitchBtnEl); + + return this; + } + +} + +/** + * @fileoverview Container class for D3BaseView header elements. + * + * @class D3BaseViewHeaderElements + * @author Brandon Clayton + */ +export class D3BaseViewHeaderElements { + + constructor() { + /** @type {HTMLElement} The grid line check icon element */ + this.gridLinesCheckEl = undefined; + + /** @type {HTMLElement} The header element */ + this.headerEl = undefined; + + /** @type {HTMLElement} The icons element */ + this.iconsEl = undefined; + + /** @type {HTMLElement} The legend check icon element */ + this.legendCheckEl = undefined; + + /** @type {HTMLElement} The title container element */ + this.titleContainerEl = undefined; + + /** @type {HTMLElement} The title element */ + this.titleEl = undefined; + + /** @type {HTMLElement} The resize icon element */ + this.viewResizeEl = undefined; + } + + /** + * Check that all elements are set. + * + * @returns {D3BaseViewHeaderElements} The elements + */ + checkElements() { + for (let value of Object.values(this)) { + Preconditions.checkNotUndefined(value); + } + + Preconditions.checkStateInstanceOfHTMLElement(this.gridLinesCheckEl); + Preconditions.checkStateInstanceOfHTMLElement(this.headerEl); + Preconditions.checkStateInstanceOfHTMLElement(this.iconsEl); + Preconditions.checkStateInstanceOfHTMLElement(this.legendCheckEl); + Preconditions.checkStateInstanceOfHTMLElement(this.titleContainerEl); + Preconditions.checkStateInstanceOfHTMLElement(this.titleEl); + Preconditions.checkStateInstanceOfHTMLElement(this.viewResizeEl); + + return this; + } + +} diff --git a/webapp/apps/js/d3/view/D3LineSubView.js b/webapp/apps/js/d3/view/D3LineSubView.js new file mode 100644 index 000000000..3121be74d --- /dev/null +++ b/webapp/apps/js/d3/view/D3LineSubView.js @@ -0,0 +1,205 @@ + +import { D3BaseSubView, D3BaseSubViewSVGElements } from './D3BaseSubView.js'; +import { D3LineSubViewOptions } from '../options/D3LineSubViewOptions.js'; + +import { Preconditions } from '../../error/Preconditions.js'; + +/** + * @package + * @fileoverview Create a sub view for a D3LineView. Adds the + * line plot SVG structure for a line plot. + * + * @class D3LineSubView + * @extends D3BaseSubView + * @author Brandon Clayton + */ +export class D3LineSubView extends D3BaseSubView { + + /** + * Create a new sub view for D3LineView + * + * @param {HTMLElement} containerEl Container element to append sub view + * @param {D3LineSubViewOptions} options The sub view options + */ + constructor(containerEl, options) { + super(containerEl, options); + + /* Update types */ + /** @type {D3LineSubViewSVGElements} Line plot SVG elements */ + this.svg; + + /** @type {D3LineSubViewOptions} Sub view options for line plot */ + this.options; + + } + + /** + * @override + * @package + * Create the sub view SVG structure for a line plot. + * + * @returns {D3LineSubViewSVGElements} The SVG elements + */ + _createSVGStructure() { + let svg = super._createSVGStructure(); + let svgEl = svg.svgEl; + let outerPlotEl = svg.outerPlotEl; + let innerPlotEl = svg.innerPlotEl; + let tooltipEl = svg.tooltipEl; + + /* Grid Lines */ + let gridLinesD3 = d3.select(innerPlotEl) + .append('g') + .attr('class', 'grid-lines'); + let xGridLinesD3 = gridLinesD3.append('g') + .attr('class', 'x-grid-lines'); + let yGridLinesD3 = gridLinesD3.append('g') + .attr('class', 'y-grid-lines'); + + /* X Axis */ + let xAxisD3 = d3.select(innerPlotEl) + .append('g') + .attr('class', 'x-axis'); + let xTickMarksD3 = xAxisD3.append('g') + .attr('class', 'x-tick-marks'); + let xLabelD3 = xAxisD3.append('text') + .attr('class', 'x-label') + .attr('fill', 'black'); + + /* Y Axis */ + let yAxisD3 = d3.select(innerPlotEl) + .append('g') + .attr('class', 'y-axis'); + let yTickMarksD3 = yAxisD3.append('g') + .attr('class', 'y-tick-marks'); + let yLabelD3 = yAxisD3.append('text') + .attr('class', 'y-label') + .attr('fill', 'black') + .attr('transform', 'rotate(-90)'); + + /* Data Container Group */ + let dataContainerD3 = d3.select(innerPlotEl) + .append('g') + .attr('class', 'data-container-group'); + + /* Legend Group */ + let legendD3 = d3.select(innerPlotEl) + .append('g') + .attr('class', 'legend') + .style('line-height', '1.5'); + + let legendForeignObjectD3 = legendD3.append('foreignObject'); + + let legendTableD3 = legendForeignObjectD3 + .append('xhtml:table') + .attr('xmlns', 'http://www.w3.org/1999/xhtml'); + + d3.select(tooltipEl).raise(); + + let els = new D3LineSubViewSVGElements(); + els.dataContainerEl = dataContainerD3.node(); + els.gridLinesEl = gridLinesD3.node(); + els.legendEl = legendD3.node(); + els.legendForeignObjectEl = legendForeignObjectD3.node(); + els.legendTableEl = legendTableD3.node(); + els.innerFrameEl = svg.innerFrameEl; + els.innerPlotEl = innerPlotEl; + els.outerFrameEl = svg.outerFrameEl; + els.outerPlotEl = outerPlotEl; + els.svgEl = svgEl; + els.tooltipEl = tooltipEl; + els.tooltipForeignObjectEl = svg.tooltipForeignObjectEl; + els.tooltipTableEl = svg.tooltipTableEl; + els.xAxisEl = xAxisD3.node(); + els.xGridLinesEl = xGridLinesD3.node(); + els.xLabelEl = xLabelD3.node(); + els.xTickMarksEl = xTickMarksD3.node(); + els.yAxisEl = yAxisD3.node(); + els.yGridLinesEl = yGridLinesD3.node(); + els.yLabelEl = yLabelD3.node(); + els.yTickMarksEl = yTickMarksD3.node(); + + return els.checkElements(); + } + +} + +/** + * @fileoverview Container class for the D3LineSubView SVG elements + * + * @class D3LineSubViewSVGElements + * @extends D3BaseSubViewSVGElements + * @author Brandon Clayton + */ +export class D3LineSubViewSVGElements extends D3BaseSubViewSVGElements { + + constructor() { + super(); + + /** @type {SVGElement} The data group element */ + this.dataContainerEl = undefined; + + /** @type {SVGElement} The grid lines group element */ + this.gridLinesEl = undefined; + + /** @type {SVGElement} The legend group element*/ + this.legendEl = undefined; + + /** @type {SVGElement} The legend foreign object element */ + this.legendForeignObjectEl = undefined; + + /** @type {HTMLElement} The table element*/ + this.legendTableEl = undefined; + + /** @type {SVGElement} The X axis group element */ + this.xAxisEl = undefined; + + /** @type {SVGElement} The X axis grid lines group element */ + this.xGridLinesEl = undefined; + + /** @type {SVGElement} The X label text element */ + this.xLabelEl = undefined; + + /** @type {SVGElement} The X axis tick marks group element */ + this.xTickMarksEl = undefined; + + /** @type {SVGElement} The Y axis group element */ + this.yAxisEl = undefined; + + /** @type {SVGElement} The Y axis grid lines group element */ + this.yGridLinesEl = undefined; + + /** @type {SVGElement} The Y label text element */ + this.yLabelEl = undefined; + + /** @type {SVGElement} The Y axis tick marks group element */ + this.yTickMarksEl = undefined; + } + + /** + * @override + * Check the elements. + * + * @returns {D3LineSubViewSVGElements} The elements + */ + checkElements() { + super.checkElements(); + + Preconditions.checkStateInstanceOfSVGElement(this.dataContainerEl); + Preconditions.checkStateInstanceOfSVGElement(this.gridLinesEl); + Preconditions.checkStateInstanceOfSVGElement(this.legendEl); + Preconditions.checkStateInstanceOfSVGElement(this.legendForeignObjectEl); + Preconditions.checkStateInstanceOfHTMLElement(this.legendTableEl); + Preconditions.checkStateInstanceOfSVGElement(this.xAxisEl); + Preconditions.checkStateInstanceOfSVGElement(this.xGridLinesEl); + Preconditions.checkStateInstanceOfSVGElement(this.xLabelEl); + Preconditions.checkStateInstanceOfSVGElement(this.xTickMarksEl); + Preconditions.checkStateInstanceOfSVGElement(this.yAxisEl); + Preconditions.checkStateInstanceOfSVGElement(this.yGridLinesEl); + Preconditions.checkStateInstanceOfSVGElement(this.yLabelEl); + Preconditions.checkStateInstanceOfSVGElement(this.yTickMarksEl); + + return this; + } + +} diff --git a/webapp/apps/js/d3/view/D3LineView.js b/webapp/apps/js/d3/view/D3LineView.js new file mode 100644 index 000000000..d078ce2be --- /dev/null +++ b/webapp/apps/js/d3/view/D3LineView.js @@ -0,0 +1,488 @@ + +import { D3BaseView, D3BaseViewFooterElements } from './D3BaseView.js'; +import { D3BaseViewBuilder } from './D3BaseView.js'; +import { D3LineData } from '../data/D3LineData.js'; +import { D3LineSeriesData } from '../data/D3LineSeriesData.js'; +import { D3LineSubView } from './D3LineSubView.js'; +import { D3LineSubViewOptions } from '../options/D3LineSubViewOptions.js'; +import { D3LineViewOptions } from '../options/D3LineViewOptions.js'; +import { D3XYPair } from '../data/D3XYPair.js'; + +import { Preconditions } from '../../error/Preconditions.js'; + +/** + * @fileoverview Create a view for line plots. The view can + * contain an upper and lower D3LineSubView for multiple SVG + * plots in a single D3LineView. + * + * Must use D3LineView.builder() to create a D3LineView instance. + * See D3LineViewBuilder. + * + * @class D3LineView + * @extends D3BaseView + * @author Brandon Clayton + */ +export class D3LineView extends D3BaseView { + + /** + * @private + * Must use D3LineView.builder() to create new instance of D3LineView + * + * @param {D3LineViewBuilder} builder The builder + */ + constructor(builder) { + super(builder); + + /** @type {Array<D3LineData>} The data that will be saved to file */ + this._saveData = undefined; + + /* Update types */ + /** @type {D3LineSubView} Lower sub view */ + this.lowerSubView; + + /** @type {D3LineSubView} Upper sub view */ + this.upperSubView; + + /** @type {D3LineViewFooterElements} The Footer elements */ + this.viewFooter; + + /** @type {D3LineViewOptions} */ + this.viewOptions; + } + + /** + * @override + * Return a new D3LineViewBuilder + * + * @returns {D3LineViewBuilder} new Builder + */ + static builder() { + return new D3LineViewBuilder(); + } + + /** + * Create the data table in the 'Data' view. + * + * @param {...D3LineData} lineDatas The line datas + */ + createDataTable(...lineDatas) { + Preconditions.checkArgumentArrayInstanceOf(lineDatas, D3LineData); + this.viewFooter.dataBtnEl.removeAttribute('disabled'); + + d3.select(this.dataTableSVGEl) + .selectAll('*') + .remove(); + + let foreignObjectD3 = d3.select(this.dataTableSVGEl) + .append('foreignObject') + .attr('height', '100%') + .attr('width', '100%') + .style('overflow', 'scroll') + .style('padding', '5px'); + + for (let lineData of lineDatas) { + let divD3 = foreignObjectD3.append('xhtml:div'); + + divD3.append('h3').text(lineData.subView.options.label); + + let tableD3 = divD3.append('table') + .attr('class', 'table table-bordered table-condensed') + .append('tbody'); + + let tableEl = tableD3.node(); + + for (let series of lineData.series) { + this._addSeriesToDataTable(tableEl, lineData, series); + } + } + + } + + /** + * Get the D3LineData that will be saved to a file(s). + * + * @return {Array<D3LineData>} The line data to save + */ + getSaveData() { + return this._saveData; + } + + /** + * Get the X axis scale based on the D3LineViewOptions.synXAxisScale + * and D3LineSubViewOptions.xAxisScale. + * + * @param {D3LineSubView} subView + * @returns {String} The X axis scale: 'log' || 'linear' + */ + getXAxisScale(subView) { + Preconditions.checkArgumentInstanceOf(subView, D3LineSubView); + + return this.viewOptions.syncXAxisScale ? this.viewOptions.xAxisScale : + subView.options.xAxisScale; + } + /** + * Get the Y axis scale based on the D3LineViewOptions.synYAxisScale + * and D3LineSubViewOptions.yAxisScale. + * + * @param {D3LineSubView} subView + * @returns {String} The Y axis scale: 'log' || 'linear' + */ + getYAxisScale(subView) { + Preconditions.checkArgumentInstanceOf(subView, D3LineSubView); + + return this.viewOptions.syncYAxisScale ? this.viewOptions.yAxisScale : + subView.options.yAxisScale; + } + + /** + * Set the D3LineDatas that will be saved to a file. + * @param {...D3LineData} lineDatas + */ + setSaveData(...lineDatas) { + Preconditions.checkArgumentArrayInstanceOf(lineDatas, D3LineData); + this._saveData = lineDatas; + } + + /** + * Add the Array<D3XYPair> to the data table. + * + * @param {D3LineData} lineData The line data + * @param {HTMLElement} tableEl The table element + * @param {D3LineSeriesData} series The series data + * @param {String} label The X/Y label + * @param {String} axis The axis: 'x' || 'y' + */ + _addDataToDataTable(lineData, tableEl, series, label, axis) { + Preconditions.checkArgumentInstanceOf(lineData, D3LineData); + Preconditions.checkArgumentInstanceOfHTMLElement(tableEl); + Preconditions.checkArgumentInstanceOf(series, D3LineSeriesData); + Preconditions.checkArgumentString(label); + Preconditions.checkArgument( + axis == 'x' || axis == 'y', + `Axis [${axis}] not supported for data table`); + + let rowD3 = d3.select(tableEl).append('tr'); + rowD3.append('th') + .attr('nowrap', true) + .text(label); + + let fractionDigits = lineData.subView + .options[`${axis}ExponentFractionDigits`]; + + let toExponent = lineData.subView.options[`${axis}ValueToExponent`]; + + rowD3.selectAll('td') + .data(series.data) + .enter() + .append('td') + .text((/** @type {D3XYPair} */ xyPair) => { + Preconditions.checkStateInstanceOf(xyPair, D3XYPair); + + let val = toExponent ? + xyPair[axis].toExponential(fractionDigits) : + xyPair[axis]; + + return xyPair[`${axis}String`] || val; + }); + } + + /** + * Add the D3LineSeriesData to the data table. + * + * @param {HTMLElement} tableEl The table element + * @param {D3LineData} lineData The line data + * @param {D3LineSeriesData} series The series data + */ + _addSeriesToDataTable(tableEl, lineData, series) { + Preconditions.checkArgumentInstanceOfHTMLElement(tableEl); + Preconditions.checkArgumentInstanceOf(lineData, D3LineData); + Preconditions.checkArgumentInstanceOf(series, D3LineSeriesData); + + d3.select(tableEl) + .append('tr') + .append('th') + .attr('nowrap', true) + .attr('colspan', series.data.length + 1) + .append('h4') + .style('margin', '0') + .text(series.lineOptions.label); + + let xLabel = lineData.subView.options.xLabel; + this._addDataToDataTable(lineData, tableEl, series, xLabel, 'x'); + + let yLabel = lineData.subView.options.yLabel; + this._addDataToDataTable(lineData, tableEl, series, yLabel, 'y'); + } + + /** + * @private + * Create the X axis buttons on the view's footer. + */ + _addXAxisBtns(footer) { + let xAxisBtnEl = footer.btnToolbarEl.querySelector('.x-axis-btns'); + let xLinearBtnEl = xAxisBtnEl.querySelector('.x-linear-btn'); + let xLogBtnEl = xAxisBtnEl.querySelector('.x-log-btn'); + + if (this.viewOptions.syncXAxisScale) { + let xScaleEl = this.viewOptions.xAxisScale == 'log' ? + xLogBtnEl : xLinearBtnEl; + + xScaleEl.classList.add('active'); + } else { + let xScaleEl = this.upperSubView.options.xAxisScale == 'log' ? + xLogBtnEl : xLinearBtnEl; + + xScaleEl.classList.add('active'); + } + + let els = { + xAxisBtnEl: xAxisBtnEl, + xLinearBtnEl: xLinearBtnEl, + xLogBtnEl: xLogBtnEl + }; + return els; + } + + /** + * @private + * Create the Y axis buttons on the view's footer + */ + _addYAxisBtns(footer) { + let yAxisBtnEl = footer.btnToolbarEl.querySelector('.y-axis-btns'); + let yLinearBtnEl = yAxisBtnEl.querySelector('.y-linear-btn'); + let yLogBtnEl = yAxisBtnEl.querySelector('.y-log-btn'); + + if (this.viewOptions.syncYAxisScale) { + let yScaleEl = this.viewOptions.yAxisScale == 'log' ? + yLogBtnEl : yLinearBtnEl; + + yScaleEl.classList.add('active'); + } else { + let yScaleEl = this.upperSubView.options.yAxisScale == 'log' ? + yLogBtnEl : yLinearBtnEl; + + yScaleEl.classList.add('active'); + } + + let els = { + yAxisBtnEl: yAxisBtnEl, + yLinearBtnEl: yLinearBtnEl, + yLogBtnEl: yLogBtnEl + }; + return els; + } + + /** + * @override + * @package + * Create the sub views. + * + * @param {HTMLElement} el Container element to append sub view + * @param {D3LineSubViewOptions} options Sub view options + * @returns {D3LineSubView} The line sub view + */ + _createSubView(el, options) { + return new D3LineSubView(el, options); + } + + /** + * @override + * @package + * Create the D3LineView footer. + * + * @return {D3LineViewFooterElements} The elements associated with the footer + */ + _createViewFooter() { + let footer = super._createViewFooter(); + + let xAxisEls = this._addXAxisBtns(footer); + let yAxisEls = this._addYAxisBtns(footer); + + let els = new D3LineViewFooterElements(); + els.btnToolbarEl = footer.btnToolbarEl; + els.dataBtnEl = footer.dataBtnEl; + els.footerEl = footer.footerEl; + els.imageOnlyEl = footer.imageOnlyEl; + els.metadataBtnEl = footer.metadataBtnEl; + els.plotBtnEl = footer.plotBtnEl; + els.saveMenuEl = footer.saveMenuEl; + els.viewSwitchBtnEl = footer.viewSwitchBtnEl; + + els.xAxisBtnEl = xAxisEls.xAxisBtnEl; + els.xLinearBtnEl = xAxisEls.xLinearBtnEl; + els.xLogBtnEl = xAxisEls.xLogBtnEl; + els.yAxisBtnEl = yAxisEls.yAxisBtnEl; + els.yLinearBtnEl = yAxisEls.yLinearBtnEl; + els.yLogBtnEl = yAxisEls.yLogBtnEl; + + + return els.checkElements(); + } + + /** + * @override + * The view footer buttons + */ + _viewFooterButtons() { + let buttons = [ + { + class: 'x-axis-btns', + footerBtnGroupColSize: 'col-xs-3', + btnGroupColSize: 'col-xs-12', + btns: [ + { + name: 'x-axis-x', + value: 'linear', + text: 'X: Linear', + class: 'x-linear-btn', + disabled: this.viewOptions.disableXAxisBtns, + }, { + name: 'x-axis-y', + value: 'log', + text: 'X: Log', + class: 'x-log-btn', + disabled: this.viewOptions.disableXAxisBtns, + } + ] + }, { + class: 'y-axis-btns', + footerBtnGroupColSize: 'col-xs-3', + btnGroupColSize: 'col-xs-12', + btns: [ + { + name: 'y-axis-x', + value: 'linear', + text: 'Y: Linear', + class: 'y-linear-btn', + disabled: this.viewOptions.disableYAxisBtns, + }, { + name: 'y-axis-y', + value: 'log', + text: 'Y: Log', + class: 'y-log-btn', + disabled: this.viewOptions.disableYAxisBtns, + } + ] + }, { + class: 'plot-data-btns', + footerBtnGroupColSize: 'col-xs-6', + btnGroupColSize: 'col-xs-12', + btns: [ + { + name: 'plot', + value: 'plot', + text: 'Plot', + class: 'plot-btn', + disabled: false, + }, { + name: 'data', + value: 'data', + text: 'Data', + class: 'data-btn', + disabled: true, + }, { + name: 'metadata', + value: 'metadata', + text: 'Metadata', + class: 'metadata-btn', + disabled: true, + } + ] + } + ]; + + return buttons; + } + +} + +/** + * @fileoverview Builder for D3LineView. + * + * Use D3LineView.builder() for new instance of builder. + * + * @class D3LineViewBuilder + * @extends D3BaseViewBuilder + * @author Brandon Clayton + */ +export class D3LineViewBuilder extends D3BaseViewBuilder { + + /** @private */ + constructor() { + super(); + } + + /** + * Returns a new D3LineView instance + */ + build() { + return new D3LineView(this); + } + + /** + * @override + * @private + * Set the default line view options + */ + _setDefaultViewOptions() { + /** @type {D3LineViewOptions} */ + this._viewOptions = D3LineViewOptions.withDefaults(); + + /** @type {D3LineSubViewOptions} */ + this._upperSubViewOptions = D3LineSubViewOptions.upperWithDefaults(); + + /** @type {D3LineSubViewOptions} */ + this._lowerSubViewOptions = D3LineSubViewOptions.lowerWithDefaults(); + } + +} + +/** + * @fileoverview Container class for D3LineView footer elements + * + * @class D3LineViewFooterElements + * @extends D3BaseViewFooterElements + * @author Brandon Clayton + */ +export class D3LineViewFooterElements extends D3BaseViewFooterElements { + + constructor() { + super(); + + /** @type {HTMLElement} The X axis button element */ + this.xAxisBtnEl = undefined; + + /** @type {HTMLElement} The X axis linear button element */ + this.xLinearBtnEl = undefined; + + /** @type {HTMLElement} The X axis log button element */ + this.xLogBtnEl = undefined; + + /** @type {HTMLElement} The Y axis button element */ + this.yAxisBtnEl = undefined; + + /** @type {HTMLElement} The Y axis linear button element */ + this.yLinearBtnEl = undefined; + + /** @type {HTMLElement} The Y axis log button element */ + this.yLogBtnEl = undefined; + } + + /** + * @override + * Check that the elements are set + */ + checkElements() { + super.checkElements(); + + Preconditions.checkStateInstanceOfHTMLElement(this.xAxisBtnEl); + Preconditions.checkStateInstanceOfHTMLElement(this.xLinearBtnEl); + Preconditions.checkStateInstanceOfHTMLElement(this.xLogBtnEl); + Preconditions.checkStateInstanceOfHTMLElement(this.yAxisBtnEl); + Preconditions.checkStateInstanceOfHTMLElement(this.yLinearBtnEl); + Preconditions.checkStateInstanceOfHTMLElement(this.yLogBtnEl); + + return this; + } + +} diff --git a/webapp/apps/js/error/NshmpError.js b/webapp/apps/js/error/NshmpError.js new file mode 100644 index 000000000..793a80c54 --- /dev/null +++ b/webapp/apps/js/error/NshmpError.js @@ -0,0 +1,253 @@ + +/** + * @fileoverview Error class that will create a Bootstrap modal + * with the error message. + * + * @extends Error + * @author Brandon Clayton + */ +export default class NshmpError extends Error { + + /** + * Create a Boostrap modal with an error message. + * + * @param {String} errorMessage The error message to display. + */ + constructor(errorMessage) { + super(errorMessage); + + if (errorMessage instanceof NshmpError) { + console.error(errorMessage); + return; + } + + this.message = errorMessage; + try { + let els = this._createErrorModal(); + this.el = els.get('el'); + this.headerEl = els.get('headerEl'); + this.bodyEl = els.get('bodyEl'); + this.footerEl = els.get('footerEl'); + + $(this.el).modal({backdrop: 'static'}); + + $(this.el).on('hidden.bs.modal', (event) => { + d3.select(this.el).remove(); + }); + } catch (err) { + alert(`${err} \n ${this.message}`); + } + + if (Error.captureStackTrace) { + Error.captureStackTrace(this, NshmpError); + } + } + + /** + * Check an array of web service responses to see if any web service + * response has "status" = "error". + * + * If a web service has an error a NshmpError is thrown + * + * If a web service response has status error and the + * supplied plot has a method clearData, it will be invoked + * for the upper panel and lower panel. + * + * @param {Array<Object>} responses The web service responses + * @param {D3LinePlot || D3GeoDeagg} plots The plots to clear + */ + static checkResponses(responses, ...plots) { + let errorMessage = ''; + let hasError = false; + + for (let response of responses) { + let status = response.status; + if (status == 'error') { + hasError = true; + errorMessage += `<p> ${response.message} </p> \n`; + } + } + + if (hasError) { + for (let plot of plots) { + if (plot.clearData) { + plot.clearData(plot.upperPanel); + plot.clearData(plot.lowerPanel); + } + } + + throw new NshmpError(errorMessage); + } + } + + /** + * Check a web service response to see for "status" = "error". + * + * If a web service has an error, a native JavaScript + * Error is thrown to allow a catch method to catch it. + * + * If a web service response has status error and the + * supplied plot has a method clearData, it will be invoked + * for the upper panel and lower panel. + * + * @param {Object} response + * @param {D3LinePLot || D3GeoDeagg} plots The plots to clear + */ + static checkResponse(response, ...plots) { + return NshmpError.checkResponses([response], ...plots); + } + + /** + * Convience method to throw a new NshmpError. + * If the error message equals 'cancal' an error is not thrown, + * useful when canceling a Promise. + * + * @param {String} errorMessage The exception message to use + */ + static throwError(errorMessage) { + if (errorMessage instanceof NshmpError) { + console.error(errorMessage); + return + } + + if (errorMessage == 'cancel') return; + + throw new NshmpError(errorMessage); + } + + /** + * Create the Bootstrap modal + */ + _createErrorModal() { + /* Modal */ + let overlayD3 = d3.select('body') + .append('div') + .attr('class', 'modal error-modal') + .attr('id', 'error-modal') + .attr('tabindex', '-1') + .attr('role', 'dialog'); + + /* Modal content */ + let contentD3 = overlayD3.append('div') + .attr('class', 'modal-dialog vertical-center') + .attr('role', 'document') + .style('display', 'grid') + .style('margin', '0 auto') + .append('div') + .attr('class', 'modal-content') + .style('overflow', 'hidden'); + + let contentEl = contentD3.node(); + let el = overlayD3.node(); + + let headerEl = this._createModalHeader(contentEl); + let bodyEl = this._createModalBody(contentEl); + let footerEl = this._createModalFooter(contentEl); + + let els = new Map(); + els.set('el', el); + els.set('headerEl', headerEl); + els.set('bodyEl', bodyEl); + els.set('footerEl', footerEl); + + return els; + } + + /** + * Add modal footer with collapsible panel with stack trace + * @param {HTMLElement} modalEl + */ + _createModalFooter(modalEl) { + let footerD3 = d3.select(modalEl) + .append('div') + .attr('class', 'panel-footer'); + + let footerTextD3 = footerD3.append('div') + .attr('role', 'button') + .attr('data-toggle', 'collapse') + .attr('data-parent', '#error-modal') + .attr('href', '#stack-trace') + .attr('aria-expanded', 'false') + .attr('aria-controls', 'stack-trace') + .text('Stack trace'); + + let chevronD3 = footerTextD3.append('span') + .attr('class', 'pull-right glyphicon glyphicon-chevron-down'); + + let collapseD3 = d3.select(modalEl) + .append('div') + .attr('class', 'panel-collapse collapse') + .attr('id', 'stack-trace') + .attr('role', 'tabpanel'); + + collapseD3.append('div') + .attr('class', 'panel-body') + .text(this.stack); + + let collapseEl = collapseD3.node(); + let chevronEl = chevronD3.node(); + this._collapseStackTraceListener(collapseEl, chevronEl); + + return footerD3.node(); + } + + /** + * Set event listeners for the collapsing panel + * @param {HTMLElement} collapseEl + * @param {HTMLElement} chevronEl + */ + _collapseStackTraceListener(collapseEl, chevronEl) { + let chevronDown = 'glyphicon-chevron-down'; + let chevronUp = 'glyphicon-chevron-up'; + + $(collapseEl).on('show.bs.collapse', () => { + chevronEl.classList.remove(chevronDown); + chevronEl.classList.add(chevronUp); + }); + + $(collapseEl).on('hide.bs.collapse', () => { + chevronEl.classList.remove(chevronUp); + chevronEl.classList.add(chevronDown); + }); + } + + /** + * Create the modal header + * @param {HTMLElement} modalEl The modal element + */ + _createModalHeader(modalEl) { + let headerD3 = d3.select(modalEl) + .append('div') + .attr('class', 'modal-header') + .style('background-color', '#EF9A9A'); + + headerD3.append('button') + .attr('type', 'button') + .attr('class', 'btn close') + .attr('data-dismiss', 'modal') + .style('opacity', '0.5') + .append('span') + .attr('class', 'glyphicon glyphicon-remove') + + headerD3.append('h4') + .attr('class', 'modal-title') + .text('Error'); + + return headerD3.node(); + } + + /** + * Create the modal body + * @param {HTMLElement} modalEl The model element + */ + _createModalBody(modalEl) { + let bodyD3 = d3.select(modalEl) + .append('div') + .attr('class', 'modal-body') + .style('word-wrap', 'break-word') + .html(this.message); + + return bodyD3.node(); + } + +} diff --git a/webapp/apps/js/error/Preconditions.js b/webapp/apps/js/error/Preconditions.js new file mode 100644 index 000000000..d374d5546 --- /dev/null +++ b/webapp/apps/js/error/Preconditions.js @@ -0,0 +1,417 @@ + +import NshmpError from './NshmpError.js'; + +/** + * @fileoverview Static convenience methods to check wether a method or + * constructor was invoked correctly. + * + * If a precondition is not statisfied a NshmpError is thrown. + * + * @class Preconditions + * @author Brandon Clayton + */ +export class Preconditions { + + /** @private */ + constructor() {} + + /** + * Ensures the truth of an expression. + * + * @param {Boolean} expression Expression to check + * @param {String} errorMessage The exception message to use if the + * expression fails + */ + static checkArgument(expression, errorMessage) { + if (!expression) { + throw new NshmpError(`IllegalArgumentException: ${errorMessage}`); + } + } + + /** + * Check whether an argument is an array. + * + * @param {Array} arr The array to test + * @param {String=} errorMessage An optional error message to show + */ + static checkArgumentArray(arr, errorMessage = 'Must be an array') { + Preconditions.checkArgument(Array.isArray(arr), errorMessage); + } + + /** + * Check whether an argument is an array and all elements + * inside are of specific type. + * + * @param {Array<Object>} arr Array to check + * @param {Object} type Type inside array to check + */ + static checkArgumentArrayInstanceOf(arr, type) { + Preconditions.checkArgumentArray(arr); + + for (let val of arr) { + Preconditions.checkArgumentInstanceOf(val, type); + } + } + + /** + * Check whether an array is of certain length. + * + * @param {Array<Object>} arr The array to test + * @param {Number} length The length the array should be + */ + static checkArgumentArrayLength(arr, length) { + Preconditions.checkArgumentArray(arr); + Preconditions.checkArgumentInteger(length); + Preconditions.checkArgument(arr.length == length); + } + + /** + * Check whether an argument is an array and all elements inside the + * array are of a specificed type. + * + * @param {Array} arr The array to test + * @param {String} type The type of data inside the array + * @param {String=} errorMessage An optional error message to show + */ + static checkArgumentArrayOf(arr, type, errorMessage = 'Must be an array') { + Preconditions.checkArgumentArray(arr, errorMessage); + + for (let data of arr) { + Preconditions.checkArgumentTypeOf(data, type); + } + } + + /** + * Check whether an argument is a boolean. + * + * @param {Boolean} val The value to test + */ + static checkArgumentBoolean(val) { + Preconditions.checkArgumentTypeOf(val, 'boolean'); + } + + /** + * Check whether an argument is a integer. + * + * @param {Number} val The value to test + */ + static checkArgumentInteger(val) { + Preconditions.checkArgument(Number.isInteger(val), 'Must be an integer'); + } + + /** + * Check whether an argument is a certain instance of a type. + * + * @param {Object} val The value to check + * @param {Object} type The type of instance the value should be + */ + static checkArgumentInstanceOf(val, type) { + Preconditions.checkArgument( + val instanceof type, + `Must be instance of [${type.name}]`); + } + + /** + * Check whether an argument is a HTMLElement + * + * @param {Object} val The value to check + * @param {Object} type The type of instance the value should be + */ + static checkArgumentInstanceOfHTMLElement(val) { + Preconditions.checkArgumentInstanceOf(val, HTMLElement); + } + + /** + * Check whether an argument is a Map + * + * @param {Object} val The value to check + * @param {Object} type The type of instance the value should be + */ + static checkArgumentInstanceOfMap(val) { + Preconditions.checkArgumentInstanceOf(val, Map); + } + + /** + * Check whether an argument is a Set + * + * @param {Object} val The value to check + * @param {Object} type The type of instance the value should be + */ + static checkArgumentInstanceOfSet(val) { + Preconditions.checkArgumentInstanceOf(val, Set); + } + + /** + * Check whether an argument is a SVGElement + * + * @param {Object} val The value to check + * @param {Object} type The type of instance the value should be + */ + static checkArgumentInstanceOfSVGElement(val) { + Preconditions.checkArgumentInstanceOf(val, SVGElement); + } + + /** + * Check whether an argument is a number. + * + * @param {Number} val The value to test + */ + static checkArgumentNumber(val) { + Preconditions.checkArgumentTypeOf(val, 'number'); + } + + /** + * Check whether an argument is an object. + * + * @param {Object} val The value to test + */ + static checkArgumentObject(val) { + Preconditions.checkArgumentTypeOf(val, 'object'); + } + + /** + * Check whether a property exists in an object. + * + * @param {Object} obj The object to check + * @param {String} property The property to see if exists + */ + static checkArgumentObjectProperty(obj, property) { + Preconditions.checkArgumentObject(obj); + Preconditions.checkArgumentString(property); + Preconditions.checkArgument( + obj.hasOwnProperty(property), + `Must have property [${property}] in object`); + } + + /** + * Check whether an argument is a string. + * + * @param {String} val The string to test + */ + static checkArgumentString(val) { + Preconditions.checkArgumentTypeOf(val, 'string'); + } + + /** + * Test whether an argument is of a specific type. + * + * @param {Object} val The value to test + * @param {String} type The type the value should be + */ + static checkArgumentTypeOf(val, type) { + Preconditions.checkArgument(typeof(val) == type, `Must be of type [${type}]`); + } + + /** + * Check whether a value is null. + * + * @param {Object} val The value to test + * @param {String=} errorMessage Optional error message + */ + static checkNotNull(val, errorMessage = 'Cannot be null') { + if (val == null) { + throw new NshmpError(`NullPointerException: ${errorMessage}`); + } + } + + /** + * Check whether a value is undefined. + * + * @param {Object} val The value to test + * @param {String=} errorMessage Optional error message + */ + static checkNotUndefined(val, errorMessage = 'Cannot be undefined') { + if (val == undefined) { + throw new NshmpError(`NullPointerException: ${errorMessage}`); + } + } + + /** + * Ensures the truth of an expression. + * + * @param {Boolean} expression Expression to check + * @param {String} errorMessage The exception message to use if the + * expression fails + */ + static checkState(expression, errorMessage) { + if (!expression) { + throw new NshmpError(`IllegalStateException: ${errorMessage}`); + } + } + + /** + * Check whether a value is an array. + * + * @param {Array} arr The array to test + * @param {String=} errorMessage An optional error message to show + */ + static checkStateArray(arr, errorMessage = 'Must be an array') { + Preconditions.checkState(Array.isArray(arr), errorMessage); + } + + /** + * Check whether an argument is an array and all elements + * inside are of specific type. + * + * @param {Array<Object>} arr Array to check + * @param {Object} type Type inside array to check + */ + static checkStateArrayInstanceOf(arr, type) { + Preconditions.checkArgumentArray(arr); + + for (let val of arr) { + Preconditions.checkStateInstanceOf(val, type); + } + } + + /** + * Check whether an array is of certain length. + * + * @param {Array<Object>} arr The array to test + * @param {Number} length The length the array should be + */ + static checkStateArrayLength(arr, length) { + Preconditions.checkArgumentArray(arr); + Preconditions.checkArgumentInteger(length); + Preconditions.checkState(arr.length == length); + } + + /** + * Check whether a value is an array and all elements inside the + * array are of a specificed type. + * + * @param {Array} arr The array to test + * @param {String} type The type of data inside the array + * @param {String=} errorMessage An optional error message to show + */ + static checkStateArrayOf(arr, type, errorMessage = 'Must be an array') { + Preconditions.checkArgumentArray(arr, errorMessage); + + for (let data of arr) { + Preconditions.checkStateTypeOf(data, type); + } + } + + /** + * Check whether a value is a boolean. + * + * @param {Boolean} val The value to test + */ + static checkStateBoolean(val) { + Preconditions.checkStateTypeOf(val, 'boolean'); + } + + /** + * Check whether a value is a integer. + * + * @param {Number} val The value to test + */ + static checkStateInteger(val) { + Preconditions.checkState(Number.isInteger(val), 'Must be an integer'); + } + + /** + * Check whether a value is a certain instance of a type. + * + * @param {Object} val The value to check + * @param {Object} type The type of instance the value should be + */ + static checkStateInstanceOf(val, type) { + Preconditions.checkState( + val instanceof type, + `Must be instance of [${type.name}]`); + } + + /** + * Check whether an argument is a HTMLElement + * + * @param {Object} val The value to check + * @param {Object} type The type of instance the value should be + */ + static checkStateInstanceOfHTMLElement(val) { + Preconditions.checkStateInstanceOf(val, HTMLElement); + } + + /** + * Check whether an argument is a Map + * + * @param {Object} val The value to check + * @param {Object} type The type of instance the value should be + */ + static checkStateInstanceOfMap(val) { + Preconditions.checkStateInstanceOf(val, Map); + } + + /** + * Check whether an argument is a Set + * + * @param {Object} val The value to check + * @param {Object} type The type of instance the value should be + */ + static checkStateInstanceOfSet(val) { + Preconditions.checkStateInstanceOf(val, Set); + } + + /** + * Check whether an argument is a SVGElement + * + * @param {Object} val The value to check + * @param {Object} type The type of instance the value should be + */ + static checkStateInstanceOfSVGElement(val) { + Preconditions.checkStateInstanceOf(val, SVGElement); + } + + /** + * Check whether a value is a number. + * + * @param {Number} val The value to test + */ + static checkStateNumber(val) { + Preconditions.checkStateTypeOf(val, 'number'); + } + + /** + * Check whether a value is an object. + * + * @param {Object} val The value to test + */ + static checkStateObject(val) { + Preconditions.checkStateTypeOf(val, 'object'); + } + + /** + * Check whether a property exists in an object. + * + * @param {Object} obj The object to check + * @param {String} property The property to see if exists + */ + static checkStateObjectProperty(obj, property) { + Preconditions.checkArgumentObject(obj); + Preconditions.checkArgumentString(property); + + Preconditions.checkState( + obj.hasOwnProperty(property), + `Must have property [${property}] in object`); + } + + /** + * Check whether a value is a string. + * + * @param {String} val The string to test + */ + static checkStateString(val) { + Preconditions.checkStateTypeOf(val, 'string'); + } + + /** + * Test whether a value is of a specific type. + * + * @param {Object} val The value to test + * @param {String} type The type the value should be + */ + static checkStateTypeOf(val, type) { + Preconditions.checkState(typeof(val) == type, `Must be of type [${type}]`); + } + +} diff --git a/webapp/apps/js/lib/Constraints.js b/webapp/apps/js/lib/Constraints.js new file mode 100644 index 000000000..f4320a0b3 --- /dev/null +++ b/webapp/apps/js/lib/Constraints.js @@ -0,0 +1,78 @@ +'use strict'; + +/** +* @fileoverview Class of static methods to listen and check if a input +* value is within certain bounds. +* If the value is outside the specified bounds a red focus ring is +* added to the parent element. +* +* @class Constraints +* @author bclayton@usgs.gov (Brandon Clayton) +*/ +export default class Constraints { + + /** + * @method check + * + * Check to see if the value of an element is within specified values + * and add a red focus ring to the parent of the element if the + * value is outside of the bounds. + * @param {!HTMLElement} el - DOM element of the input field + * @param {!number} minVal - Minimum value of bound (inclusive) + * @param {!number} maxVal - Maximum value of bound (inclusive) + * @param {boolean=} canHaveNaN - Whether the value can be empty + * @return {boolean} Whether the value is inside the bounds (true) or not + */ + static check(el, minVal, maxVal, canHaveNaN = false) { + let isInBounds; + let val = parseFloat(el.value); + + if (val < minVal || val > maxVal || (isNaN(val) && !canHaveNaN)) { + isInBounds = false; + } else { + isInBounds = true; + } + + d3.select(el.parentNode) + .classed('has-error', !isInBounds); + + return isInBounds; + } + + /** + * @method onInput + * + * Add listener, oninput, to a DOM element and check if the inputted + * value is within bounds. + * @param {!HTMLElement} el - DOM element of the input field + * @param {!number} minVal - Minimum value of bound (inclusive) + * @param {!number} maxVal - Maximum value of bound (inclusive) + * @param {boolean=} canHaveNaN - Whether the value can be empty + */ + static onInput(el, minVal, maxVal, canHaveNaN = false){ + $(el).on('input', (event) => { + Constraints.check(event.target, minVal, maxVal, canHaveNaN); + }); + } + + /** + * @method addTooltip + * + * Add a Bootstrap tooltip to a DOM element showing the specified bounds. + * Example: [0, 5] + */ + static addTooltip(el, minVal, maxVal) { + d3.select(el) + .attr('data-toggle', 'tooltip'); + + let title = '[' + minVal + ', ' + maxVal +']'; + let options = { + container: 'body', + }; + + $(el).attr('title', title) + .attr('data-original-title', title) + .tooltip(options); + } + +} diff --git a/webapp/apps/js/lib/ControlPanel.js b/webapp/apps/js/lib/ControlPanel.js new file mode 100644 index 000000000..c4551cf10 --- /dev/null +++ b/webapp/apps/js/lib/ControlPanel.js @@ -0,0 +1,1082 @@ +'use strict'; + +import Tools from './Tools.js'; +import Constraints from './Constraints.js'; + +/** + * @fileOverview Dynamically create a new control panel for a + * nshmp web application. + * + * For the control panel to render correctly the following files + * must be present in the HTML file: + * - CSS: Bootstrap v3.3.6 + * - CSS: webapp/apps/css/template.css + * - JavaScript: jQuery v3.2.1 + * - JavaScript: Bootstrap 3.3.7 + * - JavaScript: D3 v4 + * + * Use the {@link FormGroupBuilder} to create form groups that can + * consist of a: + * - Input form + * - Slider + * - Button group + * - Select menu + * + * @example + * // Create an empty control panel + * let controlPanel = new ControlPanel(); + * + * // Input form options + * let inputOptions = { + * id: 'zTop', + * label: 'z<sub>Top</sub>', + * labelColSize: 'col-xs-2', + * max: 700, + * min: 0, + * name: 'zTop', + * step: 0.5, + * value: 0.5, + * }; + * + * // Button group options + * let btnOptions = { + * addLabel: false, + * id: 'zTop-btn-group', + * name: 'zTop', + * }; + * + * // Slider options + * let sliderOptions = { id: 'zTop-slider' }; + * + * // zTop buttons for button group + * let zTopBtns = [ + * { text: '5.0 km', value: 5.0 }, + * { text: '10.0 km', value: 10.0 }, + * { text: '15.0 km', value: 15.0 }, + * ]; + * + * // Create a form group with a input, slider, and button group in + * // that order. + * let els = controlPanel.formGroupBuilder() + * .addInput(inputOptions) + * .addInputSlider(sliderOptions) + * .addBtnGroup(zTopBtns, btnOptions) + * .syncValues() + * .addInputTooltip() + * .addInputAddon('km') + * .build(); + * + * // Get input el + * let zTopEl = els.inputEl; + * + * // Get input group el + * let zTopInputGroupEl = els.inputGroupEl; + * + * // Get btn group el + * let zTopBtnGroupEl = els.btnGroupEl; + * + * // Get slider el + * let zTopSliderEl = els.sliderEl; + * + * @class ControlPanel + * @author Brandon Clayton + */ +export default class ControlPanel { + + constructor() { + let controlPanelD3 = d3.select('body') + .insert('div', '#content') + .attr('class', 'control-panel') + .attr('id', 'control'); + + let inputsD3 = controlPanelD3.append('form') + .attr('id', 'inputs'); + + let formHorizontalD3 = inputsD3.append('div') + .attr('class', 'form-horizontal'); + + /** The control panel element - @type {HTMLElement} */ + this.controlPanelEl = controlPanelD3.node(); + /** The inputs element - @type {HTMLElement} */ + this.inputsEl = inputsD3.node(); + /** The horizontal form element - @type {HTMLElement} */ + this.formHorizontalEl = formHorizontalD3.node(); + + /** + * Options for creating button groups. + * @see createBtnGroup + * + * + * @typedef {Object} BtnGroupOptions + * @property {Boolean} addLabel - To add a label before the button group. + * @property {HTMLElement} appendTo - Element to append the button group to. + * @property {String} btnGroupColSize - Bootstrap column size. + * @property {String} btnSize - The Bootstrap button size. + * @property {String} id - Button group id attibute. + * @property {String} label - The label text. + * @property {String} name - Button group name attribute. + * @property {String} paddingTop - The top padding for the button group. + * @property {String} title - Button group tittle attribute. + * @property {String} type - The type of button in the button group. + * 'radio' equals single selection, 'checkbox' equals multiple selection. + */ + this.btnGroupOptions = { + addLabel: false, + appendTo: this.formHorizontalEl, + btnGroupColSize: 'col-xs-10 col-xs-offset-2', + btnSize: 'btn-xs', + id: null, + label: null, + labelControl: true, + name: null, + paddingTop: '0.5em', + title: null, + type: 'radio', + }; + + /** + * Options for creating a Bootstrap form group div. + * @see createFormGroup + * + * @typedef {Object} FormGroupOptions + * @property {HTMLElement} appendTo - Where to append the form group div. + * @property {String} formClass - Any additional classes to add to the + * form group. + * @property {String} formSize - The Bootstrap form group size. + */ + this.formGroupOptions = { + appendTo: this.formHorizontalEl, + formClass: '', + formSize: 'form-group-sm', + }; + + /** + * Options for creating the ground motion model sorter. + * @see createGmmSorter + * + * @typedef {Object} GmmOptions + * @property {String} label - GMM sorter label. + * @property {String} labelFor - Where the label is bound. + * @property {Number} size - The size of the gmm select menu. + */ + this.gmmOptions = { + label: 'Ground Motion Models:', + labelControl: false, + labelFor: 'gmms', + size: 16, + }; + + /** + * Options for creating an input form. + * @see createInput + * + * @typedef {Object} InputOptions + * @property {Boolean} addLabel - To add a label before the input form. + * @property {HTMLElement} appendTo - Element to append the input form to. + * @property {Boolean} checked - Whether to be checked, only for + * ControlPanel#createCheckbox. + * @property {Boolean} disabled - Whether input form is disabled. + * @property {Boolean} formControl - Wheater to add the Bootstrap + * 'form-control' class to the the input form. + * @property {String} inputColSize - Bootstrap column size for the + * input form. + * @property {String} inputGroupFloat - Direction to float the + * input form. + * @property {String} label - The label if 'addLabel' is true. + * @property {String} labelColSize - The Bootstrap column size for the + * label if 'addLabel' is true. + * @property {Boolean} labelBreak - Add a line break after the label + * if true and 'addLabel' is true. + * @property {String} labelFloat - Direction to float the label. + * @property {Number} max - Sets the max attribute. + * @property {Number} min - Sets the min attribute. + * @property {String} name - Sets the name attribute. + * @property {Boolean} readOnly - Sets the readOnly property. + * @property {Number} step - Sets the step attribute. + * @property {String} text - The text for after a checkbox, only used + * for ControlPanel.createCheckbox. + * @property {String | Number} value - Sets the value attribute. + */ + this.inputOptions = { + addLabel: true, + appendTo: this.formHorizontalEl, + checked: false, + disabled: false, + formControl: true, + id: null, + inputColSize: 'col-xs-3', + inputGroupFloat: 'left', + inputGroupSize: 'input-group-sm', + label: '', + labelBreak: false, + labelColSize: '', + labelControl: true, + labelFloat: 'left', + max: null, + min: null, + name: null, + readOnly: false, + step: 1, + text: null, + type: 'number', + value: null, + }; + + /** + * Options for creating a label. + * @see createLabel + * + * @typedef {Object} LabelOptions + * @property {HTMLElement} appendTo - Where to append the form group div. + * @property {String} label - The label text. + * @property {String} labelColSize - The Bootstrap column size for the + * label if 'addLabel' is true. + * @property {String} labelFor - Where the label is bound. + * @property {String} float - Where the label should float. + */ + this.labelOptions = { + appendTo: this.formHorizontalEl, + label: '', + labelColSize: '', + labelControl: true, + labelFor: null, + float: 'initial', + }; + + /** + * Options for creating a select menu. + * @see createSelect + * + * @typedef {Object} SelectOptions + * @property {Boolean} addLabel - To add a label before the select menu. + * @property {HTMLElement} appendTo - Element to append the select menu to. + * @property {String} id - The id attribute of the select menu. + * @property {String} label - The label if 'addLabel' is true. + * @property {String} labelColSize - The Bootstrap column size for the + * label if 'addLabel' is true. + * @property {Boolean} labelBreak - Add a line break after the label + * if true and 'addLabel' is true. + * @property {Boolean} multiple - Whether the select is a multi select. + * @property {String} name - The name attribue of select menu. + * @property {Number} size - The size of the select menu. + * @property {String | Number} value - A selected value. + */ + this.selectOptions = { + addLabel: true, + appendTo: this.formHorizontalEl, + id: null, + label: '', + labelBreak: true, + labelColSize: '', + labelControl: true, + multiple: false, + name: null, + size: 0, + value: '', + }; + + /** + * Options for creating a slider. + * @see createInputSlider + * + * @typedef {Object} SliderOptions + * @property {HTMLElement} appendTo - Where to append the form group div. + * @property {String} id - The id of the slider. + * @property {HTMLElement} inputEl - The input form element that the slider + * should be bound to. + * @property {String} sliderColSize - The Bootstrap column size for the + * slider. + */ + this.sliderOptions = { + appendTo: this.formHorizontalEl, + id: '', + inputEl: null, + sliderColSize: 'col-xs-5 col-xs-offset-1', + } + } + + /** + * Return a new {@link FormGroupBuilder} to build a form + * group that can consists of a: + * - input form with Bootstrap input add on + * - slider bound to an input form + * - checkbox + * - select menu + * - button group + * + * @example + * // Create empty control panel + * let controlPanel = new ControlPanel(); + * + * // Get form group builder + * let formBuilder = controlPanel.formGroupBuilder(); + * + * @param {FormGroupOptions} formGroupOptions The form group options. + * @returns A new instance of FormGroupBuilder + */ + formGroupBuilder(formGroupOptions) { + return new this.FormGroupBuilder(formGroupOptions); + } + + /** + * Build a Bootstrap form group with combinations of: + * - input form with Bootstrap input add on + * - slider bound to an input form + * - checkbox + * - select menu + * - button group + * + * NOTE: The current builder cannot add multiple of the same element. + */ + get FormGroupBuilder() { + let controlPanel = this; + + class FormGroupBuilder { + + /** + * Create the form group needed to add element to. + * @param {FormGroupOptions} formGroupOptions + */ + constructor(formGroupOptions) { + /** The form group element - @type {HTMLElement} */ + this.formGroupEl = controlPanel._createFormGroup(formGroupOptions); + + /** Button group element - @type {HTMLElement} */ + this.btnGroupEl = undefined; + /** Checkbox element - @type {HTMLElement} */ + this.checkboxEl = undefined; + /** Input form element - @type {HTMLElement} */ + this.inputEl = undefined; + /** Input group element - @type {HTMLElement} */ + this.inputGroupEl = undefined; + /** Select ment element - @type {HTMLElement} */ + this.selectEl = undefined; + /** Slider element - @type {HTMLElement} */ + this.sliderEl = undefined; + + /** + * Whether to sync the values between an input form, slider, and button + * group. + * + * Syncing the values will work as long as any two of these + * elements exists. + * + * This is set to true by using the {@link syncValues} method. + * @type {Boolean} + */ + this._toSyncValues = false; + + /** + * Whether to add a Bootstrap tooltip to the input form. + * + * This is set to true by using the {@link addInputTooltip} method. + * + * NOTE: The input form must have the min and max attribute set + * for this to work. + * @see Constraints#addTooltip + * @type {Boolean} + */ + this._toAddInputTooltip = false; + + /** + * Whether to add a bootstrap input form add on at the end of the + * input form. + * + * This is set to true by using the {@link addInputAddon} method/ + * @type {Boolean} + */ + this._toAddInputAddon = false; + } + + /** + * Build the form group and return an Object with + * the HTMLElements based on what was choosen to be built. + * + * @return {FormGroupElements} All HTMLElement + */ + build() { + /** + * The {HTMLElement}s associated with the elements created. + * @typedef {Object} FormGroupElements + * @property {HTMLElement} formGroupEl - The form group element. + * @property {HTMLElement} btnGroupEl - The button group element. Only + * returned if addBtnGroup is called in the builder. + * @property {HTMLElement} checkboxEl - The checkbox element. Only + * returned if addCheckbox is called in the builder. + * @property {HTMLElement} inputEl - The input element. Only returned + * if addInput is called in the builder. + * @property {HTMLElement} inputGroupEl - The input group element + * where the input form exist. Only returned if addInput + * is called in the builder. + * @property {HTMLElement} sliderEl - The slider element. Only + * returned if addInputSlider is called in the builder. + * @property {HTMLElement} selectEl - The select menu element. Only + * retunred if the addSelect is called in the builder. + */ + let els = {}; + + els.formGroupEl = this.formGroupEl; + + if (this.btnGroupEl) { + els.btnGroupEl = this.btnGroupEl; + } + + if (this.checkboxEl) { + els.checkboxEl = this.checkboxEl; + } + + if (this.inputEl) { + els.inputEl = this.inputEl; + els.inputGroupEl = this.inputGroupEl; + + if (this._toAddInputAddon) { + controlPanel._inputAddon(this.inputEl, this.addonText); + } + + if (this._toAddInputTooltip) { + Constraints.addTooltip( + this.inputEl, + this.inputEl.min, + this.inputEl.max) + } + } + + if (this.sliderEl) { + els.sliderEl = this.sliderEl; + } + + if (this.selectEl) { + els.selectEl = this.selectEl; + } + + if (this._toSyncValues) { + controlPanel._syncValues( + this.inputEl, + this.btnGroupEl, + this.sliderEl); + } + + return els; + } + + /** + * Add a button group to the form group. + * @param {Buttons} btns The buttons for the button group. + * @param {BtnGroupOptions} options The button group options. + */ + addBtnGroup(btns, options) { + options.appendTo = this.formGroupEl; + this.btnGroupEl = controlPanel._createBtnGroup(btns, options); + return this; + } + + /** + * Add a checkbox to the form group. + * @param {InputOptions} options The input options. + */ + addCheckbox(options) { + options.appendTo = this.formGroupEl; + this.checkboxEl = controlPanel._createCheckbox(options); + return this; + } + + /** + * Add an input form to the form group. + * @param {InputOptions} options The input options. + */ + addInput(options) { + options.appendTo = this.formGroupEl; + this.inputEl = controlPanel._createInput(options); + this.inputGroupEl = this.inputEl.parentNode; + return this; + } + + /** + * Add the input form addon . + * @param {String} text The text to add to the input addon + */ + addInputAddon(text) { + this.addonText = text; + this._toAddInputAddon = true; + return this; + } + + /** + * Add a slider to the form group. + * @param {SliderOptions} options The slider options. + */ + addInputSlider(options) { + options.appendTo = this.formGroupEl; + options.inputEl = this.inputEl; + this.sliderEl = controlPanel._createInputSlider(options); + return this; + } + + /** + * Add a Boostrap tooltip to the input form. + */ + addInputTooltip() { + this._toAddInputTooltip = true; + return this; + } + + /** + * Add a select menu to the form group. + * @param {Array<HTMLElement>} optionArray The select menu values. + * @param {SelectOptions} options The select menu options. + */ + addSelect(optionArray, options) { + options.appendTo = this.formGroupEl; + this.selectEl = controlPanel._createSelect(optionArray, options); + return this; + } + + /** + * Sync the values between a input form, slider, and a button group. + * A minimum of two of these elements must be defined. + */ + syncValues() { + this._toSyncValues = true; + return this; + } + + } + return FormGroupBuilder; + } + + createGmmSelect(spectraParameters, gmmOptions = {}) { + let options = $.extend({}, this.gmmOptions, gmmOptions); + + let gmmSorterEls = this._createGmmSorter(options); + let gmmSorterEl = gmmSorterEls.gmmSorterEl; + let gmmFormGroupEl = gmmSorterEls.formGroupEl; + + /* Alphabetic GMMs */ + let gmmAlphaOptions = $(); + spectraParameters.gmm.values.forEach((gmm) => { + gmmAlphaOptions = gmmAlphaOptions.add($('<option>') + .attr('value', gmm.id) + .attr('id', gmm.id) + .text(gmm.label)); + }); + + /* Grouped GMMs */ + let gmmGroupOptions = $(); + spectraParameters.group.values.forEach((group) => { + let members = group.data; + let optGroup = $('<optgroup>') + .attr('label', group.label) + .attr('id', group.id); + gmmGroupOptions = gmmGroupOptions.add(optGroup); + optGroup.append(gmmAlphaOptions + .filter((index, gmmOption) => { + return members.includes(gmmOption.getAttribute('value')); + }) + .clone()); + }); + + let gmmsEl = this._createSelect( + gmmGroupOptions, { + appendTo: gmmFormGroupEl, + addLabel: false, + id: 'gmms', + name: 'gmm', + multiple: true, + size: options.size}); + + /* Bind option views to sort buttons */ + $('input', gmmSorterEl).change((event) => { + let options = event.target.value === 'alpha' ? + gmmAlphaOptions : gmmGroupOptions; + this.updateSelectOptions(gmmsEl, options); + $(gmmsEl).scrollTop(0); + Tools.resetRadioButton(event.target); + }); + + /* Add tooltips */ + $(gmmSorterEls.gmmAlphaEl).tooltip({container: 'body'}); + $(gmmSorterEls.gmmGroupEl).tooltip({container: 'body'}); + + let els = { + gmmSorterEl: gmmSorterEl, + gmmAlphaEl: gmmSorterEls.gmmAlphaEl, + gmmGroupEl: gmmSorterEls.gmmGroupEl, + gmmFormGroupEl: gmmFormGroupEl, + gmmAlphaOptions: gmmAlphaOptions, + gmmGroupOptions: gmmGroupOptions, + gmmsEl: gmmsEl, + }; + + return els; + } + + /** + * Create a label. + * @param {LabelOptions} labelOptions + */ + createLabel(labelOptions) { + let options = $.extend({}, this.labelOptions, labelOptions); + + let labelD3 = d3.select(options.appendTo) + .append('label') + .attr('class', options.labelColSize) + .classed('control-label', options.labelControl) + .attr('for', options.labelFor) + .style('float', options.float) + .html(options.label); + + return labelD3.node(); + } + + /** + * Convert a single selectable button group (a radio button group) + * to a multi-selectable button group (a checkbox button group) and + * disable the input form and slider (if present). + * + * The name attribute is removed and kept under the data-name attribute + * so the input form will not be included in a serialization of + * the inputs; + * + * @param {FormGroupElements} els The elements of the input form, + * button group, and slider (optional). + * - els.inputEl + * - els.btnGroupEl + * - els.sliderEl + */ + toMultiSelectable(els) { + let inputName = els.inputEl.getAttribute('name'); + d3.select(els.inputEl) + .property('disabled', true) + .attr('name', ''); + + if (els.sliderEl) els.sliderEl.disabled = true; + + d3.select(els.btnGroupEl) + .selectAll('input') + .attr('type', 'checkbox') + .each((d, i, el) => { + let isActive = d3.select(el[i].parentNode) + .classed('active'); + + if (isActive) el[i].checked = true; + }); + } + + /** + * Convert an array of objects into an array of option elements + * for a select menu. + * + * The object inside the array must have a text and value field. + * + * @param {Array<Object>} values The values to convert. + * @return {Array<HTMLElement>} The array of option elements. + */ + toSelectOptionArray(values) { + let optionArray = []; + values.forEach((val) => { + let el = d3.create('option') + .attr('value', val.value) + .text(val.text) + .node(); + optionArray.push(el); + }); + return optionArray; + } + + /** + * Convert a multi-selectable button group (a checkbox button group) + * to a single selectable button group (a radio button group) and + * re-enable the input form and slider (if present). + * + * The name attribute is re-added to the input form so it can + * be included in any serialization of the inputs. + * + * @param {FormGroupElements} els The elements of the input form, + * button group, and slider (optional). + * - els.inputEl + * - els.btnGroupEl + * - els.sliderEl + */ + toSingleSelectable(els) { + let inputName = els.inputEl.dataset.name; + d3.select(els.inputEl) + .property('disabled', false) + .attr('name', inputName); + + if (els.sliderEl) els.sliderEl.disabled = false; + + d3.select(els.btnGroupEl) + .selectAll('label') + .classed('active', false) + .selectAll('input') + .attr('type', 'radio') + .property('checked', false); + + $(els.inputEl).trigger('input'); + } + + /** + * Update a select menu values. + * + * @param {HTMLElement} selectEl The select menu element. + * @param {Array<HTMLElement>} optionArray The select menu option array. + */ + updateSelectOptions(selectEl, optionArray) { + d3.select(selectEl) + .selectAll('*') + .remove(); + + d3.select(selectEl) + .selectAll('*') + .data($(optionArray).clone()) + .enter() + .append((d) => { return d; }); + } + + /** + * @private + * Create a Bootstrap radio/checkbox button group in the control + * panel. + * + * The type of button group is defined by the 'type' option. + * + * @param {Buttons} btns The buttons that make up the button group. + * @param {BtnGroupOptions} btnGroupOptions The button group options. + * @returns {HTMLElement} The element representing the button group. + */ + _createBtnGroup(btns, btnGroupOptions) { + /** @type {BtnGroupOptions} */ + let options = $.extend({}, this.btnGroupOptions, btnGroupOptions); + + if (options.addLabel) { + this.createLabel({ + appendTo: options.appendTo, + label: options.label, + labelControl: options.labelControl, + labelFor: options.id}); + } + + let btnGroupD3 = d3.select(options.appendTo) + .append('div') + .attr('class', 'btn-group') + .classed(options.btnGroupColSize, true) + .attr('data-toggle', 'buttons') + .attr('id', options.id) + .attr('name', options.name) + .style('padding-top', options.paddingTop); + + btnGroupD3.selectAll('label') + .data(btns) + .enter() + .append('label') + .attr('class', 'btn btn-default') + .classed('active', (d) => { return d.isActive; }) + .classed(options.btnSize, true) + .html((d) => { + return '<input type="' + options.type + '" ' + + 'value="' + d.value + '" ' + + 'id="' + (d.id || '') + '" ' + + 'title="' + d.text + '"> ' + d.text; + }); + + return btnGroupD3.node(); + } + + /** + * @private + * Create a checkbox in the control panel. + * + * @param {InputOptions} inputOptions The input options for the checkbox. + * @returns {HTMLElement} The checkbox element. + */ + _createCheckbox(inputOptions) { + let options = $.extend({}, this.inputOptions, inputOptions); + options.type = 'checkbox'; + options.formControl = false; + options.addLabel = false; + + let tmpEl = this._createInput(options); + let inputD3 = d3.select(tmpEl.parentNode) + .append('label') + .attr('class', 'control-label secondary-input') + .attr('for', options.id) + .html(tmpEl.outerHTML + ' ' + options.text) + .select('input'); + + d3.select(tmpEl).remove(); + + inputD3.property('checked', options.checked); + return inputD3.node(); + } + + /** + * @private + * Create a form group in the control panel. + * + * @param {FormGroupOptions} formGroupOptions The form group options. + * @returns {HTMLElement} The form group element. + */ + _createFormGroup(formGroupOptions) { + let options = $.extend({}, this.formGroupOptions, formGroupOptions); + + let formGroupD3 = d3.select(options.appendTo) + .append('div') + .attr('class', 'form-group') + .classed(options.formClass, true) + .classed(options.formSize, true) + .attr('id', options.id); + + return formGroupD3.node(); + } + + /** + * @private + * Convience method to creating the ground motion model sorter. + * @param {GmmOptions} options The GMM options. + */ + _createGmmSorter(options) { + let formGroupEl = this._createFormGroup(); + + this.createLabel({ + appendTo: formGroupEl, + label: options.label, + labelControl: options.labelControl, + labelFor: options.labelFor}); + + let gmmSorterD3 = d3.select(formGroupEl) + .append('div') + .attr('id', 'gmm-sorter') + .style('float', 'right') + .attr('class', 'btn-group btn-group-xs') + .attr('data-toggle', 'buttons'); + + gmmSorterD3.append('label') + .attr('class', 'btn btn-default gmm-group active') + .attr('data-toggle', 'tooltip') + .attr('data-container', 'body') + .attr('title', 'Sort by Group') + .attr('for', 'gmm-sort-group') + .html('<input type="radio" value="group" id="gmm-sort-group"' + + 'aria-label="Sort by Group"> ' + + '<span class="glyphicon glyphicon-list" aria-hidden="true"/>'); + + gmmSorterD3.append('label') + .attr('class', 'btn btn-default gmm-alpha') + .attr('data-toggle', 'tooltip') + .attr('data-container', 'body') + .attr('title', 'Sort Alphabetically') + .attr('for', 'gmm-sort-alpha') + .html('<input type="radio" value="alpha" id="gmm-sort-alpha"' + + 'aria-label="Sort Alphabetically"> ' + + '<span class="glyphicon glyphicon-sort-by-alphabet" ' + + 'aria-hidden="true"/>'); + + let els = { + gmmSorterEl: gmmSorterD3.node(), + gmmAlphaEl: gmmSorterD3.select('.gmm-alpha').node(), + gmmGroupEl: gmmSorterD3.select('.gmm-group').node(), + formGroupEl: formGroupEl, + }; + + return els; + } + + /** + * @private + * Create a input form in the control panel. + * + * @param {InputOptions} inputOptions The input form options. + * @returns {HTMLElement} The input form element. + */ + _createInput(inputOptions) { + let options = $.extend({}, this.inputOptions, inputOptions); + + if (options.addLabel) { + this.createLabel({ + appendTo: options.appendTo, + label: options.label, + labelControl: options.labelControl, + labelFor: options.id, + labelColSize: options.labelColSize, + float: options.labelFloat}); + + if (options.labelBreak) { + d3.select(options.appendTo) + .append('br'); + } + } + + let inputD3 = d3.select(options.appendTo) + .append('div') + .attr('class', 'input-group') + .classed(options.inputGroupSize, true) + .classed(options.inputColSize, true) + .attr('id', options.id + '-input-group') + .style('float', options.inputGroupFloat) + .append('input') + .classed('form-control', options.formControl) + .attr('data-name', options.name) + .attr('id', options.id) + .attr('name', options.name) + .attr('type', options.type) + .attr('min', options.min) + .attr('max', options.max) + .attr('value', options.value) + .attr('step', options.step) + .property('disabled', options.disabled) + .property('readOnly', options.readOnly); + + return inputD3.node(); + } + + /** + * @private + * Create a slider in the control panel that based from a input form. + * + * @param {SliderOptions} sliderOptions The slider options. + * @returns {HTMLElement} The slider element. + */ + _createInputSlider(sliderOptions) { + let options = $.extend({}, this.sliderOptions, sliderOptions); + + let sliderD3 = d3.select(options.appendTo) + .append('div') + .attr('class', options.sliderColSize) + .append('input') + .attr('class', 'control-panel-slider') + .attr('id', options.id) + .attr('type', 'range') + .attr('min', options.inputEl.min) + .attr('max', options.inputEl.max) + .attr('value', options.inputEl.value) + .attr('step', options.inputEl.step); + + return sliderD3.node(); + } + + /** + * @private + * Create a select menu in the control panel. + * + * @param {SelectOptions} selectOptions The select menu options. + * @param {Array<HTMLElement>} optionArray The option elements. + * @returns {HTMLElement} The select menu element. + */ + _createSelect(optionArray, selectOptions) { + let options = $.extend({}, this.selectOptions, selectOptions); + + if (options.addLabel) { + this.createLabel({ + appendTo: options.appendTo, + label: options.label, + labelControl: options.labelControl, + labelFor: options.id, + labelColSize: options.labelColSize}); + + if (options.labelBreak) { + d3.select(options.appendTo) + .append('br'); + } + } + + let selectD3 = d3.select(options.appendTo) + .append('select') + .attr('class', 'form-control') + .attr('id', options.id) + .attr('name', options.name) + .property('multiple', options.multiple) + .attr('size', options.size); + + if (options.size > 1) { + selectD3.style('height', 'auto'); + } + + let selectEl = selectD3.node(); + selectEl.value = options.value; + this.updateSelectOptions(selectEl, optionArray); + + return selectEl; + } + + /** + * @private + * Add a input form addon to the input form. + * + * @param {HTMLElement} intputEl The input form element to add the addon. + * @param {String} text The text to add to the input addon. + */ + _inputAddon(inputEl, text) { + d3.select(inputEl.parentNode) + .append('span') + .attr('class', 'input-group-addon') + .html(text); + } + + /** + * @private + * Sync values between an input form, slider, and button group. + * + * If a button is clicked the input form and slider are updated. + * + * If the input form is updated then the slider is updated and if + * a button has that value it is activated. + * + * If the slider is updated the input form is updated and if a + * button has that value it is activated. + * + * A minumum of two of the elements must be defined. + * + * @param {HTMLElement} inputEl The input element. + * @param {HTMLElement=} btnGroupEl Optional button group element. + * @param {HTMLElement=} sliderEl Optional slider element. + */ + _syncValues(inputEl, btnGroupEl = undefined, sliderEl = undefined) { + $(inputEl).on('input', (event) => { + if (sliderEl) sliderEl.value = event.target.value; + if (btnGroupEl) this._findBtnGroupValue(btnGroupEl, event.target.value); + }); + + if (btnGroupEl) { + $('input', btnGroupEl).on('change', (event) => { + if (inputEl.disabled) return; + let val = event.target.value; + inputEl.value = val; + $(inputEl).trigger('input'); + if (sliderEl) sliderEl.value = val; + Tools.resetRadioButton(event.target); + }); + } + + if (sliderEl) { + $(sliderEl).on('input', (event) => { + inputEl.value = event.target.value; + $(inputEl).trigger('input'); + if (btnGroupEl) this._findBtnGroupValue(btnGroupEl, event.target.value); + }); + } + + $(inputEl).trigger('input'); + } + + /** + * @private + * Find a button with a specific value and activate that button. + * + * @param {HTMLElement} btnGroupEl The button group element. + * @param {String | Number} value The value to find. + */ + _findBtnGroupValue(btnGroupEl, value) { + d3.select(btnGroupEl) + .selectAll('label') + .classed('active', false); + + let el = $('input', btnGroupEl).filter((i, el) => { + return parseFloat(el.value) == parseFloat(value); + }); + + if (el[0] != undefined) { + d3.select(el[0].parentNode).classed('active', true); + } + } + +} \ No newline at end of file diff --git a/webapp/apps/js/lib/D3GeoDeagg.js b/webapp/apps/js/lib/D3GeoDeagg.js new file mode 100644 index 000000000..48be52cdb --- /dev/null +++ b/webapp/apps/js/lib/D3GeoDeagg.js @@ -0,0 +1,939 @@ +'use strict'; + +import D3View from './D3View.js'; +import D3SaveFigure from './D3SaveFigure.js'; +import D3Tooltip from './D3Tooltip.js'; +import Tools from './Tools.js'; +import NshmpError from '../error/NshmpError.js'; + +/** +* @class D3GeoDeagg +* @extends D3View +* +* @fileoverview This class plots geographic deaggregation source contribution +* results. +* This class uses the D3 satellite projection for the globe and plots +* the deagg contribution as bars. +* All properties must be set before plotting, for example: +* let myPlot = new D3GeoDeagg(...).withPlotHeader().withPlotFooter(); +* myPlot.setPanelTitle(' ') +* .setSiteLocation({latitude: lat, longitude: lon}) +* .setUpperData(data) +* .setUpperDataTableTitle(' ') +* .setUpperPlotFilename(' ') +* .setUpperPlotIds(ids) +* .setUpperPlotLabels(labels) +* .setUpperMetadata(metadata) +* .plotData(myPlot.upperPanel); +* +* @author bclayton@usgs.gov (Brandon Clayton) +*/ +export default class D3GeoDeagg extends D3View { + + /** + * @param {!HTMLElement} containerEl - DOM element to append to + * @param {LineViewOptions=} options - General options for plot panel + * @param {PlotOptions=} plotOptionsUpper - Upper plot options + * @param {PlotOptions=} plotOptionsLower - Lower plot options + */ + constructor( + containerEl, + viewOptions = {}, + plotOptionsUpper = {}, + plotOptionsLower = {}) { + let mapViewOptions = { + addGridLineCheckBtn: false, + addLegendCheckBtn: false, + centerXOffset: 5, + centerYOffsetFunction: (centerY) => { + return (centerY + this.options.centerXOffset); + }, + deaggFill: '#2196F3', + deaggNormalizationScale: 150, + deaggSelectedFill: '#0D47A1', + deaggStroke: 'black', + deaggStrokeWidth: 1.5, + deaggWidth: 10, + defaultView: 'overview', + graticuleFill: 'white', + graticuleStroke: 'lightgrey', + graticuleStrokeWidth: 1, + mapBorderStroke: 'black', + mapBorderStrokeWidth: 0.5, + mapCloseUp: { + distance: 5, + center: [0, 50], + tilt: 25, + scale: 2000, + }, + mapFill: 'white', + mapOverview: { + distance: 5, + center: [0, 0], + tilt: 0, + scale: 400, + }, + mapStroke: 'black', + mapStrokeWidth: 1, + minMapCenterY: 0, + minMapRotation: 6, + minMapScale: 200, + minMapTilt: 0, + maxMapCenterY: 50, + maxMapRotation: 60, + maxMapScale: 10000, + maxMapTilt: 25, + pointRadius: 4.5, + siteLocationFill: 'red', + siteLocationStroke: 'red', + siteLocationStrokeWidth: 1, + slowRotation: 6, + }; + + let plotOptions = { + printTitle: false, + tooltipText: [ + 'Name:', 'Longitude (°):', 'Latitude (°):', 'Contribution (%):' + ], + }; + viewOptions = $.extend({}, mapViewOptions, viewOptions); + plotOptionsUpper = $.extend({}, plotOptions, plotOptionsUpper); + plotOptionsLower = $.extend({}, plotOptions, plotOptionsLower); + + super(containerEl, viewOptions, plotOptionsUpper, plotOptionsLower); + + /** + * @type {Number} - Map rotation scaling factor. Multiply current + * map scale to obtain a rotation factor to slow down rotation + * of globe when using mouse. + */ + this.mapRotationFactor = + (this.options.maxMapRotation - this.options.minMapRotation) / + (this.options.maxMapScale - this.options.minMapScale); + + // Update SVG structure for map plots + this.updateSvgStructure(); + /** @type {Panel} */ + this.lowerPanel = this.updatePlotPanelObject(this.lowerPanel); + /** @type {Panel} */ + this.upperPanel = this.updatePlotPanelObject(this.upperPanel); + + /** @type {String} */ + this.mapBorderUrl = '/nshmp-haz-ws/data/us.json'; + /** @type {String */ + this.mapUrl = '/nshmp-haz-ws/data/americas.json'; + + let defaultView = this.options.defaultView == 'closeUp' ? + this.options.mapCloseUp : this.options.mapOverview; + + /** @type {D3Satellite} - D3 satellite (tilted perspective) projection */ + this.projection = d3.geoSatellite() + .center(defaultView.center) + .distance(defaultView.distance) + .tilt(defaultView.tilt) + .scale(defaultView.scale) + .clipAngle(Math.acos( 1 / defaultView.distance) * + 180 / Math.PI); + + /** @type {D3GeoPath} - D3 geographic path generator */ + this.path = d3.geoPath() + .projection(this.projection); + + /** @type {D3Graticule} - D3 graticule generator */ + this.graticule = d3.geoGraticule(); + } + + /** + * @method checkMapView + * + * Check if current projection parameters match that of the + * overview or close up projection parameters and update buttons. + * @param {Panel} panel - Panel with plot map. + * @param {Array<Number, Number, Number>} rotate - Original rotation + * array for projection: [lambda, phi, gamma]. + */ + checkMapView(panel, originalRotate) { + let overview = this.options.mapOverview; + let closeUp = this.options.mapCloseUp; + let distance = this.projection.distance(); + let clipAngle = this.projection.clipAngle(); + let scale = this.projection.scale(); + let center = this.projection.center(); + let rotate = this.projection.rotate(); + let tilt = this.projection.tilt(); + let offsetX = this.options.centerXOffset; + let offsetY = this.options.centerYOffsetFunction(center[1]); + let originalClipOverview = (Math.acos( 1 / overview.distance) * + 180 / Math.PI); + let originalClipCloseUp = (Math.acos( 1 / closeUp.distance) + * 180 / Math.PI); + + let originalλ = Number((originalRotate[0] - offsetX).toFixed(5)); + let originalφ = Number((originalRotate[1] + offsetY).toFixed(5)); + let originalÉ£ = originalRotate[2]; + let λ = Number(rotate[0].toFixed(5)); + let φ = Number(rotate[1].toFixed(5)); + let É£ = rotate[2]; + let rotateCheck = originalλ == λ && originalφ == φ && originalÉ£ == É£; + + if (distance == overview.distance && + center[0] == overview.center[0] && + center[1] == overview.center[1] && + scale == overview.scale && + clipAngle == originalClipOverview && + rotateCheck) { + d3.select(this.plotFooterEl) + .select('.close-up-btn') + .classed('active', false) + + d3.select(this.plotFooterEl) + .select('.overview-btn') + .classed('active', true) + } else if (distance == closeUp.distance && + center[0] == closeUp.center[0] && + center[1] == closeUp.center[1] && + scale == closeUp.scale && + clipAngle == originalClipCloseUp && + rotateCheck) { + d3.select(this.plotFooterEl) + .select('.close-up-btn') + .classed('active', true) + + d3.select(this.plotFooterEl) + .select('.overview-btn') + .classed('active', false) + } else { + d3.select(this.plotFooterEl) + .select('.close-up-btn') + .classed('active', false) + + d3.select(this.plotFooterEl) + .select('.overview-btn') + .classed('active', false) + } + + } + + /** + * Clear the deagg data + * @param {PlotPanel} panel + */ + clearData(panel) { + console.log(panel.mapEl); + d3.select(panel.deaggEl) + .selectAll('rect') + .remove(); + + d3.select(panel.siteEl) + .selectAll('circle') + .remove(); + } + + /** + * @method createDeaggTooltip + * + * Create a tooltip for the deagg contribution. + * @param {Panel} - Panel with map plot. + */ + createDeaggTooltip(panel) { + let tooltip; + let tooltipOptions = { + offsetX: this.options.deaggWidth, + offsetY: panel.options.tooltipOffsetY, + padding: panel.options.tooltipPadding, + selectionIncrement: panel.options.selectionIncrement, + }; + + d3.select(panel.deaggEl) + .selectAll('rect').on('mouseenter', (d, i, els) => { + let id = els[i].id; + let label = this.idToLabel(panel, id); + let lon = d[0]; + let lat = d[1]; + let deagg = d[2]; + let maxCont = this.maxDeaggContribution(panel.data); + let mouse = d3.mouse(els[i]); + let cx = mouse[0]; + let cy = mouse[1]; + cy = cy - deagg / maxCont * this.options.deaggNormalizationScale; + let tooltipText = [ + label, + panel.options.tooltipText[1] + ' ' + lon, + panel.options.tooltipText[2] + ' ' + lat, + panel.options.tooltipText[3] + ' ' + deagg, + ]; + + tooltip = new D3Tooltip.Builder() + .coordinates(cx, cy) + .dataEl(els[i]) + .options(tooltipOptions) + .plotHeight(panel.svgHeight) + .plotMarginLeft(panel.options.marginLeft) + .plotMarginTop(panel.options.marginTop) + .plotWidth(panel.svgWidth) + .tooltipText(tooltipText) + .tooltipEl(panel.tooltipEl) + .build() + .changeAttribute('fill', this.options.deaggSelectedFill); + + }) + .on('mouseleave', (event) => { + tooltip.changeAttribute('fill', this.options.deaggFill) + .destroy(); + + }); + + } + + /** + * @method createSiteLocationTooltip + * + * Create the tooltip for the site location. + * @param {Panel} - Panel with map plot. + */ + createSiteLocationTooltip(panel) { + let tooltip; + let siteTooltipOptions = { + fontSize: panel.options.tooltipFontSize, + offsetX: panel.options.tooltipOffsetX, + offsetY: panel.options.tooltipOffsetY, + padding: panel.options.tooltipPadding, + selectionIncrement: panel.options.selectionIncrement, + }; + + d3.select(panel.siteEl) + .selectAll('circle') + .on('mouseenter', (d, i, els) => { + let cx = d3.select(els[i]).attr('cx'); + let cy = d3.select(els[i]).attr('cy'); + let lon = d[0]; + let lat = d[1]; + let tooltipText = [ + 'Selected Site', + 'Longitude: ' + lon, + 'Latitude: ' + lat, + ]; + + tooltip = new D3Tooltip.Builder() + .coordinates(cx, cy) + .dataEl(event.target) + .options(siteTooltipOptions) + .plotHeight(panel.svgHeight) + .plotWidth(panel.svgWidth) + .tooltipText(tooltipText) + .tooltipEl(panel.tooltipEl) + .build() + .changeSizeAttribute('r', true /* To increase */); + }) + .on('mouseleave', (event) => { + tooltip.changeSizeAttribute('r', false /* To increase */) + .destroy(); + }); + } + + /** + * @method maxDeaggContribution + * + * Find the maximum deagg contribution value. + * @param {Array<Array<Number, Number, Number>>} data - Deagg data series: + * [ [lon_0, lat_0, z_0], [lon_n, lat_n, z_n] ] + * @return {Number} Max deagg contribution in data series. + */ + maxDeaggContribution(data) { + return d3.max(data, (dataSeries, i) => { + return d3.max(dataSeries, (dataArray) => { + return dataArray[2]; + }) + }); + } + + /** + * @method onMapAltRotation + * + * When mouse is down and meta or alt key is pressed, rotate the map + * in gamma value on left and right mouse move and change distance, + * scale, clipAngle on mouse up and down. + * @param {Panel} panel - Panel with plot map. + * @param {Array<Number, Number, Number>} rotate - Original rotation + * array for projection: [lambda, phi, gamma]. + */ + onMapAltRotation(panel, originalRotate) { + let mapCenterYFactor = + (this.options.maxMapCenterY - this.options.minMapCenterY) / + (this.options.maxMapTilt - this.options.minMapTilt); + + $(panel.svgEl).off('mousemove'); + $(panel.svgEl).on('mousemove', (event) => { + this.checkMapView(panel, originalRotate); + let xMove = event.originalEvent.movementX; + let yMove = event.originalEvent.movementY; + let tilt = this.projection.tilt(); + tilt -= yMove; + if (tilt < this.options.minMapTilt || + tilt > this.options.maxMapTilt) return; + let currentRotate = this.projection.rotate(); + let scale = this.projection.scale(); + let slowRotation = scale * this.mapRotationFactor; + let λ = currentRotate[0]; + let φ = currentRotate[1]; + φ -= yMove * 2; + let É£ = currentRotate[2]; + É£ += xMove / slowRotation; + + let newRotate = [λ, φ, É£]; + let centerY = tilt * mapCenterYFactor; + let center = [0, centerY]; + // Update projection + this.projection.rotate(newRotate) + .center(center) + .tilt(tilt); + // Update map + this.updateMap(panel); + }); + } + + /** + * @method onMapMouseDown + * + * Listen for mouse down on map and call either onMapAltRotation + * or onMapRotation depending if the meta or alt key is down. + * @param {Panel} panel - Panel with plot map. + * @param {Array<Number, Number, Number>} rotate - Original rotation + * array for projection: [lambda, phi, gamma]. + */ + onMapMouseDown(panel, originalRotate) { + let leftClickKeyCode = 1; + $(panel.svgEl).on('mousedown', (event) => { + if (event.which != leftClickKeyCode) return; + if (event.metaKey || event.altKey) { + this.onMapAltRotation(panel, originalRotate); + } else { + this.onMapRotation(panel, originalRotate); + } + }); + } + + /** + * @method onMapMouseUp + * + * When mouse is released, turn off mouse move listener. + * @param {Panel} panel - Panel with map plot. + */ + onMapMouseUp(panel) { + $(panel.svgEl).on('mouseup', () => { + $(panel.svgEl).off('mousemove'); + }); + } + + /** + * @method onMapRotation + * + * When mouse is down, rotate the map in lambda (mouse left and right) and + * phi (mouse up and down). + * @param {Panel} panel - Panel with plot map. + * @param {Array<Number, Number, Number>} rotate - Original rotation + * array for projection: [lambda, phi, gamma]. + */ + onMapRotation(panel, originalRotate) { + let mouseNew; + let mouseOld; + $(panel.svgEl).off('mousemove'); + $(panel.svgEl).on('mousemove', (event) => { + this.checkMapView(panel, originalRotate); + mouseOld = mouseNew; + mouseNew = [event.offsetX, event.offsetY]; + if (mouseOld == undefined) return; + let scale = this.projection.scale(); + let slowRotation = scale * this.mapRotationFactor; + let diffX = (mouseNew[0] - mouseOld[0]) / slowRotation; + let diffY = -(mouseNew[1] - mouseOld[1]) / slowRotation; + let currentRotate = this.projection.rotate(); + let λ = currentRotate[0]; + let φ = currentRotate[1]; + let É£ = currentRotate[2]; + let cosÉ£ = Math.cos(É£ * Math.PI / 180.0 ); + let sinÉ£ = Math.sin(É£ * Math.PI / 180.0 ); + λ += (diffX * cosÉ£) + (diffY * sinÉ£); + φ += -(diffX * sinÉ£) + (diffY * cosÉ£); + let newRotate = [λ, φ, É£]; + // Update projection + this.projection.rotate(newRotate); + // Update map + this.updateMap(panel); + }); + } + + /** + * @method onMapViewChange + * + * On close up and overview button click, change the map view + * accordingly. + * @param {Panel} panel - Panel with map plot. + * @param {Array<Number, Number, Number>} rotate - Original rotation + * array for projection: [lambda, phi, gamma]. + */ + onMapViewChange(panel, rotate) { + $(this.plotFooterEl).find('.view-btns').on('click', (event) => { + let distance; + let tilt; + let clipAngle; + let scale; + let center; + let newRotate; + let inputEl = $(event.target).find('input')[0]; + let value = inputEl.value; + let id = inputEl.id; + let closeUp = this.options.mapCloseUp; + let overview = this.options.mapOverview; + let offsetX = this.options.centerXOffset; + + if (value == 'closeUp') { + // Close up + distance = closeUp.distance; + tilt = closeUp.tilt; + center = closeUp.center; + clipAngle = Math.acos(1 / distance) * 180.0 / Math.PI; + scale = closeUp.scale; + } else { + // Overview + distance = overview.distance; + tilt = overview.tilt; + clipAngle = Math.acos(1 / distance) * 180.0 / Math.PI; + center = overview.center; + scale = overview.scale; + } + let offsetY = this.options.centerYOffsetFunction(center[1]); + newRotate = [rotate[0] - offsetX, rotate[1] + offsetY, rotate[2]]; + // Update projection + this.projection.distance(distance) + .clipAngle(clipAngle) + .rotate(newRotate) + .center(center) + .scale(scale) + .tilt(tilt); + // Update map + this.updateMap(panel); + }); + } + + /** + * @method onMapZoom + * + * Listen for wheel in and out and scale the plot accordingly. + * @param {Panel} panel - Panel with map plot. + * @param {Array<Number, Number, Number>} rotate - Original rotation + * array for projection: [lambda, phi, gamma]. + */ + onMapZoom(panel, originalRotate) { + $(panel.svgEl).on('wheel', (event) => { + this.checkMapView(panel, originalRotate); + let direction = event.originalEvent.deltaY; + let currentScale = this.projection.scale(); + let scale = direction > 0 /* Up */ ? currentScale * 1.025 : + currentScale * 0.975; + if (scale < this.options.minMapScale || + scale > this.options.maxMapScale) return; + // Update projection + this.projection.scale(scale); + // Update map + this.updateMap(panel); + }); + } + + /** + * @method plotAmericas + * + * Plot North and South america borders. + * @param {Panel} panel - Panel to plot maps. + * @param {Array<Object>} americas - North and South America map. Array + * of GeoJson features. + */ + plotAmericas(panel, americas) { + d3.select(panel.mapEl) + .selectAll('path') + .remove(); + + d3.select(panel.mapEl) + .selectAll('path') + .data(americas) + .enter() + .append('path') + .attr('class', 'map') + .attr('d', this.path) + .attr('fill', this.options.mapFill) + .attr('stroke', this.options.mapStroke) + .attr('stroke-width', this.options.mapStrokeWidth); + } + + /** + * @method plotData + * + * Get maps and plot deagg contribution + * @param {Panel} panel - Panel with map plot. + * @param {Array<Number, Number, Number>=} rotate - Optional rotation + * array for projection: [lambda, phi, gamma]. + */ + plotData(panel, rotate = [0, 0, 0]) { + let jsonCall = Tools.getJSONs([this.mapUrl, this.mapBorderUrl]); + + Promise.all(jsonCall.promises).then((maps) => { + let map = maps[0]; + let mapBorders = maps[1]; + + let usBorders = topojson.feature( + mapBorders, + mapBorders.objects.states) + .features; + + let americas = map.features; + this.plotMapAndBorders(panel, rotate, americas, usBorders); + }).catch((errorMessage) => { + NshmpError.throwError(errorMessage); + }); + } + + /** + * @method plotDeaggContribution + * + * Plot deagg contribution on map. + * @param {Panel} panel - Panel with map plot. + */ + plotDeaggContribution(panel) { + d3.select(panel.deaggEl) + .selectAll('rect') + .remove(); + + let maxContribution = this.maxDeaggContribution(panel.data); + + d3.select(panel.deaggEl) + .selectAll('rect') + .data(() => { return panel.data.map((d, i) => { return d[0] })}) + .enter() + .append('rect') + .attr('class', 'deagg-contribution') + .attr('id', (d, i) => { return panel.ids[i]; }) + .attr('x', (d, i) => { return this.projection(d.slice(0,3))[0]; }) + .attr('y', (d, i) => { return this.projection(d.slice(0,3))[1]; }) + .attr('height', (d, i) => { + return d[2] / maxContribution * + this.options.deaggNormalizationScale; + }) + .attr('transform', (d, i) => { + return 'translate(0, ' + (-d[2] / maxContribution * + this.options.deaggNormalizationScale) + ')'; + }) + .attr('width', this.options.deaggWidth) + .attr('fill', this.options.deaggFill) + .attr('stroke', this.options.deaggStroke) + .attr('stroke-width', this.options.deaggStrokeWidth); + } + + /** + * @method plotGraticule + * + * Plot the graticule. + * @param {Panel} panel - Panel with map plot. + */ + plotGraticule(panel) { + d3.select(panel.graticuleEl) + .selectAll('path') + .remove(); + + d3.select(panel.graticuleEl) + .append('path') + .attr('class', 'graticule') + .datum(this.graticule) + .attr('d', this.path) + .attr('fill', this.options.graticuleFill) + .attr('stroke', this.options.graticuleStroke) + .attr('stroke-width', this.options.graticuleStokeWidth); + } + + /** + * @method plotMapAndBorders + * + * Plot North America, US state borders, deagg contribution, + * graticule, and setup listeners for map interactions. + * @param {Panel} panel - Panel with map plot. + * @param {Array<Number, Number, Number>} rotate - Original rotation + * array for projection: [lambda, phi, gamma]. + * @param {Array<Object>} americas - North and South America map. Array + * of GeoJson features. + * @param {Array<Object>} usBorders - US state borders. Array + * of GeoJson features. + */ + plotMapAndBorders(panel, rotate, americas, usBorders) { + d3.select(this.el) + .classed('hidden', false); + + d3.select(this.tableEl) + .classed('hidden', true); + + d3.select(this.metadataTableEl) + .classed('hidden', true); + + d3.select(this.plotBodyEl) + .classed('hidden', false); + + d3.select(panel.plotBodyEl) + .classed('hidden', false); + + d3.select(this.plotFooterEl) + .selectAll('label') + .classed('active', false) + .classed('focus', false); + + d3.select(this.plotFooterEl) + .select('.plot-btn') + .classed('active', true); + + // Create the data table + this.createDataTable(panel); + + // Update projection + let tilt = this.projection.tilt(); + let distance = this.projection.distance(); + let scale = this.projection.scale(); + let center = this.projection.center(); + let offsetX = this.options.centerXOffset; + let offsetY = this.options.centerYOffsetFunction(center[1]); + let newRotate = [ + rotate[0] - offsetX, + rotate[1] + offsetY, + rotate[2] + ]; + this.projection.translate([panel.svgWidth / 2, panel.svgHeight / 2]) + .rotate(newRotate); + this.checkMapView(panel, rotate); + + //Plot graticule + this.plotGraticule(panel); + // Plot map + this.plotAmericas(panel, americas); + // Plot map borders + this.plotStateBorders(panel, usBorders); + //Plot site + this.plotSiteLocation(panel); + //Plot contribution + this.plotDeaggContribution(panel); + + // Listeners + this.onMapMouseDown(panel, rotate); + this.onMapMouseUp(panel); + this.onMapZoom(panel, rotate); + this.onMapViewChange(panel, rotate); + + //Tooltips + this.createSiteLocationTooltip(panel); + this.createDeaggTooltip(panel); + } + + /** + * @method plotSiteLocation + * + * Plot the site location choosen. + * @param {Panel} panel - Panel with map plot. + */ + plotSiteLocation(panel) { + d3.select(panel.siteEl) + .selectAll('circle') + .remove(); + + d3.select(panel.siteEl) + .selectAll('circle') + .data([this.siteLocation]) + .enter() + .append('circle') + .attr('class', 'site-location') + .attr('cx', (d, i) => { return this.projection(d)[0]; }) + .attr('cy', (d, i) => { return this.projection(d)[1]; }) + .attr('r', this.options.pointRadius) + .attr('fill', this.options.siteLocationFill) + .attr('stroke', this.options.siteLocationStroke) + .attr('stroke-width', this.options.siteLocationStrokeWidth) + .style('cursor', 'pointer'); + } + + /** + * @method plotStateBorders + * + * Plot the US state borders + * @param {Panel} panel - Panel with map plot. + * @param {Array<Object>} usBorders - US state borders. Array + * of GeoJson features. + */ + plotStateBorders(panel, usBorders) { + d3.select(panel.mapBorderEl) + .selectAll('path') + .remove(); + + d3.select(panel.mapBorderEl) + .selectAll('path') + .data(usBorders) + .enter() + .append('path') + .attr('class', 'borders') + .attr('d', this.path) + .attr('fill', 'none') + .attr('stroke', this.options.mapBorderStroke) + .attr('stroke-width', this.options.mapBorderStrokeWidth); + } + + /** + * Save the figure + * @param {PlotPanel} panel Plot panel to save + * @param {D3SaveFigureOptions} saveOptions The save options + * @param {String} plotFormat The plot format to save + * @param {Boolean} previewFigure Whether to preview figure + */ + saveFigure(panel, saveOptions, plotFormat, previewFigure) { + let svgCloneD3 = d3.select(panel.svgEl.cloneNode(true)); + + let builder = D3SaveFigure.builder() + .currentSvgHeight(panel.svgHeight) + .currentSvgWidth(panel.svgWidth) + .filename(panel.plotFilename) + .options(saveOptions) + .metadata(this.metadata) + .plotFormat(plotFormat) + .plotTitle(this.plotTitleEl.textContent) + .svgEl(svgCloneD3.node()); + + if(previewFigure) builder.previewFigure(); + builder.build(); + } + + /** + * @method updatePlotPanelObject + * + * Add properties to the panel object. + * @return {Panel} - Updated panel. + */ + updatePlotPanelObject(panelObject) { + let panelEl = panelObject.plotBodyEl; + let panelUpdate = { + deaggEl: panelEl.querySelector('.deagg-contribution-group'), + mapEl: panelEl.querySelector('.map-group'), + mapBorderEl: panelEl.querySelector('.map-border-group'), + graticuleEl: panelEl.querySelector('.graticule-group'), + siteEl: panelEl.querySelector('.site-location-group'), + }; + + return $.extend({}, panelObject, panelUpdate); + } + + /** + * @method updateMap + * + * Update graticule, North and South america, US state borders, + * and deagg contribution. + * @param {Panel} panel - Panel with map plots. + */ + updateMap(panel) { + // Update site location + d3.select(panel.siteEl) + .selectAll('circle') + .attr('cx', (d, i) => { return this.projection(d)[0]; }) + .attr('cy', (d, i) => { return this.projection(d)[1]; }) + + // Update Graticule + d3.select(panel.graticuleEl) + .select('path') + .datum(this.graticule) + .attr('d', this.path); + + // Update Americas map + d3.select(panel.mapEl) + .selectAll('path') + .attr('d', this.path); + + // Update state borders + d3.select(panel.mapBorderEl) + .selectAll('path') + .attr('d', this.path); + + // Update deagg contribution + d3.select(panel.deaggEl) + .selectAll('rect') + .attr('x', (d, i) => { return this.projection(d.slice(0,3))[0]; }) + .attr('y', (d, i) => { return this.projection(d.slice(0,3))[1]; }); + } + + /** + * @method updateSvgStructure + * + * Update the SVG structure for geo deagg specific. + */ + updateSvgStructure() { + let svgD3 = d3.select(this.el) + .select('.panel-outer') + .selectAll('svg') + .attr('class', 'D3GeoDeagg') + .style('user-select', 'none') + .style('-webkit-user-select', 'none') + .selectAll('.plot'); + + svgD3.append('g').attr('class', 'graticule-group'); + svgD3.append('g').attr('class', 'map-group'); + svgD3.append('g').attr('class', 'map-border-group'); + svgD3.append('g').attr('class', 'site-location-group'); + svgD3.append('g').attr('class', 'deagg-contribution-group'); + } + + /** + * @override + * @method withPlotFooter + * + * Creates the footer in the plot panel a plot/data button. + * This method is chainable. + * @return {D3GeoDeagg} - Return the class instance to be chainable + */ + withPlotFooter() { + let buttons = [ + { + class: 'view-btns', + col: 'col-xs-5', + btns: [ + { + name: 'overview', + value: 'over', + text: 'Overview', + class: 'overview-btn', + }, { + name: 'close-up', + value: 'closeUp', + text: 'Close Up', + class: 'close-up-btn', + } + ] + },{ + class: 'plot-data-btns', + col: 'col-xs-7', + btns: [ + { + name: 'plot', + value: 'plot', + text: 'Plot', + class: 'plot-btn', + }, { + name: 'data', + value: 'data', + text: 'Data', + class: 'data-btn', + }, { + name: 'metadata', + value: 'metadata', + text: 'Metadata', + class: 'metadata-btn', + } + ] + } + ]; + + this.createPanelFooter(buttons); + this.onPlotDataViewSwitch(); + + return this; + } + +} diff --git a/webapp/apps/js/lib/D3SaveData.js b/webapp/apps/js/lib/D3SaveData.js new file mode 100644 index 000000000..43ea31a79 --- /dev/null +++ b/webapp/apps/js/lib/D3SaveData.js @@ -0,0 +1,96 @@ +'use strict'; + +/** +* @class D3SaveData +* +* @fileoverview Save multiple data sets as a CSV or TSV file. +* Use Builder to set multiple data sets. +*/ +export default class D3SaveData { + + constructor(builder) { + this.dataSeries = builder.data; + this.filename = builder.filename; + this.fileFormat = builder.fileFormat; + this.dataSeriesLabels = builder.dataSeriesLabels; + this.dataRowLabels = builder.dataRowLabels; + + let delimiter = this.fileFormat == 'txt' ? '\t' : ','; + let dataRow = []; + + this.dataSeries.forEach((series, ids) => { + series.forEach((data, id) => { + let dataTranspose = d3.transpose(data); + dataRow.push([ + this.dataRowLabels[ids][0], + this.dataSeriesLabels[ids][id]]); + + dataTranspose.forEach((dataArray, ida) => { + dataRow.push([ + this.dataRowLabels[ids][ida + 1], + dataArray.join(delimiter) + ]); + }) + dataRow.push(''); + }); + dataRow.push('\n'); + }); + + let file = new Blob( + [dataRow.join('\n')], + {type:'text/' + this.fileFormat} + ); + let aEl = document.createElement('a'); + aEl.download = this.filename + '.' + this.fileFormat; + aEl.href = URL.createObjectURL(file); + aEl.click(); + aEl.remove(); + } + + /** + * @method Builder + * + * Builder for D3SaveData + */ + static get Builder() { + return class Builder { + + constructor() { + this.data = []; + this.dataRowLabels = []; + this.dataSeriesLabels = []; + } + + build() { + return new D3SaveData(this); + } + + addData(data) { + this.data.push(data); + return this; + } + + addDataRowLabels(dataRowLabels) { + this.dataRowLabels.push(dataRowLabels); + return this; + } + + addDataSeriesLabels(dataSeriesLabels) { + this.dataSeriesLabels.push(dataSeriesLabels); + return this; + } + + filename(filename) { + this.filename = filename; + return this; + } + + fileFormat(fileFormat) { + this.fileFormat = fileFormat.toLowerCase(); + return this; + } + + } + } + +} diff --git a/webapp/apps/js/lib/D3SaveFigure.js b/webapp/apps/js/lib/D3SaveFigure.js new file mode 100644 index 000000000..b8ed5c71f --- /dev/null +++ b/webapp/apps/js/lib/D3SaveFigure.js @@ -0,0 +1,909 @@ + +import NshmpError from '../error/NshmpError.js'; +import { Preconditions } from '../error/Preconditions.js'; + +/** + * @fileoverview This class will save a SVG element as a figure. + * Figure types: + * - pdf (This brings up the print dialog) + * - png + * - svg + * -jpeg + * + * Use builder for class: + * D3SaveFigure.builder() + * .centerSvgOnEl(SVGElement to center plot on page) + * .centerTitleOnEl(SVGElement to center title on page) + * .currentSvgHeight(SVG height in pixels from veiw box) + * .currentSvgWidth(SVG width in pixels from view box) + * .filename(a download name) + * .options(D3SaveFigureOptions) + * .metadata(Map<String, Array<String>>) + * .plotFormat(The plot format) + * .plotTitle(The plot title) + * .svgEl(Main SVGElement to save) + * .build(); + * + * @author Brandon Clayton + */ +export default class D3SaveFigure { + + /** + * @param {D3SaveFigureBuilder} builder The builder for this class. + * + * @typedef {Object} D3SaveFigureOptions - Options for D3SaveFigure + * @property {Number} footerFontSize Font size of footer in px. + * @property {Number} footerLineBreak Line break in px. + * @property {Number} footerPadding Padding around footer in px. + * @property {Number} marginLeft Left margin for image in inches, only used + * printCenter is false + * @property {Number} marginTop Top margin for image in inches. + * @property {Number} metadataFontSize Font size of metadata table + * @property {Number} metadataMarginTop Margin in inches for table + * @property {Number} metadataColumns Number of key-value columns + * @property {Number} metadataMaxColumnValues Max number of values + * that can reside in a row of a table + * @property {Number} pageHeight Total page height in inches. + * @property {Number} pageWidth Total page width in inches. + * @property {Number} printDpi Resolution of image in DPI. + * @property {Boolean} printCenter Whether to print image in center. + * @property {Boolean} printFooter Whether to print footer. + * @property {Boolean} printMetadata Whether to print the metadata table + * @property {Boolean} printTitle Whether to print title. + * @property {Number} svgHeight Plot height in inches. + * @property {Number} svgWidth Plot width in inches. + * @property {Number} titleFontSize Title font size in px. + */ + constructor(builder) { + Preconditions.checkArgument( + builder.constructor.name == 'Builder', + 'Must use D3SaveFigure.Builder'); + + /** @type {SVGElement} */ + this.centerTitleOnEl = builder._centerTitleOnEl; + /** @type {SVGElement} */ + this.centerSvgOnEl = builder._centerSvgOnEl; + /** @type {String} */ + this.filename = builder._filename; + /** @type {String} */ + this.plotTitle = builder._plotTitle; + /** @type {String} */ + this.plotFormat = builder._plotFormat; + /** @type {Map<String, Array<String>} */ + this.metadata = builder._metadata; + /** @type {Number} */ + this.originalPlotHeight = builder._plotHeight; + /** @type {Number} */ + this.originalPlotWidth = builder._plotWidth; + /** @type {Boolean} */ + this.previewFigure = builder._previewFigure; + /** @type {SVGElement} */ + this.svgEl = builder._svgEl; + /** @type {Number} */ + this.baseDpi = 96; + + /** @type {D3SaveFigureOptions} */ + this.options = { + footerFontSize: 8, + footerLineBreak: 8, + footerPadding: 20, + marginLeft: 0, + marginTop: 0.75, + metadataFontSize: 10, + metadataMarginTop: 0, + metadataColumns: 3, + metadataMaxColumnValues: 5, + pageHeight: 8.5, + pageWidth: 11, + printDpi: 300, + printFooter: true, + printMetadata: true, + printTitle: true, + titleFontSize: 20, + }; + $.extend(this.options, builder._options); + + /* Update DPI */ + let dpi = this.plotFormat == 'svg' ? + this.baseDpi : this.options.printDpi; + this.options.printDpi = dpi; + + /** @type {Number} */ + this.pageHeightPxPrintDpi = this.options.pageHeight * this.options.printDpi; + /** @type {Number} */ + this.pageWidthPxPrintDpi = this.options.pageWidth * this.options.printDpi; + /** @type {Number} */ + this.pageHeightPxBaseDpi = this.options.pageHeight * this.baseDpi; + /** @type {Number} */ + this.pageWidthPxBaseDpi = this.options.pageWidth * this.baseDpi; + /** @type {Number} */ + this.footerHeightInInch = 0.5; + + if (this.previewFigure) { + this._previewFigure(); + } else { + this._saveFigure(); + } + } + + /** + * Return a new instance of the Builder + * @return new Builder + */ + static builder() { + return new D3SaveFigure.Builder(); + } + + /** + * Builder for D3SaveFigure + * + * @typedef {Object} D3SaveFigureBuilder - Builder class + * @property {SVGElement} _centerSvgOnEl A SVG element to use to center + * the figure on the page + * @property {SVGElement} _centerTitleOnEl A SVG element to use to + * center the plot title on the page + * @property {String} _filename Filename for download with no extension. + * @property {String=} _plotTitle Title to add to plot. + * @property {String} _plotFormat Format for download. + * Formats: 'png' || 'svg' || 'pdf' || 'jpeg' + * @property {Map<String, Array<String>>} _metadata The metadata for + * the metadata table and footer + * @property {Number} _plotHeight Original SVG plot height in px. + * @property {Number} _plotWidth Original SVG plot width in px. + * @property {Boolean} _previewFigure Whether to save or preview the figure. + * @property {SVGElement} _svgEl SVG dom element to convert to image. + */ + static get Builder() { + return class Builder { + + constructor() { + /** @type {SVGElement} */ + this._centerSvgOnEl = null; + /** @type {SVGElement} */ + this._centerTitleOnEl = null; + /** @type {String} */ + this._filename = 'download'; + /** @type {Object} */ + this._metadata = null; + /** @type {D3SaveFigureOptions} */ + this._options = {}; + this._options.printMetadata = true; + /** @type {String} */ + this._plotFormat = 'png'; + /** @type {Number} */ + this._plotHeight; + /** @type {String} */ + this._plotTitle = ''; + /** @type {Number} */ + this._plotWidth; + /** @type {Boolean} */ + this._previewFigure = false; + /** @type {SVGElement} */ + this._svgEl; + } + + /** + * Return a new instance of D3SaveFigure + * @return Save the SVG element as a figure. + */ + build() { + Preconditions.checkState(this._plotHeight, 'Current SVG height not set'); + Preconditions.checkState(this._plotWidth, 'Current SVG width not set'); + Preconditions.checkState(this._svgEl, 'SVG element not set'); + Preconditions.checkState( + this._options.printMetadata && this._metadata != null, + 'Metadata not set'); + + return new D3SaveFigure(this); + } + + /** + * A SVG element that is used to center the image. + * @param {SVGElement} centerEl The SVG element + */ + centerSvgOnEl(centerEl) { + Preconditions.checkArgumentInstanceOfSVGElement(centerEl); + this._centerSvgOnEl = centerEl.cloneNode(true); + return this; + } + + /** + * A SVG element that is used to center the plot title. + * @param {SVGElement} centerEl The SVG element + */ + centerTitleOnEl(centerEl) { + Preconditions.checkArgumentInstanceOfSVGElement(centerEl); + this._centerTitleOnEl = centerEl.cloneNode(true); + return this; + } + + /** + * The current SVG width attribute or viewbox width in pixels + * @param {Number} width The width in pixels + */ + currentSvgWidth(width) { + Preconditions.checkArgumentNumber(width); + this._plotWidth = width; + return this; + } + + /** + * The current SVG height attribute or viewbox height in pixels + * @param {Number} height The height in pixels + */ + currentSvgHeight(height) { + Preconditions.checkArgumentNumber(height); + this._plotHeight = height; + return this; + } + + /** + * The download filename + * @param {String} filename The download filename + */ + filename(filename) { + Preconditions.checkArgumentString(filename); + this._filename = filename; + return this; + } + + /** + * The figure options + * @param {D3SaveFigureOptions} options The options + */ + options(options) { + this._options = options; + return this; + } + + /** + * The metadata for the metadata table + * @param {Map<String, Array<String>} metadata The metadata + */ + metadata(metadata) { + Preconditions.checkArgumentInstanceOfMap(metadata); + + for (let [key, value] of metadata) { + Preconditions.checkArgumentString(key); + Preconditions.checkArgumentArray(value); + } + + this._metadata = metadata; + return this; + } + + /** + * The download format. + * Either: 'png' || 'jpeg' || 'pdf || 'svg' + * @param {String} format The download format + */ + plotFormat(format) { + Preconditions.checkArgumentString(format); + + Preconditions.checkArgument( + format == 'jpeg' || format == 'pdf' || + format == 'png' || format == 'svg', + `[${format}] not supported`); + + this._plotFormat = format.toLowerCase(); + return this; + } + + /** + * The plot title + * @param {String} title The plot title + */ + plotTitle(title) { + Preconditions.checkArgumentString(title); + this._plotTitle = title; + return this; + } + + /** + * Whether to preview the figure in a new window + * instead of saving the figure + */ + previewFigure() { + this._previewFigure = true; + return this; + } + + /** + * The SVG element to save as a figure + * @param {SVGElement} svgEl The SVG element to turn into a figure + */ + svgEl(svgEl) { + Preconditions.checkArgumentInstanceOfSVGElement(svgEl); + this._svgEl = svgEl.cloneNode(true); + return this; + } + + } + } + + /** + * Add the metadata URL and date to the bottom of the page. + * @param {SVGElement} svgEl - SVG element to append the footer information. + */ + _addFooter(svgEl) { + if (!this.options.printFooter) return; + + Preconditions.checkState( + this.metadata.has('url'), + 'URL not found in metadata'); + + Preconditions.checkState( + this.metadata.has('date'), + 'Date not found in metadata'); + + let footerText = [ + this.metadata.get('url'), + this.metadata.get('date'), + ]; + + let nlines = footerText.length; + + let footerD3 = d3.select(svgEl) + .append('g') + .attr('class', 'print-footer') + .style('font-size', this.options.footerFontSize) + + footerD3.selectAll('text') + .data(footerText) + .enter() + .append('text') + .text((d,i) => {return footerText[nlines - i - 1]}) + .attr('y', (d,i) => { + return -this.options.footerLineBreak * i; + }); + + this._fitFooter(footerD3.node()); + } + + /** + * Add a table of the metadata to the plot + * @param {SVGElement} svgEl SVG element to append table to + * @param {PlotMargin} plotMargins The plot margins + */ + _addMetadataTable(svgEl, plotMargins) { + if (!this.options.printMetadata) return; + + let tableInfo = this._updateMetadataForTable(); + let maxColumnValues = this.options.metadataMaxColumnValues; + + let foreignD3 = d3.select(svgEl) + .select('g') + .append('foreignObject') + .style('overflow', 'visible'); + + let tableD3 = foreignD3.append('xhtml:table') + .attr('xmlns', 'http://www.w3.org/1999/xhtml') + .style('font-family', '"Helvetica Neue",Helvetica,Arial,sans-serif') + .style('font-size', `${this.options.metadataFontSize}px`) + .style('border-collapse', 'collapse') + .style('background', 'white'); + + for (let data of tableInfo.metadataSet) { + let tableRowD3 = tableD3.append('tr'); + + for (let datum of data) { + let key = datum[0]; + let values = datum[1]; + let rowSpan = values.length > 1 ? tableInfo.rows : 1; + + tableRowD3.append('th') + .attr('nowrap', true) + .attr('valign', 'top') + .attr('rowspan', rowSpan) + .style('padding', '4px 4px 4px 8px') + .style('text-align', 'right') + .html(key); + + let innerTableD3 = tableRowD3.append('td') + .attr('rowspan', rowSpan) + .attr('valign', 'top') + .style('padding', '4px 4px') + .append('table') + .style('font-size', `${this.options.metadataFontSize}px`) + .style('border-collapse', 'collapse') + .style('background', 'white'); + + let nValues = values.length; + if (nValues > maxColumnValues) { + values = values.slice(0, maxColumnValues); + let nMore = nValues - maxColumnValues + values.push(`... and ${nMore} others ...`); + } + + for (let value of values) { + value = Number.isNaN(value) || value == null ? '--' : value; + innerTableD3.append('tr') + .append('td') + .attr('nowrap', true) + .style('text-align', 'left') + .text(value); + } + } + } + + let tableEl = tableD3.node(); + let tableDim = tableEl.getBoundingClientRect(); + let tableWidthInPx = tableDim.width; + let tableHeightInPx = tableDim.height; + + this._fitTable( + foreignD3.node(), + tableHeightInPx, + tableWidthInPx, + plotMargins); + } + + /** + * Add the plot title + * @param {SVGElement} svgEl The SVG element to add the title + * @param {PlotMargin} plotMargins The plot margins + */ + _addPlotTitle(svgEl, plotMargins) { + if (!this.options.printTitle) return; + + let mainGroupD3 = d3.select(svgEl).select('g'); + + let titleY = - plotMargins.marginTopPx / 2; + let titleX = this.centerTitleOnEl != null ? + this.centerTitleOnEl.getBoundingClientRect().width / 2 : 0; + + mainGroupD3.append('text') + .attr('class', 'plot-title') + .attr('x', titleX) + .attr('y', titleY) + .attr('text-anchor', 'middle') + .attr('alignment-baseline', 'middle') + .style('font-size', this.options.titleFontSize) + .text(this.plotTitle); + } + + /** + * Create the canvas element + * + * @typedef {Object} CanvasObject - The canvas element and context + * @property {HTMLCanvasElement} canvasEl The canvas element + * @property {CanvasRenderingContext2D} The canvas context + * @return {CanvasObject} The canvas element and context + */ + _createCanvas() { + let canvasDivD3 = d3.select('body') + .append('div') + .attr('class', 'svg-to-canvas hidden'); + + let canvasD3 = canvasDivD3.append('canvas') + .attr('height', this.pageHeightPxPrintDpi) + .attr('width', this.pageWidthPxPrintDpi) + .style('height', `${this.options.pageHeight}in`) + .style('width', `${this.options.pageWidth}in`); + + let canvasEl = canvasD3.node(); + let canvasContext = canvasEl.getContext('2d'); + canvasDivD3.remove(); + + return {canvasEl: canvasEl, canvasContext: canvasContext}; + } + + + /** + * Create the SVG element + * @return {SVGElement} The svg element + */ + _createSvgElement() { + let iframeD3 = d3.select('body') + .append('iframe') + .style('visibility', 'hidden'); + + let svgDivD3 = d3.select(iframeD3.node().contentWindow.document.body) + .append('div') + .attr('class', 'print-plot-svg') + .html(this.svgEl.outerHTML); + + let svgD3 = svgDivD3.select('svg') + .attr('viewBox', + `0 0 ${this.pageWidthPxPrintDpi} ${this.pageHeightPxPrintDpi}`) + .style('font-family', '"helvetica neue",helvetica,arial,sans-serif') + .attr('height', this.pageHeightPxPrintDpi) + .attr('width', this.pageWidthPxPrintDpi); + + let svgEl = svgD3.node(); + this.centerSvgOnEl = this._findMatchingElement(svgEl, this.centerSvgOnEl); + this.centerTitleOnEl = this._findMatchingElement(svgEl, this.centerTitleOnEl); + let plotMargins = this._translateSvg(svgEl); + this._addPlotTitle(svgEl, plotMargins); + this._addFooter(svgEl); + this._addMetadataTable(svgEl, plotMargins); + + iframeD3.remove(); + + return svgEl; + } + + /** + * Create the SVG image soruce + * @param {SVGElement} svgEl The SVg element for the figure + */ + _createSvgImageSource(svgEl) { + return `data:image/svg+xml;base64, + ${btoa(unescape(encodeURIComponent(svgEl.outerHTML)))}`; + } + + /** + * Draw the Canvas from the SVG image and scale to set the + * correct DPI + * @param {CanvasObject} canvas The canvas element and context + * @param {ImageData} svgImg The SVG image + */ + _drawCanvas(canvas, svgImg) { + let dpiScale = this.options.printDpi / this.baseDpi; + + canvas.canvasContext.scale(dpiScale, dpiScale); + canvas.canvasContext.fillStyle = 'white'; + canvas.canvasContext.fillRect(0, 0, + this.pageWidthPxPrintDpi, this.pageHeightPxPrintDpi); + canvas.canvasContext.drawImage(svgImg, 0, 0, + this.pageWidthPxPrintDpi, this.pageHeightPxPrintDpi); + } + + /** + * Given a SVG element, find the matching element inside the main + * SVG element of the figure + * @param {SVGElement} svgEl The main SVG element of the figure + * @param {SVGElement} centerEl The SVG element to find inside the main + * figure SVG element + */ + _findMatchingElement(svgEl, centerEl) { + if (centerEl == null) return; + + let el = d3.select(svgEl) + .selectAll('*') + .filter((d, i, els) => { + return centerEl.isEqualNode(els[i]); + }).node(); + + if (el == null) { + throw new NshmpError(`Could not find ${centerEl}`); + } + + return el; + } + + /** + * Scale the footer if needed to fit on the page + * @param {SVGElement} footerEl The footer SVG element + */ + _fitFooter(footerEl) { + let footerDim = footerEl.getBoundingClientRect(); + let footerWidthInInch = footerDim.width / this.baseDpi; + let footerHeightInInch = footerDim.height / this.baseDpi; + let footerPaddingInInch = this.options.footerPadding / this.baseDpi; + + let pageWidth = this.options.pageWidth - footerPaddingInInch * 2; + + let widthScale = footerWidthInInch > pageWidth ? + pageWidth / footerWidthInInch : 1; + + let footerHeight = this.footerHeightInch - footerPaddingInInch; + let heightScale = footerHeightInInch > footerHeight ? + footerHeight / footerHeightInInch : 1; + + let footerScale = Math.min(heightScale, widthScale); + + let footerPadding = this.options.footerPadding; + let footerTransform = `translate(${footerPadding}, ` + + `${this.pageHeightPxBaseDpi - footerPadding}) scale(${footerScale})`; + + d3.select(footerEl).attr('transform', footerTransform); + } + + /** + * Scale the metadata table to fit under the figure + * @param {SVGElement} foreignObjectEl The Foriegn Object SVG element + * @param {Number} tableHeightInPx The table height in pixels + * @param {Number} tableWidthInPx The table width in pixels + * @param {PlotMargin} plotMargins The plot margins + */ + _fitTable(foreignObjectEl, tableHeightInPx, tableWidthInPx, plotMargins) { + let svgHeightInInch = this.originalPlotHeight / this.baseDpi; + let tableMarginTopInPx = this.options.metadataMarginTop * this.baseDpi; + + let tableHeightInInch = tableHeightInPx / this.baseDpi; + let tableWidthInInch = tableWidthInPx / this.baseDpi; + + let availableHeight = this.options.pageHeight - + this.options.marginTop - + svgHeightInInch - + this.options.metadataMarginTop - + this.footerHeightInInch; + + let heightScale = tableHeightInInch > availableHeight ? + availableHeight / tableHeightInInch : 1; + + let widthScale = tableWidthInInch > this.options.pageWidth ? + this.options.pageWidth / tableWidthInInch : 1; + + let tableScale = Math.min(heightScale, widthScale); + + let centerTableX = - plotMargins.marginLeftPx + ( this.pageWidthPxBaseDpi - + ( tableWidthInPx * tableScale )) / 2; + + d3.select(foreignObjectEl) + .attr('transform', `scale(${tableScale})`) + .attr('height', tableHeightInPx) + .attr('width', tableWidthInPx) + .attr('y', (this.originalPlotHeight + tableMarginTopInPx) / tableScale) + .attr('x', centerTableX / tableScale); + } + + /** + * Convert the metadata Map into a set of arrays to be used to create + * the table + * @param {Map<String, Array<String>} metadata The metadata Map + * @param {Number} rows The number of rows the table will have + * @param {Number} maxColumns The number of key-value columns the table + * will have + * @param {Boolean} expandColumn Whether the first item in the metadata + * Map will expand all rows + * @return {Set<Array<String, Array<String>>>} The metadata set of arrays + */ + _metadataToSet(metadata, rows, maxColumns, expandColumn) { + let metadataSet = new Set(); + let tmpArray = Array.from(metadata); + + let iStart = 0; + let iEnd = 0; + for (let jl = 0; jl < rows; jl++) { + iStart = iEnd; + iEnd = iStart + maxColumns; + if (jl == 0 && expandColumn) iEnd++; + metadataSet.add(tmpArray.slice(iStart, iEnd)); + } + + return metadataSet; + } + + /** + * Open the figure in a new window to preview + */ + _previewFigure() { + let svgImg = new Image(); + let svgEl = this._createSvgElement(); + + d3.select(svgEl) + .attr('height', this.pageHeightPxBaseDpi * this.options.printDpi) + .attr('width', this.pageWidthPxBaseDpi * this.options.printDpi) + .style('background', 'white'); + + let svgImgSrc = this._createSvgImageSource(svgEl); + let canvas = this._createCanvas(); + + svgImg.onload = () => { + this._drawCanvas(canvas, svgImg); + + try { + switch (this.plotFormat) { + /* SVG format */ + case 'svg': + this._previewFigureNewWindow(svgImgSrc); + break; + /* JPEG or PNG format */ + case 'png': + case 'jpeg': + canvas.canvasEl.toBlob((blob) => { + let canvasImage = URL.createObjectURL(blob); + this._previewFigureNewWindow(canvasImage); + }, 'image/' + this.plotFormat, 1.0); + break; + } + } catch (err) { + throw new NshmpError(err); + } + } + + svgImg.setAttribute('src', svgImgSrc); + } + + /** + * Open the new window with specific image + * @param {ImageData} imageSrc The image source + */ + _previewFigureNewWindow(imageSrc) { + let win = window.open('', '_blank') + win.document.title = this.filename; + win.focus(); + + d3.select(win.document.body) + .style('margin', '0 5em') + .style('background', 'black') + .append('img') + .attr('src', imageSrc) + .style('height', 'auto') + .style('width', 'auto') + .style('max-height', '-webkit-fill-available') + .style('max-width', '100%') + .style('display', 'block') + .style('margin', 'auto') + .style('padding', '2em 0') + .style('top', '50%') + .style('transform', 'translate(0, -50%)') + .style('position', 'relative'); + + this._previewFigureDownloadButton(win, imageSrc); + } + + /** + * Add a download button to the new window for the figure preview + * @param {Window} win The new window + */ + _previewFigureDownloadButton(win, imageSrc) { + d3.select(win.document.body) + .append('button') + .style('background', 'white') + .style('position', 'absolute') + .style('bottom', '0.5em') + .style('left', '1em') + .style('padding', '0.5em') + .style('font-size', '1em') + .style('border-radius', '4px') + .attr('href', imageSrc) + .text('Download') + .on('click', () => { this._saveFigure(); }); + } + + /** + * Save the figure as PDF using jsPDF + * @param {CanvasObject} canvas Canvas element and context + */ + _saveAsPdf(canvas) { + let height = this.options.pageHeight; + let width = this.options.pageWidth; + let pdf = new jsPDF('landscape', 'in', [width, height]); + canvas.canvasEl.toBlob((blob) => { + pdf.addImage( + URL.createObjectURL(blob), + 'PNG', + 0, 0, + width, + height); + + pdf.save(this.filename); + }, 'image/png', 1.0); + } + + /** + * Save the figure as a PNG or JPEG + * @param {CanvasObject} canvas Canvas element and context + * @param {HTMLElement} aEl The HTML element of an anchor + */ + _saveAsPngJpeg(canvas, aEl) { + canvas.canvasEl.toBlob((blob) => { + aEl.href = URL.createObjectURL(blob); + aEl.click(); + }, 'image/' + this.plotFormat, 1.0); + } + + /** + * Save the SVG figure as specified format + */ + _saveFigure() { + let svgImg = new Image(); + let svgEl = this._createSvgElement(); + let svgImgSrc = this._createSvgImageSource(svgEl); + let canvas = this._createCanvas(); + + svgImg.onload = () => { + this._drawCanvas(canvas, svgImg); + + let aEl = document.createElement('a'); + aEl.download = this.filename; + + try { + switch (this.plotFormat) { + /* SVG format */ + case 'svg': + aEl.href = svgImg.src; + aEl.click(); + break; + /* JPEG or PNG format */ + case 'png': + case 'jpeg': + this._saveAsPngJpeg(canvas, aEl); + break; + /* PDF format */ + case 'pdf': + this._saveAsPdf(canvas); + break; + } + } catch (err) { + throw new NshmpError(err); + } + } + + svgImg.setAttribute('src', svgImgSrc); + } + + /** + * Translate the SVG main group + * + * @typedef {Object} PlotMargin + * @property {Number} marginLeftPx Plot margin left in pixels + * @property {Number} marginTopPx Plot margin top in pixels + * + * @param {SVGElement} svgEl The main SVG element of the figure + * @return {PlotMargin} The left and top plot margins in pixels + */ + _translateSvg(svgEl) { + let marginTopPx = this.options.marginTop * this.baseDpi; + let marginLeftPx; + + if (this.centerSvgOnEl != null) { + let centerElDim = this.centerSvgOnEl.getBoundingClientRect(); + let centerElWidthPx = centerElDim.width; + marginLeftPx = ( this.pageWidthPxBaseDpi - centerElWidthPx ) / 2; + } else { + marginLeftPx = this.options.marginLeft * this.baseDpi; + } + + let plotTransform = `translate(${marginLeftPx}, ${marginTopPx})`; + d3.select(svgEl) + .select('g') + .attr('transform', plotTransform); + + return {marginLeftPx: marginLeftPx, marginTopPx: marginTopPx}; + } + + /** + * Update the metadata Map to put the key with a values array + * greater than 1 in the first position and convert Map to + * a Set + * + * @typedef {Object} MetadataTableInfo - Parameters to make table. + * @property {Set<Array<String, Array<String>>>} metadataSet + * Set of metadata + * @property {Number} rows The number of rows in table + * + * @return {MetadataTableInfo} The table parameters + */ + _updateMetadataForTable() { + let metadata = new Map(this.metadata); + metadata.delete('url') + metadata.delete('date'); + + let maxKey; + let maxValue = []; + for (let [key, value] of metadata) { + if (value.length > maxValue.length) { + maxKey = key; + maxValue = value; + } + } + + metadata.delete(maxKey); + let reMapped = new Map(); + reMapped.set(maxKey, maxValue); + for (let [key, value] of metadata) { + reMapped.set(key, value); + } + + let expandColumn = maxValue.length > 1; + let maxColumns = expandColumn ? this.options.metadataColumns - 1 : + this.options.metadataColumns; + + let rows = Math.ceil( reMapped.size / maxColumns ); + let metadataSet = this._metadataToSet( + reMapped, + rows, + maxColumns, + expandColumn); + + return { + metadataSet: metadataSet, + rows: rows, + }; + } + +} diff --git a/webapp/apps/js/lib/D3Tooltip.js b/webapp/apps/js/lib/D3Tooltip.js new file mode 100644 index 000000000..c70a511bb --- /dev/null +++ b/webapp/apps/js/lib/D3Tooltip.js @@ -0,0 +1,220 @@ +'use strict'; + +/** +* @class Tooltip +* +* @fileoverview Create a tooltip +*/ +export default class D3Tooltip { + + /** + * @param {D3TooltipBuilder} builder + */ + constructor(builder) { + this.dataEl = builder.dataEl; + this.tooltipEl = builder.tooltipEl; + this.tooltipText = builder.tooltipText; + this.tooltipX = builder.tooltipX; + this.tooltipY = builder.tooltipY; + this.plotMarginLeft = builder._plotMarginLeft; + this.plotMarginTop = builder._plotMarginTop; + + this.plotHeight = builder.plotHeight - this.plotMarginTop; + this.plotWidth = builder.plotWidth - this.plotMarginLeft; + + this.options = { + fontSize: 12, + offsetX: 2, + offsetY: 8, + padding: 10, + selectionIncrement: 2, + }; + $.extend(this.options, builder.options || {}); + + /** @type {Number} */ + this.offsetX = this.options.offsetX; + /** @type {Number} */ + this.offsetY = this.options.offsetY; + /** @type {Number} */ + this.padding = this.options.padding; + + let tableD3 = d3.select(this.tooltipEl) + .append('foreignObject') + .attr('height', '100%') + .attr('width', '100%') + .append('xhtml:table') + .style('font-size', this.options.fontSize + 'px') + .style('border-collapse', 'separate') + .style('border', '1px solid gray') + .style('border-radius', '5px') + .style('box-shadow', '0 1px 1px rgba(0, 0, 0, 0.05)') + .style('padding', this.padding + 'px') + .style('background', 'white'); + + tableD3.selectAll('tr') + .data(this.tooltipText) + .enter() + .append('tr') + .append('td') + .text((d, i) => { return d; }); + this.tooltipTableEl = tableD3.node(); + + this.tooltipHeight = parseFloat( + d3.select(this.tooltipTableEl).style('height')); + this.tooltipWidth = parseFloat( + d3.select(this.tooltipTableEl).style('width')); + let tooltipTranslation = this.tooltipLocation(); + + d3.select(this.tooltipEl) + .select('foreignObject') + .attr('height', this.tooltipHeight) + .attr('width', this.tooltipWidth) + .attr('transform', tooltipTranslation); + + d3.select(this.tooltipEl) + .raise(); + } + + /** + * @method Builder + * + * D3Tooltip builder. + */ + static get Builder() { + return class Builder { + constructor() {} + + build() { + return new D3Tooltip(this); + } + + coordinates(x, y) { + this.tooltipX = parseFloat(x); + this.tooltipY = parseFloat(y); + return this; + } + + dataEl(dataEl) { + this.dataEl = dataEl; + return this; + } + + options(options) { + this.options = options; + return this; + } + + plotHeight(plotHeight) { + this.plotHeight = plotHeight; + return this; + } + + plotMarginLeft(_plotMarginLeft) { + this._plotMarginLeft = _plotMarginLeft; + return this; + } + + plotMarginTop(_plotMarginTop) { + this._plotMarginTop = _plotMarginTop; + return this; + } + + plotWidth(plotWidth) { + this.plotWidth = plotWidth; + return this; + } + + tooltipEl(tooltipEl) { + this.tooltipEl = tooltipEl; + return this; + } + + tooltipText(tooltipText) { + this.tooltipText = tooltipText; + return this; + } + } + } + + /** + * @method destroy + * + * Method to remove the tooltip and remove all variables + * @param {Panel} panel - Upper or lower panel object + */ + destroy() { + this.remove(); + + for( let obj in this) { + this[obj] = null; + } + } + + /** + * @method changeAttribute + * + * Method to change an attribute on the data element + */ + changeAttribute(attr, value) { + d3.select(this.dataEl).attr(attr, value); + return this; + } + + /** + * @method increaseRadius + * + * Method to increase or decrease an attribute on the data element. + */ + changeSizeAttribute(attr, toIncrease = true) { + let value = parseFloat(d3.select(this.dataEl).attr(attr)); + value = toIncrease ? value + this.options.selectionIncrement : + value - this.options.selectionIncrement; + + d3.select(this.dataEl).attr(attr, value); + return this; + } + + /** + * @method pointColor + * + * Change the dot color + * @param {String} color - The color the dot should be + */ + pointColor(color) { + d3.select(this.selectedEl) + .attr('fill', color); + } + + /** + * @method remove + * + * Method to remove the tooltip + * @param {Panel} panel - Upper or lower panel object + */ + remove() { + d3.select(this.tooltipEl) + .select('*') + .remove(); + } + + /** + * @method tooltipLocation + * + * Find best location to put the tooltip + * @return {String} - The translation needed for the tooltip. + */ + tooltipLocation() { + let availableWidth = this.plotWidth - this.tooltipX; + let x = ( this.tooltipWidth + this.offsetX ) > availableWidth ? + this.tooltipX - this.tooltipWidth - this.offsetX + availableWidth : + this.tooltipX + this.offsetX; + + let availableHeight = this.plotHeight - this.tooltipY; + let y = ( this.tooltipHeight + this.offsetY ) > availableHeight ? + this.tooltipY - this.tooltipHeight - this.offsetY : + this.tooltipY + this.offsetY; + + return `translate(${x}, ${y})`; + } + +} diff --git a/webapp/apps/js/lib/D3View.js b/webapp/apps/js/lib/D3View.js new file mode 100644 index 000000000..8bcf9285c --- /dev/null +++ b/webapp/apps/js/lib/D3View.js @@ -0,0 +1,1384 @@ +'use strict'; + +import D3SaveFigure from './D3SaveFigure.js'; +import D3SaveData from './D3SaveData.js'; +import NshmpError from '../error/NshmpError.js'; + +/** +* @class D3View +* +* @fileoverview D3View class creates the panel in which +* a upper plot and lower plot can reside. Panel can consist of +* a panel header and a panel footer. +* +* The creation of the panel is chainable, for example, to get +* a plot panel that consists of a header and a footer with +* buttons for line plotting: +* let view = new D3View(containerEl) +* .withHeader +* .withLinePlotFooter(); +* +* This class is the parent for the plotting classes and contains chainable +* methods to set the follwing: +* - Plot title +* - Lower/upper data +* - Lower/upper data table title +* - Lower/upper download filename +* - Lower/upper data series ids +* - Lower/upper data series labels +* - Lower/upper metadata +* - Lower/upper X-label +* - Lower/upper Y-label +* +* @author bclayton@usgs.gov (Brandon Clayton) +*/ +export default class D3View { + + /** + * @param {!HTMLElement} containerEl - DOM element to append to + * @param {ViewOptions=} options - General options for plot panel + * @param {PlotOptions=} plotOptionsUpper - Upper plot options + * @param {PlotOptions=} plotOptionsLower - Lower plot options + */ + constructor(containerEl, + options = {}, + plotOptionsUpper = {}, + plotOptionsLower = {}) { + /** @type {HTMLElement} */ + this.containerEl = containerEl; + /** @type {String} */ + this.resizeFull = 'resize glyphicon glyphicon-resize-full'; + /** @type {String} */ + this.resizeSmall = 'resize glyphicon glyphicon-resize-small'; + + /** + * @typedef {Object} ViewOptions + * @property {String} colSizeMin - Bootstrap column panel minimum size. + * Default: 'col-md-6'. + * @property {String} colSizeMinCenter - Bootstrap column panel + * centered min size. + * Default: 'col-md-offset-3 col-md-6'. + * @property {String} colSizeMax - Bootstrap column panel max size. + * Default: 'col-md-offset-1 col-md-10'. + * @property {String} colSizeDefault - Default column size: min or max. + * Default: 'max'. + * @property {Boolean} disableXAxisBtns - Whether to disable X axis buttons. + * Default: false. + * @property {Boolean} disableYAxisBtns - Whether to disable Y axis buttons. + * Default: false. + * @property {Boolean} plotLowerPanel - Whether a plot will exist + * is lower panel. + * Default: false. + * @property {Boolean} printLowerPanel - Wheter to print lower plot. + * Default: true. + * @property {Boolean} syncSelections - Whether to sync upper and + * lower plots when clicking on data. + * Default: false. + * @property {Boolean} syncXAxis - Whether to sync upper and lower plots + * when clicking the X axis log and linear buttons. + * Default: true. + * @property {Boolean} syncYAxis - Whether to sync upper and lower plots + * when clicking the Y axis log and linear buttons. + * Default: true. + * @property {String} xAxisScale - X axis scale: 'log' || 'linear' for + * D3LinePlot. This value is overridden with the PlotOptions value + * if syncXAxis is false. + * Default: 'log'. + * @property {String} yAxisScale - Y axis scale: 'log' || 'linear' for + * D3LinePlot. This value is overridden with the PlotOptions value + * if syncYAxis is false. + * Default: 'log'. + */ + this.options = { + addLegendCheckBtn: true, + addGridLineCheckBtn: true, + disableXAxisBtns: false, + disableYAxisBtns: false, + syncSelections: false, + colSizeMin: 'col-sm-12 col-md-6', + colSizeMinCenter: 'col-sm-offset-1 col-sm-10 col-xl-offset-2 ' + + 'col-xl-8 col-xxl-offset-3 col-xxl-6', + colSizeMax: 'col-sm-12 col-xl-offset-1 col-xl-10 col-xxl-offset-2 col-xxl-8', + colSizeDefault: 'max', + plotLowerPanel: false, + printLowerPanel: true, + syncXAxis: true, + syncYAxis : true, + xAxisScale: 'log', + yAxisScale: 'log', + }; + // Override options + this.options = $.extend({}, this.options, options); + + /** + * @typedef {Object} PlotOptions + * @property {Number} labelFontSize - Font size of X/Y labels in px. + * Default: 16. + * @property {String} lengendLocation - Location of legend: 'topright' || + * 'topleft' || 'bottomright' || 'bottomleft'. Default: 'topright'. + * @property {Number} legendOffset - Offset around legend in px. + * Default: 5. + * @property {Number} legendPaddingX - Padding inside legend in X in px. + * Default: 20. + * @property {Number} legendPaddingY - Padding inside legend in Y in px. + * Default: 15. + * @property {Number} legendLineBreak - Distance between each label in + * the legend in px. Default: 20. + * @property {Number} legendFontSize - Font size of legend in px. + * Default: 14. + * @property {Number} linewidth - Data line linewidth. Default: 2.5. + * @property {Number} linewidthSelection - Data line linewidth when selected. + * Default: 4.5. + * @property {Number} marginBottom - Margin bottom around plot in px. + * Default: 50. + * @property {Number} marginLeft - Margin left around plot in px. + * Default: 70. + * @property {Number} marginRight - Margin right around plot in px. + * Default: 20. + * @property {Number} marginTop - Margin top around plot in px. + * Default: 20. + * @property {Number} plotHeight - Plot height in px for plot aspect. + * Default: 504. + * @property {Number} plotWidth - Plot width in px for plot aspect. + * Default: 896. + * @property {Number} pointRadius - Data point radius. Default: 3.5. + * @property {Number} pointRadiusSelection - Data point radius when selected. + * Default: 5.5. + * @property {Number} pointRadiusTooltip - Data point radius when + * selected and mouse is over data point. Default: 8.5. + * @property {Boolean} printTitle - Whether to add the plot title to the + * printed version. Default: true. + * @property {Boolean} printFooter - Whether to add the metadata to the + * printed version. Default: true. + * @property {Number} printFooterPadding - Padding around printed footer. + * Default: 20. + * @property {Number} printFooterLineBreak - Distance between each line + * in printed footer. Default: 20. + * @property {Number} printFooterFontSize - Font size for printed footer. + * Default: 14. + * @property {Number} printHeight - Printed page height in inches. + * Default: 8.5. + * @property {Number} printWidth - Printed page width in inches. + * Default: 11. + * @property {Number} printPlotWidth - Plot width in inches for printed + * version. Height is determined from plot ratio. + * Default: 10. + * @property {Number} printDpi - Print quality in dots per inch. + * Default: 600. + * @property {Number} printMarginTop - Top margin in inches on page. + * Default: 1. + * @property {Number} printMarginLeft - Left margin in inches on page. + * Default: 0. + * @property {Boolean} showData - Whether to show data in data table view. + * Default: true. + * @property {Boolean} showLegend - Whether to show legend on plot. + * Default: true. + * @property {Number} tickFontSize - Font size of tick labels. + * Default 12. + * @property {Number} tickExponentFontSize - Font size of the exponent + * in the tick label. Default: 10. + * @property {Number} titleFontSize - Font size of the title when printed. + * Default: 20. + * @property {Number} tooltipOffset - Offset in Y in px from data point for + * tooltip. + * Default: 10. + * @property {Number} tooltipPadding - Padding inside tooltip in px. + * Default: 10. + * @property {Array<String>} tooltipText - Array of labels for the tooltip. + * Must be length of three and is ordered: line-label, x-label, y-label. + * Default: ['Label:', 'X Value:', 'Y Value:']. + * @property {Boolean} tooltipXToExponent - Whether to put X values in + * exponent form in tooltip. + * Default: false. + * @property {Boolean} tooltipYToExponent - Whether to put Y values in + * exponent form in tooltip. + * Default: false. + * @property {Number} transitionDuration - Time in miliseconds for + * transitions when updating the plot from linear to log. + * Default: 500. + * @property {String} xAxisLocation - Location for the X axis: + * 'top' || 'bottom'. + * Default: 'bottom'. + * @property {Boolean} xAxisNice - Whether to extend the X domain to round + * number. d3.domain.nice. + * Default: true. + * @property {String} xAxisScale - X axis scale: 'log' || 'linear' for + * D3LinePlot. This value is overridden with the ViewOptions value + * if syncXAxis is true. + * Default: 'log'. + * @property {Number} xLabelPadding - Padding around X-label. + * Default: 8. + * @property {Number} xTickMarks - The number of tick marks in X axis. + * This is only suggested as D3 may override this value. + * Default: 10. + * @property {String} yAxisLocation - Location for the Y axis: + * 'top' || 'bottom'. + * Default: 'bottom'. + * @property {Boolean} yAxisNice - Whether to extend the Y domain to round + * number. d3.domain.nice. + * Default: true. + * @property {String} yAxisScale - Y axis scale: 'log' || 'linear' for + * D3LinePlot. This value is overridden with the ViewOptions value + * if syncYAxis is true. + * Default: 'log'. + * @property {Number} yLabelPadding - Padding around Y-label. + * Default: 10. + * @property {Number} yTickMarks - The number of tick marks in Y axis. + * This is only suggested as D3 may override this value. + * Default: 10. + */ + let plotOptions = { + gridLinewidth: 0.75, + gridLineColor: '#E0E0E0', + labelFontSize: 16, + legendLocation: 'topright', + legendOffset: 5, + legendPaddingX: 20, + legendPaddingY: 15, + legendLineBreak: 20, + legendFontSize: 14, + linewidth: 2.5, + linewidthSelection: 4.5, + marginBottom: 50, + marginLeft: 70, + marginRight: 20, + marginTop: 20, + plotHeight: 504, + plotWidth: 896, + pointRadius: 3.5, + plotReturnPeriod: false, + plotZeroReferenceLine: false, + printTitle: true, + printCenter: true, + printFooter: true, + printFooterPadding: 10, + printFooterLineBreak: 8, + printFooterFontSize: 8, + printPageHeight: 8.5, + printPageWidth: 11, + printDpi: 300, + printMarginTop: 0.5, + printMarginLeft: 1, + printMetadataFontSize: 10, + printMetadataMarginTop: 0, + printMetadataColumns: 3, + printMetadataMaxColumnValues: 5, + printMetadata: true, + referenceLineStroke: '#9E9E9E', + referenceLineStrokeWidth: 1, + selectionIncrement: 2, + showData: true, + showLegend: true, + tickFontSize: 12, + tickExponentFontSize: 10, + titleFontSize: 16, + tooltipFontSize: 12, + tooltipOffsetX: 2, + tooltipOffsetY: 10, + tooltipPadding: 10, + tooltipText: ['Label:', 'X value:', 'Y value'], + tooltipXToExponent: false, + tooltipYToExponent: false, + transitionDuration: 500, + xAxisLocation: 'bottom', + xAxisNice: true, + xAxisScale: this.options.xAxisScale, + xLabelPadding: 8, + xTickMarks: 8, + yAxisLocation: 'left', + yAxisNice: true, + yAxisScale: this.options.yAxisScale, + yLabelPadding: 10, + yTickMarks: 6, + }; + + /** + * @typedef {Array<Number, Number>} SiteLocation - Use setSiteLocation + * method. + * Format: [Logitude, Latitude] + */ + this.siteLocation = undefined; + + /** @type {HTMLElement} */ + this.plotFooterEl = undefined; + /** @type {HTMLElement} */ + this.plotHeaderEl = undefined; + /** @type {HTMLElement} */ + this.plotResizeEl = undefined; + /** @type {HTMLElement} */ + this.plotTitleEl = undefined; + /** @type {HTMLElement} */ + this.saveAsMenuEl = undefined; + + /** @type {HTMLElement} */ + this.el = this.createPlotPanel(); + this.createSvgStructure(); + /** @type {HTMLElement} */ + this.plotBodyEl = this.el.querySelector('.panel-outer'); + /** @type {HTMLElement} */ + this.plotPanelEl = this.el.querySelector('.panel'); + /** @type {HTMLElement} */ + this.metadataTableEl = this.plotBodyEl.querySelector('.metadata-table'); + /** @type {HTMLElement} */ + this.tableEl = this.plotBodyEl.querySelector('.data-table'); + + this.lowerPanel = this.createPlotPanelObject('lower', + $.extend({}, plotOptions, plotOptionsLower)); + + this.upperPanel = this.createPlotPanelObject('upper', + $.extend({}, plotOptions, plotOptionsUpper)); + + // Update SVG view box + this.setSvgViewBox(); + + // TODO: Import jsPDF another way + d3.select('body') + .append('script') + .attr('src', 'https://unpkg.com/jspdf@latest/dist/jspdf.min.js'); + } + + /** + * @method createDataTable + * + */ + createDataTable(panel) { + let panelDim = this.plotBodyEl.getBoundingClientRect(); + let plotRatio = Number((panelDim.width / panelDim.height).toFixed(8)); + + // Update table height and width + d3.select(this.tableEl) + .style('height', panelDim.height + 'px') + .style('width', panelDim.width + 'px'); + + // On window resize + this.onResize(plotRatio); + if (!panel.options.showData) return; + + d3.select(this.tableEl) + .selectAll('.' + panel.panelId + '-tables') + .remove(); + + // Create table + panel.data.forEach((dataSet, ids) => { + let tableBodyD3 = d3.select(this.tableEl) + .append('table') + .attr('class', 'table table-bordered table-condensed ' + + panel.panelId + '-tables') + .append('tbody') + .attr('class', 'data-table-body') + + if(ids == 0){ + tableBodyD3.append('tr') + .append('th') + .attr('class', 'data-table-title') + .attr('colspan', dataSet.length+1) + .text(panel.dataTableTitle); + } + + tableBodyD3.append('tr') + .append('th') + .attr('colspan', dataSet.length+1) + .text(panel.labels[ids]); + + let dataSetTranspose = d3.transpose(dataSet); + dataSetTranspose.forEach((dataArray, ida) => { + let tableRow = tableBodyD3.append('tr') + tableRow.append('td') + .attr('nowrap', true) + .text(panel.options.tooltipText[ida + 1]); + dataArray.forEach((datum) => { + tableRow.append('td') + .text(datum); + }); + }); + }); + } + + /** + * @method createMetadataTable + * + */ + createMetadataTable() { + let panelDim = this.plotBodyEl.getBoundingClientRect(); + let plotRatio = Number((panelDim.width / panelDim.height).toFixed(8)); + + // Update table height and width + d3.select(this.metadataTableEl) + .style('height', panelDim.height + 'px') + .style('width', panelDim.width + 'px'); + + // On window resize + this.onResize(plotRatio); + + d3.select(this.metadataTableEl) + .selectAll('table') + .remove(); + + let tableBodyD3 = d3.select(this.metadataTableEl) + .append('table') + .attr('class', 'table table-bordered table-condensed ') + .append('tbody') + .attr('class', 'metadata-table-body'); + + for (let [key, value] of this.metadata) { + if (key == 'url' || key == 'date') continue; + + let tableRowD3 = tableBodyD3.append('tr') + .style('border', '1px solid #ddd'); + + tableRowD3.append('th') + .attr('nowrap', true) + .html(key); + + tableRowD3.selectAll('tr') + .data(value) + .enter() + .append('tr') + .append('td') + .attr('nowrap', true) + .text((d) => { return d; }); + } + } + + /** + * @method createPanelFooter + * + * Create the panel footer with input buttons. + * @param {Array<Object>} btns - Buttons to add. + * @param {Boolean=} withSaveMenu - Whether to add save menu. + */ + createPanelFooter(btns, withSaveMenu = true) { + let plotFooterD3 = d3.select(this.plotPanelEl) + .append('div') + .attr('class', 'panel-footer'); + + let footerToolbarD3 = plotFooterD3.append('div') + .attr('class', 'btn-toolbar footer-btn-toolbar') + .attr('role', 'toolbar'); + + // Create buttons + let footerBtnsD3 = footerToolbarD3.selectAll('div') + .data(btns) + .enter() + .append('div') + .attr('class', (d, i) => {return d.col + ' footer-btn-group';}) + .append('div') + .attr('class', (d, i) => { + return 'btn-group btn-group-xs btn-group-justified ' + d.class; + }) + .attr('data-toggle', 'buttons') + .attr('role', 'group'); + + footerBtnsD3.selectAll('label') + .data((d, i) => {return d.btns}) + .enter() + .append('label') + .attr('class',(d, i) => { + return 'btn btn-xs btn-default footer-button ' + d.class; + }) + .attr('for', (d, i) => {return d.name}) + .html((d, i) => { + return '<input type="radio" name="' + d.name + '"' + + ' value="' + d.value + '"/> ' + d.text; + }); + + this.plotFooterEl = this.el.querySelector('.panel-footer'); + // Create the save menu + if (withSaveMenu) this.createSaveMenu(); + } + + /** + * @method createPlotPanel + * + * Creates the general plot panel with SVG elements for a upper + * and lower plot. + * @return {D3View} - Return the class instance to be chainable + */ + createPlotPanel() { + if (this.options.colSizeDefault == 'min') { + this.colSize = this.options.colSizeMin; + } else { + this.colSize = this.options.colSizeMax; + } + + let containerD3 = d3.select(this.containerEl); + + let elD3 = containerD3.append('div') + .attr('class', 'D3View hidden ' + this.colSize) + + let plotPanelD3 = elD3.append('div') + .attr('class', 'panel panel-default'); + + let panelBodyD3 = plotPanelD3.append('div') + .attr('class', 'panel-body panel-outer'); + + panelBodyD3.append('div') + .attr('class', 'panel-body panel-upper'); + + panelBodyD3.append('div') + .attr('class', 'panel-body panel-lower') + .classed('hidden', !this.options.plotLowerPanel); + + panelBodyD3.append('div') + .attr('class', 'data-table panel-table hidden'); + + panelBodyD3.append('div') + .attr('class', 'metadata-table panel-table hidden'); + + return elD3.node(); + } + + /** + * @method createPlotPanelObject + * + * @typedef {Object} Panel + * @property {HTMLElement} allDataEl - All data group inside SVG. + * @property {Array<String>} color - Array of colors for the plots. + * Value: d3.schemeCategory10. + * @property {Array<Array<Array<Number, Number>>>} data - Data to plot. + * Format: [ [ [x11, y11], ... ], [ [x21, y21], ...], ...]. + * Use setUpperData or setLowerData methods. + * @property {String} dataTableTitle - Title for data. + * Default: 'Data'. + * Use setUpperDataTableTitle or setLowerDataTableTitle method. + * @property {Array<String>} ids - ID corresponding to each data series. + * Format: [ ['id1'], ['id2], ... ]. + * Use setUpperPlotIds or setLowerPlotIds method. + * @property {Array<String>} labels - Labels corresponding to each + * data series. + * Format: [ ['Label 1'], ['Label 2'], ... ]. + * Use setUpperPlotLabels or setLowerPlotLabels method. + * @property {HTMLElement} legendEl - Legend element. + * @property {Function} line - D3 line function. + * @property {{url: {String}, date: {Date} }} metadata - + * Metadata to print underneath plot. + * Use setUpperMetadata or setLowerMetadata methods. + * @property {PlotOptions} options - Plot options for specific panel. + * @property {String} panelId - Panel id: 'lower' || 'upper'. + * @property {HTMLElement} plotBodyEl - Upper or lower panel body. + * @property {HTMLElement} plotEl - Upper or lower panel plot group inside + * SVG element. + * @property {String} plotFilename - Download filename. + * @property {Number} plotHeight - Calculated plot height. + * options.plotHeight - options.marginBottom - options.marginTop. + * @property {Number} plotWidth - Calculated plot width. + * options.plotWidth - options.marginLeft - options.marginRight. + * @property {Number} plotScale - Calculated value from the current + * panel width Vs. the plot width. plotWidth / panelWidth. + * @property {HTMLElement} svgEl - Upper or lower SVG element. + * @property {Number} svgHeight - options.plotHeight. + * @property {Number} svgWidth - options.plotWidth. + * @property {HTMLElement} tooltipEl - Tooltip element inside SVG. + * @property {HTMLElement} xAxisEl - X-axis element inside SVG. + * @property {Function} xBounds - D3 axis function. d3.range().domain(). + * @property {Array<Number, Number>} xExtremes - Min and max X value. + * Format: [min, max]. + * @property {String} xLabel - X axis label. + * Use setUpperXLabel or setLowerXLabel methods. + * @property {HTMLElement} yAxisEl - Y-axis element inside SVG. + * @property {Function} yBounds - D3 axis function. d3.range().domain(). + * @property {Array<Number, Number>} yExtremes - Min and max Y value. + * Format: [min, max]. + * @property {String} yLabel - Y axis label. + * Use setUpperYLabel or setLowerYLabel methods. + */ + createPlotPanelObject(panel, options) { + panel = panel.toLowerCase(); + let panelEl = this.el.querySelector('.panel-' + panel); + let svgHeight = options.plotHeight; + let svgWidth = options.plotWidth; + let plotHeight = svgHeight - + options.marginTop - options.marginBottom; + let plotWidth = svgWidth - + options.marginLeft - options.marginRight; + return { + data: undefined, + dataTableTitle: 'Data', + ids: undefined, + labels: undefined, + metadata: undefined, + options: options, + panelId: panel + '-panel', + plotBodyEl: panelEl, + plotEl: panelEl.querySelector('.plot'), + plotFilename: 'figure', + plotHeight: plotHeight, + plotWidth: plotWidth, + svgEl: panelEl.querySelector('svg'), + svgHeight: svgHeight, + svgWidth: svgWidth, + tooltipEl: panelEl.querySelector('.d3-tooltip'), + }; + + } + + /** + * @method createSvgStructure + * + * Create the basic SVG structure + */ + createSvgStructure() { + let svgD3 = d3.select(this.el) + .select('.panel-outer') + .selectAll('.panel-body') + .append('svg') + .attr('version', 1.1) + .attr('xmlns', 'http://www.w3.org/2000/svg') + .attr('preserveAspectRatio', 'xMinYMin meet'); + + let plotD3 = svgD3.append('g') + .attr('class', 'plot'); + + plotD3.append('g') + .attr('class', 'd3-tooltip'); + } + + /** + * @method createSaveMenu + * + * Creates the dropup save menu in the panel footer. + */ + createSaveMenu() { + let saveAsD3 = d3.select(this.plotFooterEl) + .append('span') + .attr('class', 'dropup icon'); + + saveAsD3.append('div') + .attr('class', 'glyphicon glyphicon-save' + + ' footer-button dropdown-toggle') + .attr('data-toggle', 'dropdown') + .attr('aria-hashpop', true) + .attr('aria-expanded', true); + + let saveMenu = [ + { label: 'Preview Figure as:', format: 'dropdown-header', type: 'preview' }, + { label: 'JPEG', format: 'jpeg', type: 'preview' }, + { label: 'PNG', format: 'png', type: 'preview' }, + { label: 'SVG', format: 'svg', type: 'preview' }, + { label: 'Save Figure As:', format: 'dropdown-header', type: 'plot' }, + { label: 'JPEG', format: 'jpeg', type: 'plot' }, + { label: 'PDF', format: 'pdf', type: 'plot' }, + { label: 'PNG', format: 'png', type: 'plot' }, + { label: 'SVG', format: 'svg', type: 'plot' }, + { label: 'Save Data As:', format: 'dropdown-header', type: 'data' }, + { label: 'CSV', format: 'csv', type: 'data' }, + ]; + + let saveListD3 = saveAsD3.append('ul') + .attr('class', 'dropdown-menu dropdown-menu-right save-as-menu') + .attr('aria-labelledby', 'save-as-menu') + .style('min-width', 'auto'); + + let saveDataEnter = saveListD3.selectAll('li') + .data(saveMenu) + .enter() + .append('li'); + + saveDataEnter.filter((d) => { return d.format == 'dropdown-header'}) + .text((d) => { return d.label; }) + .attr('class', (d) => { return d.format; }) + .style('cursor', 'initial'); + + saveDataEnter.filter((d) => { return d.format != 'dropdown-header'}) + .html((d) => { + return `<a data-format=${d.format} data-type=${d.type}> ${d.label} </a>`; + }) + .style('cursor', 'pointer'); + + this.saveAsMenuEl = this.el.querySelector('.save-as-menu'); + + this.onSaveMenuClick(); + } + + + + /** + * @method hide + * + * Hide or show the plot panel + * @param {!Boolean} toHide - Whether to hide the plot panel + */ + hide(toHide){ + d3.select(this.el).classed('hidden', toHide); + } + + /** + * @method idToLabel + * + * Given and ID, return the corresponding label. + * @param {Panel} panel - Panel with data. + * @param {String} id - ID to search and match. + * @return {String} - Label of matching ID. + */ + idToLabel(panel, id) { + let iLabel = panel.ids.findIndex((d, i) => { return d == id; }); + return panel.labels[iLabel]; + } + + /** + * @method onPlotDataViewSwitch + * + */ + onPlotDataViewSwitch() { + $(this.plotFooterEl).find('.plot-data-btns').on('click', (event) => { + let selectedValue = $(event.target).find('input').val(); + let panelDim = this.plotBodyEl.getBoundingClientRect(); + + if (selectedValue == 'plot'){ + d3.select(this.tableEl) + .classed('hidden', true); + + d3.select(this.metadataTableEl) + .classed('hidden', true); + + d3.select(this.upperPanel.plotBodyEl) + .classed('hidden', false); + + if (this.options.plotLowerPanel) + d3.select(this.lowerPanel.plotBodyEl) + .classed('hidden', false); + }else if (selectedValue == 'data') { + d3.select(this.tableEl) + .classed('hidden', false) + .style('height', panelDim.height + 'px') + .style('width', panelDim.width + 'px'); + + d3.select(this.upperPanel.plotBodyEl) + .classed('hidden', true); + + d3.select(this.metadataTableEl) + .classed('hidden', true); + + if (this.options.plotLowerPanel){ + d3.select(this.lowerPanel.plotBodyEl) + .classed('hidden', true); + } + } else if (selectedValue == 'metadata') { + d3.select(this.metadataTableEl) + .classed('hidden', false) + .style('height', panelDim.height + 'px') + .style('width', panelDim.width + 'px'); + + d3.select(this.upperPanel.plotBodyEl) + .classed('hidden', true); + + d3.select(this.tableEl) + .classed('hidden', true); + + if (this.options.plotLowerPanel){ + d3.select(this.lowerPanel.plotBodyEl) + .classed('hidden', true); + } + } + }); + } + + /** + * @method onPanelResize + * + * Resizes the plot panel when the resize glyphicon is clicked + */ + onPanelResize() { + d3.select(this.metadataTableEl) + .classed('hidden', true); + + d3.select(this.tableEl) + .classed('hidden', true); + + d3.select(this.plotFooterEl) + .select('.plot-data-btns') + .selectAll('label') + .classed('active', false); + + d3.select(this.plotFooterEl) + .select('.plot-btn') + .classed('active', true); + + d3.select(this.upperPanel.plotBodyEl) + .classed('hidden', false); + + if (this.options.plotLowerPanel) + d3.select(this.lowerPanel.plotBodyEl) + .classed('hidden', false); + + let nplots = d3.selectAll('.D3View') + .filter((d, i, els) => { + return !d3.select(els[i]).classed('hidden') + }).size(); + + let isMax = d3.select(this.el) + .classed(this.options.colSizeMax); + + d3.select(this.el) + .classed(this.options.colSizeMax, false) + .classed(this.options.colSizeMin, false) + .classed(this.options.colSizeMinCenter, false) + + if (isMax){ + this.colSize = nplots == 1 ? this.options.colSizeMinCenter : + this.options.colSizeMin; + d3.select(this.el) + .classed(this.options.colSizeMinCenter, false) + .classed(this.colSize, true); + d3.select(this.plotResizeEl) + .attr('class', this.resizeFull); + }else{ + this.colSize = this.options.colSizeMax; + d3.select(this.el) + .classed(this.colSize, true); + d3.select(this.plotResizeEl) + .attr('class', this.resizeSmall); + } + } + + /** + * Add event listener to the save menu + */ + onSaveMenuClick() { + $(this.saveAsMenuEl).find('a').on('click', (event) => { + let saveType = event.target.getAttribute('data-type'); + let saveFormat = event.target.getAttribute('data-format'); + this.onSaveMenuClickHandler(saveType, saveFormat); + }); + } + + /** + * Handle the save menu click + * @param {String} saveType The save type: 'data' || 'plot' || 'preview' + * @param {String} saveFormat The save format: 'svg' || 'jpeg' ... + */ + onSaveMenuClickHandler(saveType, saveFormat) { + switch (saveType) { + case 'data': + this.saveMenuDataHandler(saveFormat); + break; + case 'plot': + this.saveMenuFigureHandler(saveFormat); + break; + case 'preview': + this.saveMenuFigureHandler(saveFormat, true /* preview figure */); + break; + default: + throw new NshmpError(`IllegalStateException: + Type [${saveType}] with format [${saveFormat}] not supported`); + } + } + + /** + * Save the data + * @param {String} saveFormat The save format for the data: 'csv' + */ + saveMenuDataHandler(saveFormat) { + let saveBuilder = new D3SaveData.Builder() + .filename(this.upperPanel.plotFilename) + .fileFormat(saveFormat); + + for (let panel of [this.upperPanel, this.lowerPanel]) { + if (!panel.options.showData) continue; + saveBuilder.addData(panel.data) + .addDataRowLabels(panel.options.tooltipText) + .addDataSeriesLabels(panel.labels); + } + + saveBuilder.build(); + } + + /** + * + * @param {String} saveFormat The save format: + * 'jpeg' || 'png' || 'svg' || 'pdf' + * @param {Boolean} previewFigure Whether to preview the figure + * instead of saving the figure + */ + saveMenuFigureHandler(saveFormat, previewFigure) { + if (this.options.plotLowerPanel && this.options.printLowerPanel) { + this.saveFigure( + this.lowerPanel, + this._saveFigureOptions(this.lowerPanel), + saveFormat, + previewFigure); + } + + this.saveFigure( + this.upperPanel, + this._saveFigureOptions(this.upperPanel), + saveFormat, + previewFigure); + } + + /** + * The save options + * @param {PlotPanel} panel The plot panel + * @return {D3SaveFigureOptions} The save options + */ + _saveFigureOptions(panel) { + let saveOptions = { + footerFontSize: panel.options.printFooterFontSize, + footerLineBreak: panel.options.printFooterLineBreak, + footerPadding: panel.options.printFooterPadding, + marginLeft: panel.options.printMarginLeft, + marginTop: panel.options.printMarginTop, + metadataFontSize: panel.options.printMetadataFontSize, + metadataMarginTop: panel.options.printMetadataMarginTop, + metadataColumns: panel.options.printMetadataColumns, + metadataMaxColumnValues: panel.options.printMetadataMaxColumnValues, + printMetadata: panel.options.printMetadata, + pageHeight: panel.options.printPageHeight, + pageWidth: panel.options.printPageWidth, + printDpi: panel.options.printDpi, + printCenter: panel.options.printCenter, + printFooter: panel.options.printFooter, + printTitle: panel.options.printTitle, + titleFontSize: panel.options.titleFontSize, + }; + + return saveOptions; + } + + /** + * @method onResize + * + * Update the table height and width to keep aspect ratio + */ + onResize(plotRatio) { + $(window).off(); + $(window).resize(() => { + let panelDimResize = this.plotBodyEl.getBoundingClientRect(); + let width = panelDimResize.width; + let height = Number((width / plotRatio).toFixed(6)); + + // Update metadata table height and width + d3.select(this.metadataTableEl) + .style('height', height + 'px') + .style('width', width + 'px'); + + // Update table height and width + d3.select(this.tableEl) + .style('height', height + 'px') + .style('width', width + 'px'); + }); + } + + /** + * @method panelResize + * + * Resizes the plot panel + * @param {String} colSize - String identifier to the col size to use. + * Possible values: min or max. + */ + panelResize(colSize){ + d3.select(this.el) + .classed(this.options.colSizeMax, false) + .classed(this.options.colSizeMin, false) + .classed(this.options.colSizeMinCenter, false) + if (colSize == 'min'){ + d3.select(this.el) + .classed(this.options.colSizeMin, true); + d3.select(this.plotResizeEl) + .classed(this.resizeSmall, false) + .classed(this.resizeFull, true) + } + else{ + d3.select(this.el) + .classed(this.options.colSizeMax, true); + d3.select(this.plotResizeEl) + .classed(this.resizeSmall, false) + .classed(this.resizeFull, true) + } + } + + /** + * @method setLowerData + * + * Sets the lower plot data. This method is chainable. + * @param {!Array<Array<Number, Number>>} data - The data to plot + * @return {D3View} - Return the class instance to be chainable + */ + setLowerData(data) { + this.lowerPanel.data = data; + return this; + } + + /** + * @method setLowerDataTableTitle + * + * Sets the lower data table title. This method is chainable. + * @param {!String} title - The title for the lower data + * @return {D3View} - Return the class instance to be chainable + */ + setLowerDataTableTitle(title) { + this.lowerPanel.dataTableTitle = title; + return this; + } + + /** + * @method setLowerPlotFilename + * + * Sets the lower plot filename for download. This method is chainable. + * @param {!String} filename - The filename for downloading + * @return {D3View} - Return the class instance to be chainable + */ + setLowerPlotFilename(filename) { + this.lowerPanel.plotFilename = filename; + return this; + } + + /** + * @method setLowerPlotIds + * + * Sets the lower plot ids. This method is chainable. + * @param {!Array<String>} ids - Array of ids for each data series + * @return {D3View} - Return the class instance to be chainable + */ + setLowerPlotIds(ids) { + this.lowerPanel.ids = ids; + return this; + } + + /** + * @method setLowerPlotLabels + * + * Sets the lower plot labels. This method is chainable. + * @param {!Array<String>} labels - Array of labels for each data series + * @return {D3View} - Return the class instance to be chainable + */ + setLowerPlotLabels(labels) { + this.lowerPanel.labels = labels; + return this; + } + + /** + * @method setLowerTimeHorizon + * + */ + setLowerTimeHorizon(timeHorizon) { + this.lowerPanel.timeHorizon = timeHorizon; + return this; + } + + /** + * @method setLowerXLabel + * + * Sets the lower plot X label. This method is chainable. + * @param {!String} xLabel - X label + * @return {D3View} - Return the class instance to be chainable + */ + setLowerXLabel(xLabel) { + this.lowerPanel.xLabel = xLabel; + return this; + } + + /** + * @method setLowerYLabel + * + * Sets the lower plot Y label. This method is chainable. + * @param {!String} yLabel - Y label + * @return {D3View} - Return the class instance to be chainable + */ + setLowerYLabel(yLabel) { + this.lowerPanel.yLabel = yLabel; + return this; + } + + /** + * @method setMetadata + * + * Sets the metadata and creates the metadata table. + * This method is chainable. + * @param {!Object} metadata - Metadata for plots + * @return {D3View} - Return the class instance to be chainable + */ + setMetadata(metadata) { + this.metadata = metadata; + this.createMetadataTable(); + return this; + } + + /** + * @method setPlotTitle + * + * Sets the plot panel header title. This method is chainable. + * @param {!String} title - Title for plot panel + * @return {D3View} - Return the class instance to be chainable + */ + setPlotTitle(title) { + this.plotTitleEl.textContent = title; + return this; + } + + /** + * @method setPlotScale + * + * Calculate a scaling value from the current plot width and the viewbox + * width. + * @param {Panel} panel - Upper or lower plot panel. + */ + setPlotScale(panel) { + let svgGeom = panel.svgEl.getBoundingClientRect(); + let width = svgGeom.width; + panel.plotScale = panel.svgWidth / width; + } + + /** + * @method setTimeHorizonUsage + * + */ + setTimeHorizonUsage(usage) { + this.timeHorizonUsage = usage; + return this; + } + + /** + * @method setSiteLocation + * + * Sets the site location. This method is chainable. + * @param {!Object} site - Site location + * @property {Number} latitude + * @property {Number} longitude + * @return {D3View} - Return the class instance to be chainable + */ + setSiteLocation(site) { + this.siteLocation = [site.longitude, site.latitude]; + return this; + } + + /** + * @method setSvgViewBox + * + * Update the view box dimensions on the SVG elements + */ + setSvgViewBox() { + // Update lower plot dimensions + d3.select(this.el) + .select('.panel-lower') + .select('svg') + .attr('viewBox', '0 0 ' + this.lowerPanel.svgWidth + + ' ' + this.lowerPanel.svgHeight) + .select('.plot') + .attr('transform', 'translate(' + + this.lowerPanel.options.marginLeft + ',' + + this.lowerPanel.options.marginTop +')'); + + // Update upper plot dimension + d3.select(this.el) + .select('.panel-upper') + .select('svg') + .attr('viewBox', '0 0 ' + this.upperPanel.svgWidth + + ' ' + this.upperPanel.svgHeight) + .select('.plot') + .attr('transform', 'translate(' + + this.upperPanel.options.marginLeft + ',' + + this.upperPanel.options.marginTop +')'); + } + + /** + * @method setUpperData + * + * Sets the upper plot data. This method is chainable. + * @param {!Array<Array<Number, Number>>} data - The data to plot + * @return {D3View} - Return the class instance to be chainable + */ + setUpperData(data) { + this.upperPanel.data = data; + return this; + } + + /** + * @method setUpperDataTableTitle + * + * Sets the upper data table title. This method is chainable. + * @param {!String} title - The title for the upper data + * @return {D3View} - Return the class instance to be chainable + */ + setUpperDataTableTitle(title) { + this.upperPanel.dataTableTitle = title; + return this; + } + + /** + * @method setUpperPlotFilename + * + * Sets the upper plot filename for download. This method is chainable. + * @param {!String} filename - The filename for downloading + * @return {D3View} - Return the class instance to be chainable + */ + setUpperPlotFilename(filename) { + this.upperPanel.plotFilename = filename; + return this; + } + + /** + * @method setUpperPlotIds + * + * Sets the upper plot ids. This method is chainable. + * @param {!Array<String>} ids - Array of ids for each data series + * @return {D3View} - Return the class instance to be chainable + */ + setUpperPlotIds(ids) { + this.upperPanel.ids = ids; + return this; + } + + /** + * @method setUpperPlotLabels + * + * Sets the upper plot labels. This method is chainable. + * @param {!Array<String>} labels - Array of labels for each data series + * @return {D3View} - Return the class instance to be chainable + */ + setUpperPlotLabels(labels) { + this.upperPanel.labels = labels; + return this; + } + + /** + * @method setUpperTimeHorizon + * + */ + setUpperTimeHorizon(timeHorizon) { + this.upperPanel.timeHorizon = timeHorizon; + return this; + } + + /** + * @method setUpperXLabel + * + * Sets the upper plot X label. This method is chainable. + * @param {!String} xLabel - X label + * @return {D3View} - Return the class instance to be chainable + */ + setUpperXLabel(xLabel) { + this.upperPanel.xLabel = xLabel; + return this; + } + + /** + * @method setUpperYLabel + * + * Sets the upper plot Y label. This method is chainable. + * @param {!String} yLabel - Y label + * @return {D3View} - Return the class instance to be chainable + */ + setUpperYLabel(yLabel) { + this.upperPanel.yLabel = yLabel; + return this; + } + + /** + * @method withHeader + * + * Creates a header on the plot panel. This method is chainable. + * @return {D3View} - Return the class instance to be chainable + */ + withPlotHeader() { + let plotHeaderD3 = d3.select(this.plotPanelEl) + .append('div') + .attr('class', 'panel-heading') + .lower(); + + let plotTitleD3 = plotHeaderD3.append('h2') + .attr('class', 'panel-title') + + let plotTitleWidth = this.options.addLegendCheckBtn && + this.options.addGridLineCheckBtn ? 'calc(100% - 8em)' : + this.options.addLegendCheckBtn || + this.options.addGridLineCheckBtn ? 'calc(100% - 5em)' : + 'calc(100% - 2em)'; + + plotTitleD3.append('div') + .attr('class', 'plot-title') + .attr('contenteditable', true) + .style('width', plotTitleWidth); + + let iconsD3 = plotHeaderD3.append('span') + .attr('class', 'icon'); + + if (this.options.addGridLineCheckBtn) { + iconsD3.append('div') + .attr('class', 'grid-line-check glyphicon glyphicon-th') + .attr('data-toggle', 'tooltip') + .attr('title', 'Click to toggle grid lines') + .property('checked', true) + .style('margin-right', '2em'); + + this.gridLinesCheckEl = this.el.querySelector('.grid-line-check'); + $(this.gridLinesCheckEl).tooltip({container: 'body'}); + } + + if (this.options.addLegendCheckBtn) { + iconsD3.append('div') + .attr('class', 'legend-check glyphicon glyphicon-th-list') + .attr('data-toggle', 'tooltip') + .attr('title', 'Click to toggle legend') + .property('checked', true) + .style('margin-right', '2em'); + + this.legendCheckEl = this.el.querySelector('.legend-check'); + $(this.legendCheckEl).tooltip({container: 'body'}); + } + + iconsD3.append('div') + .attr('class',() => { + return this.colSize == this.options.colSizeMin + ? this.resizeFull : this.resizeSmall; + }) + .attr('data-toggle', 'tooltip') + .attr('title', 'Click to resize'); + + this.plotHeaderEl = this.el.querySelector('.panel-heading'); + this.plotResizeEl = this.el.querySelector('.resize'); + this.plotTitleEl = this.el.querySelector('.plot-title'); + + $(this.plotResizeEl).on('click',() => { this.onPanelResize() }); + + $(this.plotResizeEl).tooltip({container: 'body'}); + + return this; + } + + /** + * @method withPlotFooter + * + * Creates the footer in the plot panel a plot/data button. + * This method is chainable. + * @return {D3View} - Return the class instance to be chainable + */ + withPlotFooter() { + let buttons = [ + { + class: 'plot-data-btns', + col: 'col-xs-offset-4 col-xs-4', + btns: [ + { + name: 'plot', + value: 'plot', + text: 'Plot', + class: 'plot-btn', + }, { + name: 'data', + value: 'data', + text: 'Data', + class: 'data-btn', + }, { + name: 'metadata', + value: 'metadata', + text: 'Metadata', + class: 'metadata-btn', + } + ] + } + ]; + + this.createPanelFooter(buttons, true /* With save menu */); + d3.select(this.el) + .select('.plot-btn') + .classed('active', true); + + this.plotFooterEl = this.el.querySelector('.panel-footer'); + // Update buttons + this.onPlotDataViewSwitch(); + + return this; + } + +} diff --git a/webapp/apps/js/lib/Footer.js b/webapp/apps/js/lib/Footer.js new file mode 100644 index 000000000..1bfb3886d --- /dev/null +++ b/webapp/apps/js/lib/Footer.js @@ -0,0 +1,314 @@ + +import { Preconditions } from '../error/Preconditions.js'; +import { WebServiceResponse } from '../response/WebServiceResponse.js'; + +/** +* @class Footer +* +* @fileoverview Creates the footer to be used with all +* nshmp-haz-ws webapps. +* The footer contains two buttons: +* - raw-data: When clicked would open a new +* tab with raw JSON return. +* - update-plot: When clicked would update the plot(s). +* +* @author bclayton@usgs.gov (Brandon Clayton) +* +* +* @typedef {Object} FooterOptions - Options for page footer +* @property {String} position - CSS position: fixed || absolute +* @property {Boolean} rawBtnDisable - Whether the raw button is disabled or not +* @property {Boolean} updateBtnDisable - Whether the update button is disabled +* +* +* @typedef {Object} Server - Server object from nshmp-haz-ws response. +* @property {{ +* url: {String}, +* version: {String}, +* }} nshmp-haz - nshmp-haz info. +* @property {{ +* url: {String}, +* version: {String}, +* }} nshmp-haz-ws - nshmp-haz-ws info. +* @poperty {Number} threads - Number of threads used. +* @property {String} servlet. +* @property {String} calc - Calculation time. +*/ +export default class Footer{ + + /** + * @param {HTMLElement=} containerEl - DOM element to put the footer + */ + constructor(containerEl = document.querySelector('body')) { + /** @type {FooterOptions} */ + this.options = { + position: 'fixed', + rawBtnDisable: false, + updateBtnDisable: false, + }; + + let btns = [ + { + class: 'btn btn-primary', + id: 'update-plot', + text: 'Update', + }, { + class: 'btn btn-danger pull-right', + id: 'raw-data', + text: 'Raw Data', + } + ]; + + // Append footer to body + let footerD3 = d3.select(containerEl) + .append('div') + .attr('class', 'Footer') + .attr('id', 'footer'); + + // Add footer buttons + footerD3.append('div') + .attr('class', 'footer-btns') + .selectAll('button') + .data(btns) + .enter() + .append('button') + .attr('class', (d) => { return d.class; }) + .attr('id', (d) => { return d.id; }) + .text((d) => { return d.text; }) + + let iconsD3 = footerD3.append('div') + .attr('class', 'footer-icons'); + + // Add info icon + iconsD3.append('span') + .attr('class', 'glyphicon glyphicon-info-sign code-info-icon disabled') + .property('disabled', true) + .attr('data-toggle', 'false') + .attr('data-target', '#code-info-collapse'); + + /* Github Icon */ + let githubD3 = iconsD3.append('a') + .attr('href', 'https://github.com/usgs/nshmp-haz-ws/issues/new') + .attr('target', '_blank') + .append('img') + .attr('class', 'github-icon') + .attr('title', 'Submit a GitHub issue') + .attr('alt', 'Submit a GitHub issue') + .attr('data-toggle', 'tooltip') + .attr('src', '/nshmp-haz-ws/apps/img/github.svg'); + + // Add collapsable div for metadata + footerD3.append('div') + .attr('class', 'collapse code-info-collapse') + .attr('id', 'code-info-collapse') + .append('div') + .attr('class', 'well') + .attr('id', 'code-info'); + + + /* + btnRightD3.append('span') + .attr('class', 'glyphicon glyphicon-cog settings-btn') + .attr('title', 'Settings'); + */ + + footerD3.lower(); + + /** @type {HTMLElment} */ + this.footerEl = footerD3.node(); + /** @type {HTMLElment} */ + this.codeInfoEl = this.footerEl.querySelector('#code-info'); + /** @type {HTMLElment} */ + this.codeInfoIconEl = this.footerEl.querySelector('.code-info-icon'); + this.codeInfoCollapseEl = this.footerEl + .querySelector('#code-info-collapse'); + /** @type {HTMLElment} */ + this.rawBtnEl = this.footerEl.querySelector('#raw-data'); + /** @type {HTMLElment} */ + this.updateBtnEl = this.footerEl.querySelector('#update-plot'); + //this.settingsBtnEl = this.footerEl.querySelector('.settings-btn'); + this.githubBtnEl = this.footerEl.querySelector('.github-icon'); + + this.onCodeInfo(); + this.onDocumentKeypress(); + + $(this.githubBtnEl).tooltip({container: 'body'}); + } + + /** + * @method onCodeInfo + */ + onCodeInfo() { + $(this.codeInfoIconEl).on('click', (event) => { + let isDisabled = d3.select(event.target).property('disabled'); + if (isDisabled) return; + $(this.codeInfoCollapseEl).collapse('toggle'); + }); + } + + /** + * @method onDocumentKeypress + */ + onDocumentKeypress() { + let returnKeyCode = 13; + + $(document).keypress((event) => { + let isDisabled = $(this.updateBtnEl).prop('disabled'); + let isAlreadyLoading = $('*').hasClass('Spinner'); + if (event.which == returnKeyCode && !isDisabled && !isAlreadyLoading) { + $(this.updateBtnEl).click(); + } + }); + } + + /** + * @method onInput + * + * Listen for all input changes to a specified element, if there + * is no 'has-error' class present then the update button can be + * visible. + * @param {HTMLElement} el - The element to listen for input changes. + */ + onInput(el, options) { + $(el).on('change input', (event) => { + let hasError; + let val; + $(el).find('input').each((i, d) => { + val = parseFloat(d.value); + hasError = isNaN(val) ? true : $(d.parentNode).hasClass('has-error'); + return !hasError; + }); + + options.updateBtnDisable = hasError; + this.setOptions(options); + }); + } + + /** + * @method onRawDataBtn + * + * Listen for click on raw data button and open the new windows with + * raw data. + * @param {Array<String>} urls - The array of urls to call. + */ + onRawDataBtn(urls) { + $(this.rawBtnEl).off(); + $(this.rawBtnEl).on('click', (event) => { + for (let url of urls) { + window.open(url); + } + }); + } + + /** + * @method removeButtons + * + * Remove the update and raw data buttons + */ + removeButtons() { + d3.select(this.rawBtnEl) + .remove(); + + d3.select(this.updateBtnEl) + .remove(); + + return this; + } + + /** + * @method removeInfoIcon + */ + removeInfoIcon() { + d3.select(this.codeInfoIconEl) + .remove(); + + return this; + } + + /** + * @method setMetadata + * + * Set the collapsable panel with the nshmp-haz version number. + * @param {Server} server - Server object from response. + */ + setMetadata(server) { + if (server == undefined) return; + + d3.select(this.codeInfoIconEl) + .property('disabled', false) + .classed('disabled', false); + + let codeInfo = [ + ['nshmp-haz version: ' + server['nshmp-haz'].version], + ['nshmp-haz-ws version: ' + server['nshmp-haz-ws'].version], + ['Cores used: ' + server['threads']], + ]; + + d3.select(this.codeInfoEl) + .selectAll('div') + .data(codeInfo) + .enter() + .append('div') + .text((d) => { return d; }); + } + + /** + * Set the collapsable panel with the nshmp-haz and nshmp-haz-ws + * version numbers. + * + * @param {WebServiceResponse} response The web service response + */ + setWebServiceMetadata(response) { + Preconditions.checkArgumentInstanceOf(response, WebServiceResponse); + + d3.select(this.codeInfoIconEl) + .property('disabled', false) + .classed('disabled', false); + let server = response.server; + + let codeInfo = [ + [`nshmp-haz version: ${server.nshmpHaz.version}`], + [`nshmp-haz-ws version: ${server.nshmpHazWs.version}`], + [`Cores used: ${server.threads}`], + ]; + + d3.select(this.codeInfoEl) + .selectAll('div') + .data(codeInfo) + .enter() + .append('div') + .text((d) => { return d; }); + } + + /** + * @method setOptions + * + * Set the footer options + * + * @param {FooterOptions} options - Footer options + */ + setOptions(options) { + options.position = options.position == 'fixed' || + options.position == 'absolute' ? options.position : 'fixed'; + + $.extend(this.options, options); + this.updateOptions(); + } + + /** + * @method updateOptions + * + * Update the footer options: whether to disable the footer buttons; + */ + updateOptions() { + d3.select(this.footerEl) + .style('position', this.options.position); + + d3.select(this.rawBtnEl) + .property('disabled', this.options.rawBtnDisable); + + d3.select(this.updateBtnEl) + .property('disabled', this.options.updateBtnDisable); + } + +} diff --git a/webapp/apps/js/lib/Gmm.js b/webapp/apps/js/lib/Gmm.js new file mode 100644 index 000000000..a8335878f --- /dev/null +++ b/webapp/apps/js/lib/Gmm.js @@ -0,0 +1,589 @@ +'use strict'; + +import Constraints from './Constraints.js'; +import Footer from './Footer.js'; +import Header from './Header.js'; +import Settings from './Settings.js'; +import Spinner from './Spinner.js'; +import Tools from './Tools.js'; +import NshmpError from '../error/NshmpError.js'; + +/** +* @fileoverview Parent class for ground motion model based web apps including: +* - HwFw +* - GmmDistance +* - Spectra +* This class contains some common HTML elements, including: +* - #gmms +* - .gmm-alpha +* - .gmm-group +* - #gmm-sorter +* - #inputs +* - #Mw +* - #vs30 +* - #z1p0 +* - #z2p5 +* This class creates the web pages header, footer, spinner, and settings +* elements. +* Once getUsage() is called, the class will call out to the respective +* web service, gmm/distance, gmm/hw-fw, or gmm/spectra, to get the +* usage and build the control panel with values. +* NOTE: If any other classes extend this class the WebApps enum must be +* updated accordingly. +* +* @class Gmm +* @author bclayton@usgs.gov (Brandon Clayton) +*/ +export class Gmm { + + /** + * @param {!String} webApp Identifier of the application being used. + * Possible values: GmmDistance, HwFW, or Spectra + * @param {!String} wsUrl URL to corresponding web service. + * Possible values: /nshmp-haz-ws/gmm/distance, + * /nshmp-haz-ws/gmm/hw-fw, /nshmp-haz-ws/gmm/spectra + */ + constructor(webApp, webServiceUrl, config) { + /** @type {Footer} */ + this.footer = new Footer(); + this.footerOptions = { + rawBtnDisable: true, + updateBtnDisable: true, + }; + this.footer.setOptions(this.footerOptions); + + /** @type {Header} */ + this.header = new Header(); + /** @type {Spinner} */ + this.spinner = new Spinner(); + /** @type {Settings} */ + //this.settings = new Settings(this.footer.settingsBtnEl); + /** @type {String} */ + this.currentWebApp = webApp; + /** @type {String} */ + this.webServiceUrl = webServiceUrl; + + /** @type {HTMLElement} */ + this.controlPanelEl = document.querySelector('#control'); + /** @type {HTMLElement} */ + this.gmmsEl = document.querySelector('#gmms'); + /** @type {HTMLElement} */ + this.gmmAlphaEl = document.querySelector('.gmm-alpha'); + /** @type {HTMLElement} */ + this.gmmGroupEl = document.querySelector('.gmm-group'); + /** @type {HTMLElement} */ + this.gmmSorterEl = document.querySelector('#gmm-sorter'); + /** @type {HTMLElement} */ + this.inputsEl = document.querySelector('#inputs'); + /** @type {HTMLElement} */ + this.MwEl = document.querySelector('#Mw'); + /** @type {HTMLElement} */ + this.vs30El = document.querySelector('#vs30'); + /** @type {HTMLElement} */ + this.z1p0El = document.querySelector('#z1p0'); + /** @type {HTMLElement} */ + this.z2p5El = document.querySelector('#z2p5'); + + /** @type {Object} */ + this.config = config; + + /** + * Web applications extending the Gmm class + * @enum {String} + */ + this.WebApps = { + GMM_DISTANCE: 'GmmDistance', + HW_FW: 'HwFw', + SPECTRA: 'Spectra', + }; + + $(this.footer.updateBtnEl).click((event) => { + $(this.footer.rawBtnEl).off(); + this.footerOptions.rawBtnDisable = false; + this.footer.setOptions(this.footerOptions); + this.updatePlot(); + }); + + // On any input + $(this.inputsEl).on('input', (event) => { this.inputsOnInput(event); }); + + // Setup jQuery tooltip + $('[data-toggle="tooltip"]').tooltip(); + + } + + /** + * @method addToggle + * + * Add toggle behavier to all button children of id. + * @param {!String} id ID of the button element + * @param {!Function} callback + */ + addToggle(id, callback) { + $('#' + id + ' button').click((event) => { + if ($(event.target).hasClass('active')) return; + $(event.target).siblings().removeClass('active'); + $(event.target).addClass('active'); + this.callback_ = callback; + this.callback_(event.target.id); + }); + } + + /** + * @method buildInputs + * + * Process usage response and build form inputs + * @param {!Object} usage JSON response from web service call + */ + buildInputs(usage) { + this.spinner.off(); + let params = usage.parameters; + + // Alphabetical GMMs. + let gmmAlphaOptions = $(); + params.gmm.values.forEach((gmm) => { + gmmAlphaOptions = gmmAlphaOptions.add($('<option>') + .attr('value', gmm.id) + .attr('id', gmm.id) + .text(gmm.label)); + }); + + // Grouped GMMs. + let gmmGroupOptions = $(); + params.group.values.forEach((group) => { + let members = group.data; + let optGroup = $('<optgroup>') + .attr('label', group.label) + .attr('id', group.id); + gmmGroupOptions = gmmGroupOptions.add(optGroup); + optGroup.append(gmmAlphaOptions + .filter((index, gmmOption) => { + return members.includes(gmmOption.getAttribute('value')); + }) + .clone()); + }); + + // Bind option views to sort buttons + $(this.gmmSorterEl).find('input').change((event) => { + let options = event.target.value === 'alpha' ? + gmmAlphaOptions : gmmGroupOptions; + $(this.gmmsEl).empty().append(options); + $(this.gmmsEl).scrollTop(0); + }); + + // Set initial view to groups + $(this.gmmsEl).empty().append(gmmGroupOptions); + + // Populate fields with defaults. + Object.keys(params) + .filter((key) => { + if (key === 'gmm') return false; + if (key === 'group') return false; + return true; + }) + .forEach((key, index) => { + let input = $('input[name="' + key + '"]'); + let inputEl = input[0]; + input.val(params[key].value); + if (inputEl != undefined && inputEl.type != 'radio'){ + Constraints.addTooltip(inputEl, params[key].min, params[key].max); + } + }); + + if (this.currentWebApp != this.WebApps.SPECTRA){ + let imtOptions = $(); + imtOptions = imtOptions.add($('<option>') + .attr('value', 'default') + .text('Select a GMM') + ); + $(this.imtEl).append(imtOptions); + + $(this.gmmsEl).change((event) => { + this.setImts(); + }); + } + + $(this.controlPanelEl).removeClass('hidden'); + + this.checkQuery(gmmAlphaOptions); + } + + /** + * @method calcDistances + * + * Calculate rX, rJB, and rRup + * @return {Array<number, number, number>} [rX, rRup, rX] + */ + calcDistances() { + let rX = this.rX_val(); + let zTop = this.zTop_val(); + let footwall = $(this.hwFwFwEl).hasClass('active'); + let rRup = Math.hypot(rX, zTop); + + if (footwall) { + return [rX, rRup, rX]; + } + + let δ = this.dip_val(); + let W = this.width_val(); + let sinδ = Math.sin(δ); + let cosδ = Math.cos(δ); + let Wx = W * cosδ; + let Wz = W * sinδ; + let rJB = Math.max(0.0, rX - Wx); + let h1 = zTop / cosδ; + let rCut1 = h1 * sinδ; + + if (rX < rCut1) { + return [rJB, rRup, rX]; + } + + let zBot = zTop + Wz; + let h2 = zBot / cosδ; + let rCut2 = Wx + h2 * sinδ; + + if (rX >= rCut2) { + rRup = Math.hypot(zBot, rJB); + return [rJB, rRup, rX]; + } + + /* + * Linear scaling of distance normal + * to top and bottom of fault. + */ + rRup = h1 + (h2 - h1) * ((rX - rCut1) / (rCut2 - rCut1)); + return [rJB, rRup, rX]; + } + + /** + * @method checkRakeRange + * + * Check if rake is normal, reverse, or strike-slip + * @return {number} Rake value + */ + checkRakeRange(mech, value) { + console.log(mech); + let isNormal = value < -45.0 && value > -135.0; + let isReverse = value > 45.0 && value < 135.0; + let isStrike = !isReverse && !isNormal; + if (mech == 'fault-style-reverse') return isReverse ? value : 90.0; + if (mech == 'fault-style-normal') return isNormal ? value : -90.0; + return isStrike ? value : 0.0; + } + + /** + * @method checkQuery + * + * Check URL to see if parameters exist on hash part of URL and plot + * parameters if they are present. + * @param {Array<HTMLElements>} Array of GMM options for GMM select menu + */ + checkQuery(gmmOptions){ + let url = window.location.hash.substring(1); + if (!url) return; + + $(this.gmmGroupEl).removeClass('active'); + $(this.gmmAlphaEl).addClass('active'); + $(this.gmmAlphaEl).find('input').prop('checked', true); + $(this.gmmsEl).empty().append(gmmOptions); + + if (this.currentWebApp == this.WebApps.SPECTRA){ + $('input[type*="checkbox"]').prop('checked', false); + $(this.zHypEl).prop('readOnly', false); + $(this.rRupEl).prop('readOnly', false); + $(this.rJBEl).prop('readOnly', false); + $(this.hwFwHwEl).prop('disabled', true); + $(this.hwFwFwEl).prop('disabled', true); + } + + let pars = url.split('&'); + pars.forEach((par, i) => { + let key = par.split('=')[0]; + let value = par.split('=')[1]; + if (key == 'gmm'){ + $(this.gmmsEl).find('option[value="' + value + '"]') + .prop('selected', true); + } else if (key == 'rMin') { + this.rMin = parseFloat(value); + } else if (key == 'rMax') { + this.rMax = parseFloat(value); + } else { + $('input[name="' + key + '"]').val(value); + } + }); + + if (this.currentWebApp == this.WebApps.SPECTRA){ + this.updateFocalMech(); + } else { + this.setImts(); + } + + let gmm = document.querySelector('#' + this.gmmsEl.value); + gmm.scrollIntoView(); + + this.footerOptions.rawBtnDisable = false; + this.footerOptions.updateBtnDisable = false; + this.footer.setOptions(this.footerOptions); + this.updatePlot(); + } + + /** + * @method dip_val + * + * Return the dip in radians as a float + * @return {number} Dip in radians + */ + dip_val() { + return parseFloat(this.dipEl.value) * Math.PI / 180.0; + } + + /** + * @method getCurrentGmms + * + * Get a list of all selected ground motion models + * @return {Array<String>} - Array of string values corresponding + * to ground motion model. + */ + getCurrentGmms() { + let gmmVals = $(this.gmmsEl).val(); + let gmms = []; + gmmVals.forEach((val) => { + gmms.push(d3.select('#' + val).text()); + }); + + return gmms; + }; + + /** + * @method getUsage + * + * Call web service and get usage information to build control panel + * @param {Function=} callback - Callback function + */ + getUsage(callback = () => {}) { + this.callback = callback; + let url = this.webServiceUrl; + let jsonCall = Tools.getJSON(url); + this.spinner.on(jsonCall.reject); + + jsonCall.promise.then((usage) => { + NshmpError.checkResponse(usage); + this.parameters = usage.parameters; + this.buildInputs(usage); + this.callback(); + }).catch((errorMessage) => { + this.spinner.off(); + NshmpError.throwError(errorMessage); + }); + } + + /** + * @method imtOnChange + * + * Update plot on any IMT change + * @param {Event=} event - The event that triggered the change + */ + imtOnChange(event) { + this.updatePlot(); + } + + /** + * @method inputsOnInput + * + * Check to see if any input field has class "has-error" and + * disable the update button if true. + * @param {Event=} event - The event that triggered it + */ + inputsOnInput(event = null) { + if (event != null) { + let el = event.target; + let id = el.id; + if (el.type == 'text' || el.type == 'number'){ + let minVal = this.parameters[id].min; + let maxVal = this.parameters[id].max; + if (id == 'z1p0' || id == 'z2p5'){ + Constraints.check(el, minVal, maxVal, true /* can have NaN */); + } else { + Constraints.check(el, minVal, maxVal); + } + } + } + + let hasError = $('*').hasClass('has-error'); + let hasNoGmm = $(':selected', this.gmmsEl).length == 0; + if ( !hasNoGmm) { + this.footerOptions.updateBtnDisable = hasError; + this.footer.setOptions(this.footerOptions); + } + } + + /** + * @method rake_val + * + * Return the rake value as a float + * @return {number} Rake + */ + rake_val() { + return parseFloat(this.rakeEl.value); + } + + /** + * @method rX_val + * + * Return rX as a float + * @return {number} rX + */ + rX_val() { + return parseFloat(this.rXEl.value); + } + + /** + * @method setImts + * + * Set the intensity mesure type select menu + */ + setImts(){ + let selectedGmms = $(this.gmmsEl).val(); + let supportedImts = []; + + selectedGmms.forEach((selectedGmm) => { + let gmm = this.parameters.gmm.values.find((gmm) => { + return gmm.id == selectedGmm; + }); + supportedImts.push(gmm.supportedImts); + }); + + let commonImts = this.supportedValues(supportedImts, this.parameters.imt); + + let imtOptions = $(); + commonImts.forEach((imt) => { + imtOptions = imtOptions.add($('<option>') + .attr('value', imt.value) + .text(imt.display) + ); + }); + $(this.imtEl).empty().append(imtOptions); + } + + /** + * @method serializeGmmUrl + * + * Serialize all forms for ground motion web service and + * set the hash of the window location to reflect the form values + * @return {String} URL to call the web service + */ + serializeGmmUrl() { + let inputs = $(this.inputsEl).serialize(); + let url = this.webServiceUrl + '?' + inputs; + window.location.hash = inputs; + + return url; + } + + /** + * @method supportedValues + * + * Find common supported values of a parameters + * @param {Array<String>} values - Array of all supported values + * @param {Object} params + * @return {Object} + */ + supportedValues(values, params){ + let allValues = values.toString().split(","); + let uniqueValues = []; + allValues.forEach((val) => { + if ($.inArray(val, uniqueValues) == -1) uniqueValues.push(val); + }); + + let commonValues = uniqueValues.filter((val, jv) => { + return values.every((d, i) => { + return d.includes(val); + }); + }); + + let supportedParams = params.values.filter((par) => { + return commonValues.find((val) => { + return val == par.value; + }) + }); + + return supportedParams; + } + + /** + * @method updateDistance + * + * Update rJB, rRup, and rX calculations and input fields + */ + updateDistance() { + if (!$(this.rCheckEl).prop('checked')) return; + let r = this.calcDistances(); + $(this.rJBEl).val(r[0].toFixed(2)); + $(this.rRupEl).val(r[1].toFixed(2)); + } + + /** + * @method updateFocalMech + * + * Update focal mech selection based on rake. + */ + updateFocalMech() { + let rake = this.rake_val(); + if (rake > 45.0 && rake < 135.0 + && !$(this.faultStyleReverseEl).hasClass('active')) { + $(this.faultStyleReverseEl).click(); + return; + } + if (rake < -45.0 && rake > -135.0 + && !$(this.faultStyleNormalEl).hasClass('active')) { + $(this.faultStyleNormalEl).click(); + return; + } + if (!$(this.faultStyleStrikeEl).hasClass('active')) { + $(this.faultStyleStrikeEl).click(); + } + } + + /** + * @method updateHypoDepth + * + * Update + */ + updateHypoDepth() { + let hypoDepth = this.zTop_val() + + Math.sin(this.dip_val()) * this.width_val() / 2.0; + $(this.zHypEl).val(hypoDepth.toFixed(2)); + } + + /** + * @method updateRake + * + * Update rake if out of focal mech range + * @param {String} id - ID of fault mech + */ + updateRake(id) { + $(this.rakeEl) + .val(this.checkRakeRange(id, this.rake_val())); + } + + /** + * @method width_val + * + * Return the width + * @return {number} The width + */ + width_val() { + return parseFloat(this.widthEl.value); + } + + /** + * @method zTop_val + * + * Return zTop + * @return {number} zTop + */ + zTop_val() { + return parseFloat(this.zTopEl.value); + } + +} diff --git a/webapp/apps/js/lib/GmmBeta.js b/webapp/apps/js/lib/GmmBeta.js new file mode 100644 index 000000000..1691c4607 --- /dev/null +++ b/webapp/apps/js/lib/GmmBeta.js @@ -0,0 +1,455 @@ +'use strict'; + +import Constraints from './Constraints.js'; +import ControlPanel from './ControlPanel.js'; +import Footer from './Footer.js'; +import Header from './Header.js'; +import NshmpError from '../error/NshmpError.js'; +import Settings from './Settings.js'; +import Spinner from './Spinner.js'; +import Tools from './Tools.js'; + +/** + * @fileoverview Parent class for ground motion model based web apps including: + * - HwFw + * - GmmDistance + * - Spectra + * This class creates the web pages header, footer, spinner, and settings + * elements. + * Once getUsage() is called, the class will call out to the respective + * web service, gmm/distance, gmm/hw-fw, or gmm/spectra, to get the + * usage and build the control panel with values. + * NOTE: If any other classes extend this class the WebApps enum must be + * updated accordingly. + * + * @class Gmm + * @author bclayton@usgs.gov (Brandon Clayton) + */ +export class GmmBeta { + + /** + * @param {!String} webApp Identifier of the application being used. + * Possible values: GmmDistance, HwFW, or Spectra + * @param {!String} wsUrl URL to corresponding web service. + * Possible values: /nshmp-haz-ws/gmm/distance, + * /nshmp-haz-ws/gmm/hw-fw, /nshmp-haz-ws/gmm/spectra + */ + constructor(webApp, webServiceUrl, config) { + /** @type {Footer} */ + this.footer = new Footer(); + this.footerOptions = { + rawBtnDisable: true, + updateBtnDisable: true, + }; + this.footer.setOptions(this.footerOptions); + + /** @type {Header} */ + this.header = new Header(); + /** @type {Spinner} */ + this.spinner = new Spinner(); + /** @type {Settings} */ + //this.settings = new Settings(this.footer.settingsBtnEl); + /** @type {String} */ + this.currentWebApp = webApp; + /** @type {String} */ + this.webServiceUrl = webServiceUrl; + /** The control panel - @type {ControlPanel} */ + this.controlPanel = new ControlPanel(); + + /** @type {Object} */ + this.config = config; + + /** + * Web applications extending the Gmm class + * @enum {String} + */ + this.WebApps = { + GMM_DISTANCE: 'GmmDistance', + HW_FW: 'HwFw', + SPECTRA: 'Spectra', + }; + + $(this.footer.updateBtnEl).click((event) => { + $(this.footer.rawBtnEl).off(); + this.footerOptions.rawBtnDisable = false; + this.footer.setOptions(this.footerOptions); + this.updatePlot(); + }); + } + + /** + * Calculate rX, rJB, and rRup + * + * @return {Array<Number, Number, Number>} [rX, rRup, rX] + */ + calcDistances() { + let rX = this.rX_val(); + let zTop = this.zTop_val(); + let footwall = this.hwFwFwEl.checked; + let rRup = Math.hypot(rX, zTop); + + if (footwall) { + return [rX, rRup, rX]; + } + + let δ = this.dip_val(); + let W = this.width_val(); + let sinδ = Math.sin(δ); + let cosδ = Math.cos(δ); + let Wx = W * cosδ; + let Wz = W * sinδ; + let rJB = Math.max(0.0, rX - Wx); + let h1 = zTop / cosδ; + let rCut1 = h1 * sinδ; + + if (rX < rCut1) { + return [rJB, rRup, rX]; + } + + let zBot = zTop + Wz; + let h2 = zBot / cosδ; + let rCut2 = Wx + h2 * sinδ; + + if (rX >= rCut2) { + rRup = Math.hypot(zBot, rJB); + return [rJB, rRup, rX]; + } + + /* + * Linear scaling of distance normal to top and bottom of fault. + */ + rRup = h1 + (h2 - h1) * ((rX - rCut1) / (rCut2 - rCut1)); + return [rJB, rRup, rX]; + } + + /** + * Check URL to see if parameters exist on hash part of URL and plot + * parameters if they are present. + */ + checkQuery(){ + let url = window.location.hash.substring(1); + if (!url) return; + + $(this.gmmGroupEl).removeClass('active'); + $(this.gmmAlphaEl).addClass('active'); + this.controlPanel.updateSelectOptions(this.gmmsEl, this.gmmAlphaOptions); + + if (this.currentWebApp == this.WebApps.SPECTRA) { + $('input[type*="checkbox"]').prop('checked', false); + $(this.zHypEl).prop('readOnly', false); + $(this.rRupEl).prop('readOnly', false); + $(this.rJBEl).prop('readOnly', false); + $(this.hwFwHwEl.parentNode).toggleClass('disabled', true); + $(this.hwFwFwEl.parentNode).toggleClass('disabled', true); + } + + let urlObject = Tools.urlQueryStringToObject(url); + let multiParam = urlObject.multi; + + for (let key in urlObject) { + let value = urlObject[key]; + + switch(key) { + case 'gmm': + if (multiParam != 'gmms' || !Array.isArray(value)) value = [value]; + + for (let gmm of value) { + $(this.gmmsEl).find(`option[value='${gmm}']`) + .prop('selected', true); + } + break; + case 'rMin' || 'rMax': + this[key] = parseFloat(value); + break; + case multiParam: + if (multiParam == 'gmms') break; + this.multiSelectEl.value = multiParam; + $(this.multiSelectEl).trigger('input'); + let btnGroupEl = d3.select(this.multiSelectEl).data()[0]; + + d3.select(btnGroupEl) + .selectAll('label') + .classed('active', false) + .selectAll('input') + .property('checked', false); + + for (let val of value) { + $(btnGroupEl).find(`[value='${val}']`).click(); + } + $(`#${key}`).trigger('change'); + break; + default: + $(`input[name='${key}']`).val(value); + $(`#${key}`).trigger('input'); + } + + } + + if (this.currentWebApp != this.WebApps.SPECTRA){ + this.setImts(); + } + + let gmm = document.querySelector('#' + this.gmmsEl.value); + gmm.scrollIntoView(); + + $(this.controlPanel.inputsEl).trigger('input'); + $(this.footer.updateBtnEl).click(); + } + + /** + * Return the dip in radians as a float. + * + * @returns {Number} Dip in radians. + */ + dip_val() { + return parseFloat(this.dipEl.value) * Math.PI / 180.0; + } + + /** + * Get a list of all selected ground motion models. + * + * @returns {Array<String>} Array of string values corresponding + * to ground motion model. + */ + getCurrentGmms() { + let gmmVals = $(this.gmmsEl).val(); + gmmVals = Array.isArray(gmmVals) ? gmmVals : [gmmVals]; + let gmms = []; + gmmVals.forEach((val) => { + let text = d3.select(this.gmmsEl) + .select('#' + val) + .text(); + gmms.push(text); + }); + + return gmms; + }; + + /** + * Call web service and get usage information to build control panel. + * + * @param {Function=} callback - Callback function + */ + getUsage(callback = () => {}) { + this._callback = callback; + let url = this.webServiceUrl; + let jsonCall = Tools.getJSON(url); + this.spinner.on(jsonCall.reject); + + jsonCall.promise.then((usage) => { + NshmpError.checkResponse(usage); + this.parameters = usage.parameters; + this.createControlPanel(usage); + this._callback(); + }).catch((errorMessage) => { + this.spinner.off(); + NshmpError.throwError(errorMessage); + }) + } + + /** + * Update plot on any IMT change. + * + * @param {Event=} event - The event that triggered the change + */ + imtOnChange(event) { + this.updatePlot(); + } + + /** + * Check to see if any input field has class "has-error" and + * disable the update button if true. + * + * @param {Event=} event - The event that triggered it + */ + inputsOnInput(event = null) { + if (event != null) { + let el = event.target; + let id = el.id; + if (el.type == 'text' || el.type == 'number'){ + let minVal = el.min || this.parameters[id].min; + let maxVal = el.max || this.parameters[id].max; + if (id == 'z1p0' || id == 'z2p5'){ + Constraints.check(el, minVal, maxVal, true /* can have NaN */); + } else { + Constraints.check(el, minVal, maxVal); + } + } + } + + let hasError = $('*').hasClass('has-error'); + let gmmIsSelected = $(':selected', this.gmmsEl).length > 0; + + let multiSelectParam = this.multiSelectEl.value; + let multiSelectableBtnGroupEl = d3.select(this.multiSelectEl).data()[0]; + + let btnsAreSelected = multiSelectParam == 'gmms' ? gmmIsSelected : + $(':checked', multiSelectableBtnGroupEl).length > 0; + + hasError = hasError || !btnsAreSelected || !gmmIsSelected; + this.footerOptions.updateBtnDisable = hasError; + this.footer.setOptions(this.footerOptions); + } + + /** + * Return the rake value as a float. + * + * @returns {Number} Rake value. + */ + rake_val() { + return parseFloat(this.rakeEl.value); + } + + /** + * Return rX as a float. + * + * @returns {Number} rX + */ + rX_val() { + return parseFloat(this.rXEl.value); + } + + /** + * Set the intensity mesure type select menu. + */ + setImts(){ + let selectedGmms = $(this.gmmsEl).val(); + let supportedImts = []; + + selectedGmms.forEach((selectedGmm) => { + let gmm = this.parameters.gmm.values.find((gmm) => { + return gmm.id == selectedGmm; + }); + supportedImts.push(gmm.supportedImts); + }); + + let commonImts = this.supportedValues(supportedImts, this.parameters.imt); + // let commonImts = Tools + // .supportedParameters(this.parameters.imt, supportedImts); + + let imtOptions = $(); + commonImts.forEach((imt) => { + imtOptions = imtOptions.add($('<option>') + .attr('value', imt.value) + .text(imt.display) + ); + }); + $(this.imtEl).empty().append(imtOptions); + } + + /** + * Serialize all forms for ground motion web service and + * set the hash of the window location to reflect the form values. + * + * @returns {Array<String>} Array of URLs to call the web service. + */ + serializeGmmUrl() { + let inputs = $(this.controlPanel.inputsEl).serialize(); + + let selectedParam = this.multiSelectEl.value; + + if (selectedParam == 'gmms') { + window.location.hash = inputs + '&multi=gmms'; + return [this.webServiceUrl + '?' + inputs]; + } + + let btnGroupEl = d3.select(this.multiSelectEl).data()[0]; + let multiParams = ''; + let urls = []; + + $(':checked', btnGroupEl).each((i, d) => { + let param = btnGroupEl.getAttribute('name'); + multiParams += '&' + param + '=' + d.value; + urls.push(this.webServiceUrl + '?' + inputs + '&' + + param + '=' + d.value); + }); + + window.location.hash = inputs + multiParams + '&multi=' + selectedParam; + + return urls; + } + + /** + * Find common supported values of a parameters. + * + * @param {Array<String>} values - Array of all supported values + * @param {Object} params + * @returns {Object} The supported parameters. + */ + supportedValues(values, params){ + let allValues = values.toString().split(","); + let uniqueValues = []; + allValues.forEach((val) => { + if ($.inArray(val, uniqueValues) == -1) uniqueValues.push(val); + }); + + let commonValues = uniqueValues.filter((val, jv) => { + return values.every((d, i) => { + return d.includes(val); + }); + }); + + let supportedParams = params.values.filter((par) => { + return commonValues.find((val) => { + return val == par.value; + }) + }); + + return supportedParams; + } + + /** + * Update rJB, rRup, and rX calculations and input fields. + */ + updateDistance() { + if (!this.rCheckEl.checked) return; + let r = this.calcDistances(); + $(this.rJBEl).val(r[0].toFixed(2)); + $(this.rRupEl).val(r[1].toFixed(2)); + } + + /** + * Update hypocenter depth. + */ + updateHypoDepth() { + if (this.zCheckEl.checked) { + let hypoDepth = this.zTop_val() + + Math.sin(this.dip_val()) * this.width_val() / 2.0; + + this.zHypEl.min = this.parameters.zHyp.min; + this.zHypEl.max = this.parameters.zHyp.max; + + $(this.zHypEl).val(hypoDepth.toFixed(2)); + $(this.zHypEl).tooltip('destroy'); + $(this.zHypEl).trigger('change'); + } else { + let hypoDepthMax = this.zTop_val() + + Math.sin(this.dip_val()) * this.width_val(); + + hypoDepthMax = parseFloat(hypoDepthMax.toFixed(2)); + this.zHypEl.min = this.zTop_val(); + this.zHypEl.max = hypoDepthMax; + Constraints.addTooltip(this.zHypEl, this.zTop_val(), hypoDepthMax); + $(this.zHypEl).trigger('change'); + } + } + + /** + * Return the width as a float. + * + * @returns {Number} The width + */ + width_val() { + return parseFloat(this.widthEl.value); + } + + /** + * Return zTop as a float. + * + * @returns {Number} zTop + */ + zTop_val() { + return parseFloat(this.zTopEl.value); + } + +} diff --git a/webapp/apps/js/lib/Hazard.js b/webapp/apps/js/lib/Hazard.js new file mode 100644 index 000000000..193f2381d --- /dev/null +++ b/webapp/apps/js/lib/Hazard.js @@ -0,0 +1,565 @@ +'use strict'; + +import Footer from './Footer.js'; +import Header from './Header.js'; +import LeafletTestSitePicker from './LeafletTestSitePicker.js'; +import NshmpError from '../error/NshmpError.js'; +import Settings from './Settings.js'; +import Spinner from './Spinner.js'; +import Tools from './Tools.js'; + +export class Hazard { + + constructor(config){ + this.footer = new Footer(); + this.footerOptions = { + rawBtnDisable: true, + updateBtnDisable: true + }; + this.footer.setOptions(this.footerOptions); + + // Create header + this.header = new Header(); + + // Create spinner + this.spinner = new Spinner(); + + // Settings menu + //this.settings = new Settings(this.footer.settingsBtnEl); + + this.controlEl = document.querySelector("#control"); + this.editionEl = document.getElementById("edition"); + this.regionEl = document.getElementById("region"); + this.imtEl = document.getElementById("imt"); + this.vs30El = document.getElementById("vs30"); + this.latBoundsEl = document.getElementById("lat-bounds"); + this.lonBoundsEl = document.getElementById("lon-bounds"); + this.latEl = document.getElementById("lat"); + this.lonEl = document.getElementById("lon"); + this.latFormEl = document.getElementById("lat-form"); + this.lonFormEl = document.getElementById("lon-form"); + + this.Y_MIN_CUTOFF = 1e-16; + + this.config = config; + + $(this.lonEl).on('input', (event) => { + this.checkCoordinates(false,true); + }); + + $(this.latEl).on('input', (event) => { + this.checkCoordinates(true,false); + }); + + $(this.controlEl).on('input change', (event) => { + let canSubmit = this.checkCoordinates(false,false); + this.footerOptions = { + updateBtnDisable: !canSubmit + }; + this.footer.setOptions(this.footerOptions); + }); + + this.dynamicUrl = this.config.server.dynamic + "/nshmp-haz-ws/hazard"; + this.staticUrl = this.config.server.static + "/hazws/staticcurve/1"; + + this.testSitePickerBtnEl = document.querySelector('#test-site-picker'); + + /* @type {LeafletTestSitePicker} */ + this.testSitePicker = new LeafletTestSitePicker( + this.latEl, + this.lonEl, + this.testSitePickerBtnEl); + + /* Bring Leaflet map up when clicked */ + $(this.testSitePickerBtnEl).on('click', (event) => { + this.testSitePicker.plotMap(this.region()); + }); + } + + /** + * Get current region value + */ + region() { + let region = ''; + + switch(this.regionEl.value) { + case 'CEUS': + case 'CEUS0P10': + region = 'CEUS'; + break; + case 'COUS': + case 'COUS0P05': + region = 'COUS'; + break; + case 'WUS': + case 'WUS0P05': + region = 'WUS'; + break; + case 'AK': + case 'AK0P10': + region = 'AK'; + break; + case 'HI0P02': + region = 'HAWAII'; + break; + case 'PRIVI0P01': + case 'GMNI0P10': + case 'AMSAM0P05': + region = null; + break; + default: + region = null; + } + + return region; + } + + getHazardParameters(callback) { + let jsonCall = Tools.getJSONs([this.dynamicUrl, this.staticUrl]); + this.spinner.on(jsonCall.reject, 'Calculating'); + + Promise.all(jsonCall.promises).then((responses) => { + NshmpError.checkResponses(responses); + let dynamicParameters = responses[0].parameters; + let staticParameters = responses[1].parameters; + + var mainPars = ["edition","region"]; + var editionType = ["static","dynamic"]; + + for (var jt in editionType){ + var par = eval(editionType[jt]+"Parameters"); + for (var jp in mainPars){ + for (var jv in par[mainPars[jp]].values){ + par[mainPars[jp]].values[jv].dataType = editionType[jt]; + } + } + } + + //.................. Combine Static and Dynamic Parameters ........... + var editionValues = staticParameters.edition.values + .concat(dynamicParameters.edition.values); + var regionValues = staticParameters.region.values + .concat(dynamicParameters.region.values); + var imtValues = staticParameters.imt.values; + var vs30Values = staticParameters.vs30.values; + + //........ Sort Combined Parameters by Display Order Parameter ....... + editionValues.sort(this.sortDisplayorder); + regionValues.sort(this.sortDisplayorder); + imtValues.sort(this.sortDisplayorder); + vs30Values.sort(this.sortDisplayorder); + + //....... Create a Single Parameter Object for Static and Dynamic .... + var combinedParameters = { + edition: { + label: dynamicParameters.edition.label, + type: dynamicParameters.edition.type, + values: editionValues + }, + region: { + label: dynamicParameters.region.label, + type: dynamicParameters.region.type, + values: regionValues + }, + imt: { + label: dynamicParameters.imt.label, + type: dynamicParameters.imt.type, + values: imtValues + }, + vs30: { + label: dynamicParameters.vs30.label, + type: dynamicParameters.vs30.type, + values: vs30Values + } + }; + + callback(combinedParameters); + }).catch((errorMessage) => { + this.spinner.off(); + NshmpError.throwError(errorMessage); + }); + + } + + /* + - The sort_displayorder function takes a parameter, + like edition, and sorts them based on the display + order given in the two JSON files + - This function returns the subtraction of the display + order values of two editions to see which one should be + displayed first (a negative value return is displayed first) + */ + sortDisplayorder(a,b){ + return (a.displayorder - b.displayorder); + } + + setSelectMenu(el,options){ + + d3.select(el) + .selectAll("option") + .data(options) + .enter() + .append("option") + .attr("value",(d,i) => {return d.value}) + .attr("id",(d,i) => {return d.value}) + .text((d,i) => {return d.display.replace("&","&")}) + } + + /* + - This function is used for model-compare and model-explorer + + Format of Dynamic URL: + https://earthquake.usgs.gov/nshmp-haz-ws/hazard? + edition=value®ion=value&longitude=value + &latitude=value&imt=value&vs30=value + + Format of Static URL: + https://earthquake.usgs.gov/hazws/staticcurve/1 + /{edition}/{region}/{longitude}/{latitude}/{imt}/{vs30}" + */ + composeHazardUrl(edition,region,lat,lon,vs30,dataType){ + if (dataType == "static"){ + var urlInfo = { + dataType: "static", + url: this.staticUrl + + edition + "/" + + region + "/" + + lon + "/" + + lat + "/" + + "any" + "/" + + vs30 + }; + }else if (dataType == "dynamic"){ + var urlInfo = { + dataType: "dynamic", + url: this.dynamicUrl + + "?edition=" + edition + + "®ion=" + region + + "&longitude=" + lon + + "&latitude=" + lat + + "&vs30=" + vs30 + }; + } + return urlInfo; + } + + checkQuery(){ + let url = window.location.hash.substring(1); + + if (!url) return false; + + let pars = url.split("&"); + let key; + let value; + let lat; + let lon; + let vs30; + let imt; + let editions = []; + let dataType = []; + let regions = []; + let urlInfo = []; + pars.forEach((par,i) => { + key = par.split("=")[0]; + value = par.split("=")[1]; + switch (key){ + case("lat"): + lat = value; + break; + case("lon"): + lon = value; + break; + case("vs30"): + vs30 = value; + break; + case("imt"): + imt = value; + break; + case("edition"): + editions.push(value); + break; + case("dataType"): + dataType.push(value); + break; + case("region"): + regions.push(value); + break; + } + }); + + d3.select(this.editionEl) + .selectAll("option") + .property("selected",false); + + editions.forEach((edition,i) => { + d3.select(this.editionEl) + .select("#"+edition) + .property("selected",true); + }); + + $(this.editionEl).trigger('change'); + + this.latEl.value = lat; + this.lonEl.value = lon; + this.imtEl.value = imt; + this.vs30El.value = vs30; + + if (this.options.type == "compare"){ + let comparableRegion = this.comparableRegions.find((d,i) => { + return d.staticValue == regions[0] || d.dynamicValue == regions[0]; + }); + this.regionEl.value = comparableRegion.value; + this.options.regionDefault = comparableRegion.value; + }else{ + this.regionEl.value = regions[0]; + this.options.regionDefault = regions[0]; + this.options.editionDefault = editions[0]; + } + + this.options.imtDefault = imt; + this.options.vs30Default = vs30; + + + return true; + } + + setParameterMenu(par,supportedValues){ + let el = eval("this."+par+"El"); + d3.select(el) + .selectAll("option") + .remove(); + + if ((this.options.type == "explorer" && par == "region") || + (this.options.type == "compare" && par == "edition" || "region")) + this.setSelectMenu(el,supportedValues); + else + this.setSelectMenu(el,this.parameters[par].values); + + + d3.select(el) + .selectAll("option") + .property("disabled",true) + .filter((d,i) => { + return supportedValues.some((sv,isv) => { + return d.value == sv.value; + }) + }) + .property("disabled",false); + + let defaultVal = this.options[par+"Default"]; + let isFound = supportedValues.some((val,i) => { + return val.value == defaultVal; + }); + defaultVal = isFound ? defaultVal + : supportedValues[0].value; + el.value = defaultVal; + } + + supportedValues(par){ + + let type = this.options.type; + let supports = []; + let selectedEditions = this.editionEl.querySelectorAll(":checked"); + selectedEditions.forEach((e,i) => { + let edition = this.parameters.edition.values.find((ev,iev) => { + return ev.value == e.value; + }); + supports.push(edition.supports[par]); + let dataType = edition.dataType; + if (type == "compare"){ + let comparableRegion = this.comparableRegions.find((r,ir) => { + return r.value == this.regionEl.value; + }); + let region = this.parameters.region.values.find((r,ir) => { + return r.value == comparableRegion[dataType+"Value"]; + }); + supports.push(region.supports[par]); + }else if (type == "explorer"){ + let region = this.parameters.region.values.find((r,ir) => { + return r.value == this.regionEl.value; + }); + supports.push(region.supports[par]); + } + }); + + let supportedValues = this.parameters[par].values.filter((p,ip) => { + return supports.every((pc,ipc) => { + return pc.includes(p.value); + }); + }); + + return supportedValues; + } + + setBounds(){ + + let latMax, + latMin, + lonMax, + lonMin, + region; + + region = this.parameters.region.values.find((d,i) => { + return d.value == this.regionEl.value; + }); + + latMax = region.maxlatitude; + latMin = region.minlatitude; + lonMax = region.maxlongitude; + lonMin = region.minlongitude; + + this.latBoundsEl.innerHTML = "<br>" + this.regionEl.value + + " bounds: " + " ["+latMin+","+latMax+"]"; + + this.lonBoundsEl.innerHTML = "<br>" + this.regionEl.value + + " bounds: " + " ["+lonMin+","+lonMax+"]"; + + } + + checkCoordinates(checkLat,checkLon){ + let latMax, + latMin, + lonMax, + lonMin, + region; + + region = this.parameters.region.values.find((d,i) => { + return d.value == this.regionEl.value; + }); + + latMax = region.maxlatitude; + latMin = region.minlatitude; + lonMax = region.maxlongitude; + lonMin = region.minlongitude; + + let lat = this.latEl.value; + let lon = this.lonEl.value; + + let canLatSubmit = lat < latMin || lat > latMax + || isNaN(lat) ? false : true; + let canLonSubmit = lon < lonMin || lon > lonMax + || isNaN(lon) ? false : true; + + if(checkLat){ + d3.select(this.latFormEl) + .classed("has-error",!canLatSubmit); + d3.select(this.latFormEl) + .classed("has-success",canLatSubmit); + } + if(checkLon){ + d3.select(this.lonFormEl) + .classed("has-error",!canLonSubmit); + d3.select(this.lonFormEl) + .classed("has-success",canLonSubmit); + } + + return canLatSubmit && canLonSubmit ? true : false; + } + + clearCoordinates(){ + this.latEl.value = ""; + this.lonEl.value = ""; + + d3.select(this.latFormEl) + .classed("has-error",false); + d3.select(this.latFormEl) + .classed("has-success",false); + + d3.select(this.lonFormEl) + .classed("has-error",false); + d3.select(this.lonFormEl) + .classed("has-success",false); + } + + getSelections(){ + + $(this.footer.rawBtnEl).off(); + + let selectedEditions = this.editionEl.querySelectorAll(":checked"); + let vs30 = this.vs30El.value; + let lat = this.latEl.value; + let lon = this.lonEl.value; + let imt = this.imtEl.value; + + let type = this.options.type; + if (type == "compare"){ + var regionInfo = this.comparableRegions.find((d,i) => { + return d.value == this.regionEl.value; + }); + } + var urlInfo = []; + let windowUrl = "lat="+lat+"&lon="+lon+"&vs30="+vs30+"&imt="+imt; + selectedEditions.forEach((se,ise) => { + var editionInfo = this.parameters.edition.values.find((d,i) => { + return d.value == se.value; + }); + var dataType = editionInfo.dataType; + var editionVal = editionInfo.value; + var regionVal = type == "compare" ? regionInfo[dataType+"Value"] + : this.regionEl.value; + windowUrl += "&dataType="+dataType+"&edition=" + +editionVal+"®ion="+regionVal; + let url = this.composeHazardUrl(editionVal,regionVal, + lat,lon,vs30,dataType); + urlInfo.push(url); + }); + + window.location.hash = windowUrl; + + $(this.footer.rawBtnEl).click(() => { + urlInfo.forEach((url,iu) => { + window.open(url.url); + }) + }); + + return urlInfo; + } + + callHazard(callback){ + + var canSubmit = this.checkCoordinates(true,true); + if (!canSubmit) return; + + let urlInfo = this.getSelections(); + + this.footerOptions = { + rawBtnDisable: false, + updateBtnDisable: false + }; + this.footer.setOptions(this.footerOptions); + + let urls = []; + for (var ju in urlInfo){ + urls.push(urlInfo[ju].url); + } + + let jsonCall = Tools.getJSONs(urls); + this.spinner.on(jsonCall.reject, 'Calculating'); + + Promise.all(jsonCall.promises).then((responses) => { + NshmpError.checkResponses(responses); + + let jsonResponse = []; + + responses.forEach((jsonReturn,i) => { + jsonReturn.response.dataType = urlInfo[i].dataType; + jsonResponse.push(jsonReturn.response); + }); + + let responseWithServer = responses.find((d, i) => { + return d.server; + }); + + let server = responseWithServer != undefined ? + responseWithServer.server : undefined; + this.footer.setMetadata(server); + + callback(jsonResponse); + }).catch((errorMessage) => { + this.spinner.off(); + NshmpError.throwError(errorMessage); + }); + + } + +} diff --git a/webapp/apps/js/lib/HazardNew.js b/webapp/apps/js/lib/HazardNew.js new file mode 100644 index 000000000..991026cc6 --- /dev/null +++ b/webapp/apps/js/lib/HazardNew.js @@ -0,0 +1,237 @@ + +import Constraints from './Constraints.js'; +import Footer from './Footer.js'; +import Header from './Header.js'; +import NshmpError from '../error/NshmpError.js'; +import Spinner from './Spinner.js'; +import Tools from './Tools.js'; + +/** + * @fileoverview Parent class for hazard based web apps including: + * - DynamicCompare + * - ModelCompare + * - ModelExplorer + * + * This class contains common HTML elements including: + * - #control: control panel + * - #imt: IMT select menu + * - #inputs: input form + * - #lat: latitude input + * - #lon: longitude input + * - #return-period: return period input + * - #return-period-btns: return period buttons + * - #vs30: vs30 select menu + * + * @author Brandon Clayton + */ +export class Hazard { + + /** + * @param {String} webApp - Identifier of the application being used. + * Possible values: 'DynamicCompare', 'ModelComparison', 'ModelExplorer'. + * @param {String} webServiceUrl - URK to corresponding web servce. + * Possible values: '/nshmp-haz-ws/hazard', '/nshmp-haz-ws/source/models' + * @param {Config} config - The config file. + */ + constructor(webApp, webServiceUrl, config) { + /** @type {Footer} */ + this.footer = new Footer(); + this.footerOptions = { + rawBtnDisable: true, + updateBtnDisable: true, + }; + this.footer.setOptions(this.footerOptions); + + /** @type {Header} */ + this.header = new Header(); + + /** @type {Spinner} */ + this.spinner = new Spinner(); + + /** @type {String} */ + this.currentWebApp = webApp; + + /** @type {String} */ + this.webServiceUrl = webServiceUrl; + + /** @type {Object} */ + this.config = config; + + /** @type {HTMLElement} */ + this.controlPanelEl = document.querySelector("#control"); + + /** @type {HTMLElement} */ + this.imtEl = document.querySelector("#imt"); + + /** @type {HTMLElement} */ + this.inputsEl = document.querySelector('#inputs'); + + /** @type {HTMLElement} */ + this.latEl = document.querySelector("#lat"); + + /** @type {HTMLElement} */ + this.lonEl = document.querySelector("#lon"); + + /** @type {HTMLElement} */ + this.returnPeriodEl = document.querySelector('#return-period'); + + /** @type {HTMLElement} */ + this.returnPeriodBtnsEl = document.querySelector('#return-period-btns'); + + /** @type {HTMLElement} */ + this.vs30El = document.querySelector("#vs30"); + + this.Y_MIN_CUTOFF = 1e-16; + + /** + * Web applications extending the Hazard class + * @enum {String} + */ + this.WebApps = { + MODEL_EXPLORER: 'ModelExplorer', + MODEL_COMPARISON: 'ModelComparison', + DYNAMIC_COMPARISON: 'DynamicComparison', + }; + + /** @type {String} */ + this.dynamicUrl = this.config.server.dynamic + '/nshmp-haz-ws/haz'; + + /** @type {String} */ + this.staticUrl = this.config.server.static + '/hazws/staticcurve/1'; + + /* Update plot on click */ + $(this.footer.updateBtnEl).click((event) => { + $(this.footer.rawBtnEl).off(); + this.footerOptions.rawBtnDisable = false; + this.footer.setOptions(this.footerOptions); + this.updatePlot(); + }); + + /* Listen for return period to change */ + this.returnPeriodEventListener(); + + /* Return period custom event */ + this.returnPeriodEvent = new Event('returnPeriodChange'); + } + + /** + * Given a region in the web service usage parameters, + * check to see the inputted value in either + * the latitude or longitude input is correct. + * + * @param {HTMLElement} el - The input element. + * @param {Object} region - The region to check lat or lon against. + */ + checkCoordinates(el, region) { + let min = el.id == 'lat' ? region.minlatitude : + region.minlongitude; + + let max = el.id == 'lat' ? region.maxlatitude : + region.maxlongitude; + + Constraints.check(el, min, max); + } + + /** + * Check the return period input value using the Contraints.check method. + * If the value is out of range or bad, the plot cannot be updated. + */ + checkReturnPeriod() { + let period = this.parameters.returnPeriod; + + return Constraints.check( + this.returnPeriodEl, period.values.minimum, period.values.maximum); + } + + /** + * Check to see if the input value matches any of the return period + * buttons. + */ + checkReturnPeriodButtons() { + d3.select(this.returnPeriodBtnsEl) + .selectAll('label') + .classed('active', false) + .selectAll('input') + .select((d, i, els) => { + if (this.returnPeriodEl.value == els[i].value) { + return els[i].parentNode; + } + }) + .classed('active', true); + } + + /** + * Get web service usage and set this.parameters to usage.parameters. + * + * @param {Callback=} callback - An optional callback. + */ + getUsage(callback = () => {}) { + this.callback = callback; + let jsonCall = Tools.getJSON(this.webServiceUrl); + this.spinner.on(jsonCall.reject); + + jsonCall.promise.then((usage) => { + NshmpError.checkResponse(usage); + this.parameters = usage.parameters; + this.buildInputs(); + this.callback(); + }).catch((errorMessage) => { + this.spinner.off(); + NshmpError.throwError(errorMessage); + }); + } + + /** + * On return period button click event handler. + */ + onReturnPeriodClick() { + let el = $(event.target).closest('.btn').find('input'); + + let val = el.val(); + this.returnPeriodEl.value = val; + + let returnPeriodInBounds = this.checkReturnPeriod(); + + if (returnPeriodInBounds) { + this.returnPeriodEl.dispatchEvent(this.returnPeriodEvent); + } + } + + /** + * On return period input event handler + */ + onReturnPeriodInput() { + let returnPeriodInBounds = this.checkReturnPeriod(); + if (!returnPeriodInBounds) return; + this.returnPeriodEl.dispatchEvent(this.returnPeriodEvent); + this.checkReturnPeriodButtons(); + } + + /** + * Add event listener on input for the return period input and + * click for the return period buttons. + */ + returnPeriodEventListener() { + this.returnPeriodBtnsEl.addEventListener('click', () => { + this.onReturnPeriodClick(); + }); + + this.returnPeriodEl.addEventListener('input', () => { + this.onReturnPeriodInput(); + }); + } + + /** + * Set the default return period value and button. + */ + setDefaultReturnPeriod() { + d3.select(this.returnPeriodBtnsEl) + .selectAll('input') + .filter('input[value="' + this.options.defaultReturnPeriod + '"]') + .select((d, i, els) => { return els[0].parentNode }) + .classed('active', true); + + this.returnPeriodEl.value = this.options.defaultReturnPeriod; + } + +} diff --git a/webapp/apps/js/lib/Header.js b/webapp/apps/js/lib/Header.js new file mode 100644 index 000000000..7180a2afd --- /dev/null +++ b/webapp/apps/js/lib/Header.js @@ -0,0 +1,175 @@ +'use strict'; + +/** +* @class Header +* +* @fileoverview Creates the header to be used for all nshmp-haz-ws webapps. +* +* @typedef {Object} HeaderOptions - Object for header options +* @property {String} position - CSS positions: fixed || absolute +* +* @typedef {Array<Object>} HeaderMenu - Header menu labels and hrefs +* @property {String} label - The header menu text to be shown +* @property {String} href - The href for the corresponding menu +* +* @author bclayton@usgs.gov (Brandon Clayton) +*/ +export default class Header{ + + /** + * @param {HTMLElement=} containerEl - Optional container element + * to put the header. Default is body. + */ + constructor(containerEl = document.querySelector('body')) { + document.title = "NSHMP-HAZ-WS"; + + /** @type {HeaderOptions} */ + this.options = { + position: 'fixed', + }; + + /** @type {HeaderMenu} */ + this.menuItems = [ + { + label: 'Dashboard', + href: '/nshmp-haz-ws/', + }, { + label: 'Dynamic Comparison', + href: '/nshmp-haz-ws/apps/dynamic-compare.html', + }, { + label: 'Geographic Deaggregation', + href: '/nshmp-haz-ws/apps/geo-deagg.html', + }, { + label: 'Ground Motion Vs. Distance', + href: '/nshmp-haz-ws/apps/gmm-distance.html', + }, { + label: 'Hanging Wall Effects', + href: '/nshmp-haz-ws/apps/hw-fw.html', + }, { + label: 'Model Compare', + href: '/nshmp-haz-ws/apps/model-compare.html', + }, { + label: 'Model Explorer', + href: '/nshmp-haz-ws/apps/model-explorer.html', + }, { + label: 'Response Spectra', + href: '/nshmp-haz-ws/apps/spectra-plot.html', + }, { + label: 'Exceedance Explorer', + href: '/nshmp-haz-ws/apps/exceedance-explorer.html' + }, { + label: 'Services', + href: '/nshmp-haz-ws/apps/services.html', + } + ]; + + // Append header to body + let headerD3 = d3.select(containerEl) + .append('div') + .attr('id', 'header'); + + // Append webapp title + headerD3.append('span') + .attr('class', 'title') + .attr('id', 'header-title') + .text(''); + + // // Create dropdown + // headerD3.append('div') + // .attr('class', 'dropdown-toggle') + // .attr('id', 'header-menu') + // .attr('data-toggle', 'dropdown') + // .append('span') + // .attr('class', 'glyphicon glyphicon-menu-hamburger'); + + // // Append unordered list + // let headerMenuD3 = headerD3.append('ul') + // .attr('class', 'dropdown-menu dropdown-menu-right') + // .attr('aria-labelledby', 'header-menu'); + + // // Create dropdown list of all webapps + // headerMenuD3.selectAll('li') + // .data(this.menuItems) + // .enter() + // .append('li') + // .append('a') + // .text((d,i) => {return d.label}) + // .attr('href', (d,i) => {return d.href}); + + headerD3.lower(); + + /** @type {HTMLElement} */ + this.containerEl = containerEl; + /** @type {HTMLElement} */ + this.headerEl = headerD3.node(); + /** @type {HTMLElement} */ + this.headerListEl = this.headerEl.querySelector('ul'); + /** @type {HTMLElement} */ + this.headerTitleEl = this.headerEl.querySelector('#header-title'); + } + + /** + * @method setCustomMenu + * + * Create a custom dropdown menu for the header + * @param {HeaderMenu} menuItems - New custom header menu + */ + setCustomMenu(menuItems) { + d3.select(this.headerListEl) + .selectAll('li') + .remove(); + + // Create dropdown list of all webapps + d3.select(this.headerListEl) + .selectAll("li") + .data(menuItems) + .enter() + .append('li') + .append('a') + .text((d,i) => {return d.label}) + .attr('href', (d,i) => {return d.href}); + + } + + /** + * @method setOptions + * + * Set the header options. + * @param {HeaderOptions} options - Header options + * @return {Header} - Return class to be chainable + */ + setOptions(options) { + options.position = options.position == 'fixed' || + options.position == 'absolute' ? options.position : 'fixed'; + + $.extend(this.options, options); + this.updateOptions(); + return this; + } + + /** + * @method setTitle + * + * Set the header title next to the header menu. + * @param {String} title - The header title + * @return {Header} - Return class to be chainable + */ + setTitle(title) { + d3.select(this.headerTitleEl) + .text(title); + + document.title = "NSHMP: " + title; + return this; + } + + /** + * @method updateOptions + * + * Update the header options: the position of the header (fixed || absolute} + */ + updateOptions() { + d3.select(this.headerEl) + .style('position', this.options.position); + } + +} diff --git a/webapp/apps/js/lib/LeafletTestSitePicker.js b/webapp/apps/js/lib/LeafletTestSitePicker.js new file mode 100644 index 000000000..16dc7a507 --- /dev/null +++ b/webapp/apps/js/lib/LeafletTestSitePicker.js @@ -0,0 +1,467 @@ +'use strict'; + +import TestSiteView from './TestSiteView.js'; +import Tools from './Tools.js'; +import NshmpError from '../error/NshmpError.js'; + +/** +* @class LeafletTestSitePicker +* @extends TestSiteView +* +* @fileoverview Location widget to choose a test site. +* +* @author Brandon Clayton +*/ +export default class LeafletTestSitePciker extends TestSiteView { + + /** + * @param {HTMLElement} latEl - The HTML element associated with the + * latitude input. + * @param {HTMLElement} lonEl - The HTML element associated with the + * longitude input. + * @param {HTMLElement} activationBtnEl - The HTML element associated with + * a button that onced pressed would show the location widget. + */ + constructor(latEl, lonEl, activationBtnEl) { + super(latEl, lonEl, activationBtnEl); + + /** @type {Object} */ + this.options = { + plotHeight: 1280, + plotWidth: 1024, + map: { + minZoom: 1, + maxZoom: 18, + zoomSnap: 0.5, + }, + site: { + radius: 4, + color: 'black', + opactiy: 1, + fillOpacity: 1, + className: '', + }, + siteSelected: { + radius: 4, + color: 'red', + opactiy: 1, + fillOpacity: 1, + className: 'selected', + }, + fitBounds: { + animate: false, + }, + }; + + /** @type {HTMLElement} */ + this.leafletEl = this.mapBodyEl; + /** @type {LeafletMap} */ + this.leafletMap = this.createBaseMap(); + /** @type {LeafletGeoJsonLayer} */ + this.geoJsonLayer = this.createGeoJson(); + + /* Get test sites */ + this.getUsage(); + + /** @type {EventListner} */ + this.on = document.addEventListener.bind(document); + + /** + * @type {CustomEvent} New custom event to be called + * as this.on('testSiteLoad', () => {}). Gets triggered + * after the test sites have been loaded. + */ + this.onTestSiteLoadEvent = new CustomEvent( + 'testSiteLoad', + {bubbles: true, cancelable: true}); + } + + /** + * @method checkForRegion + * + * Check if a region exists in the test sites and disabled button if + * there is no region. + */ + checkForRegion(regionId) { + let region = this.testSites.find((feature) => { + return feature.properties.regionId == regionId; + }); + + if (region == undefined) { + d3.select(this.activationBtnEl) + .property('disabled', true); + } else { + d3.select(this.activationBtnEl) + .property('disabled', false); + } + } + + /** + * @method createBaseMap + * + * Create the Leaflet base map with greyscale, street, and satellite layers. + * @return {LeafletMap} The leaflet map. + */ + createBaseMap() { + let mapUrl = 'https://api.tiles.mapbox.com/v4/{id}/{z}/{x}/{y}@2x.png' + + '?access_token={accessToken}'; + + let mapToken = 'pk.eyJ1IjoiYmNsYXl0b24iLCJhIjoiY2pmbWxmd3BhMHg1eTJ' + + '4bzR3M2Q3ZTM5YyJ9.lCQ8B6C9xhpZmd8BrukbDw'; + + /* Greyscale layer */ + let greyscale = L.tileLayer(mapUrl, { + accessToken: mapToken, + id: 'mapbox.light', + }); + + /* Street layer */ + let street = L.tileLayer(mapUrl, { + accessToken: mapToken, + id: 'mapbox.streets', + }); + + /* Satellite layer */ + let satellite = L.tileLayer(mapUrl, { + accessToken: mapToken, + id: 'mapbox.satellite', + }); + + /* Topography layer */ + let topography = L.tileLayer(mapUrl, { + accessToken: mapToken, + id: 'mapbox.outdoors', + }); + + /* Pirate layer */ + let pirate = L.tileLayer(mapUrl, { + accessToken: mapToken, + id: 'mapbox.pirates', + }); + + /* Create the map */ + let leafletMap = L.map(this.leafletEl, { + center: [0, 0], + zoom: 0, + layers: [greyscale], + minZoom: this.options.map.minZoom, + maxZoom: this.options.map.maxZoom, + zoomSnap: this.options.map.zoomSnap, + }); + + /* Base maps */ + let baseMaps = { + 'Greyscale': greyscale, + 'Street': street, + 'Satellite': satellite, + 'Topography': topography, + 'Pirate': pirate, + }; + + /* Add layers to Leaflet map */ + L.control.layers(baseMaps, null).addTo(leafletMap); + + return leafletMap; + } + + /** + * @method createGeoJson + * + * Create an empty Leaflet GeoJson layer to add test sites to. + * Test sites are rendered in SVG. + * @return {LeafletGeoJsonLayer} The GeoJson layer. + */ + createGeoJson() { + let geoJsonLayer = L.geoJSON(null, { + /* + * Render test site in SVG. + * See if lat and lon values match a + * test site and use selected site options if so. + */ + pointToLayer: (feature, latlng) => { + let coords = this.getCoordinates(feature); + latlng.lng = coords[0]; + latlng.lat = coords[1]; + let options = this.options.site; + + if (this.lonEl.value == coords[0] && + this.latEl.value == coords[1]) { + d3.select(this.useLocationBtnEl) + .property('disabled', false); + + d3.select(this.siteListEl) + .select('#' + feature.id) + .classed('active', true) + .node() + .scrollIntoView(); + + options = this.options.siteSelected; + } + + return L.circleMarker(latlng, options); + }, + /* Create tooltips for each location. */ + onEachFeature: (feature, layer) => { + let coords = this.getCoordinates(feature); + let tooltip = '<b> ' + feature.properties.title + '</b><br>' + + 'Longitude: ' + coords[0] + '<br>' + + 'Latitude: ' + coords[1]; + + layer.bindTooltip(tooltip); + }, + }); + + geoJsonLayer.addTo(this.leafletMap); + + return geoJsonLayer; + } + + /** + * @method findTestSite + * + * Search all test sites in a particular region + * to see if the current lat and lon values match a test site. + * @return {Object || Undefined} The test site. + */ + findTestSite(regionId) { + let region = this.getRegion(regionId); + + let site = region.find((site) => { + let coords = this.getCoordinates(site); + return this.lonEl.value == coords[0] && + this.latEl.value == coords[1]; + }); + + return site != undefined ? site : undefined; + } + + /** + * @method getSelectedSiteFromClassName + * + * Find a selected test site based on the class name + * attribute. + */ + getSelectedSiteFromClassName() { + let layers = this.geoJsonLayer.getLayers(); + let selectedLayer = layers.find((layer) => { + return layer.options.className == + this.options.siteSelected.className; + }); + + return selectedLayer.feature; + } + + /** + * Find a selected site based on the location id. + * + * @param {String} locationId The location id + */ + getSelectedSiteFromId(locationId) { + let layers = this.geoJsonLayer.getLayers(); + let selectedLayer = layers.find((layer) => { + return layer.feature.id == locationId; + }); + + return selectedLayer; + } + + /** + * @method getTestSiteTitle + * + * Find a test site that matched current lat and lon values + * and return a string as: 'Location (lon, lat)' + * If the lat and lon values do not match a test site then + * return just lat and lon value as string. + * @return {String} The location string. + */ + getTestSiteTitle(regionId) { + let testSite = this.findTestSite(regionId); + let loc = testSite != undefined ? testSite.properties.title : ''; + + return loc + ' (' + this.lonEl.value + ', ' + this.latEl.value + ')'; + } + + /** + * @method getUsage + * + * Get the test sites from /nshmp-haz-ws/util/testsites. + * Update the activationBtnEl to be clickable. + * Trigger the custom event on('testSiteLoad'). + */ + getUsage() { + let jsonCall = Tools.getJSON(this.webServiceUrl); + + jsonCall.promise.then((usage) => { + NshmpError.checkResponse(usage); + this.testSites = usage.features; + document.dispatchEvent(this.onTestSiteLoadEvent); + d3.select(this.activationBtnEl) + .property('disabled', false); + }).catch((errorMessage) => { + NshmpError.throwError(errorMessage); + }); + } + + /** + * @method onSiteListSelect + * + * Listen for a mouseover, mouseout, and mousedown on the + * site list. + * Mousedown: Update the corresponding site in the map with + * this.options.selectedSite + * Mouseover: Show the tooltip on the corresponding site in the map. + * Mouseout: Remove tooltip. + */ + onSiteListSelect() { + /* + * Update options to options.selectedSite when + * site list clicked. + */ + $(this.siteListEl).on('mousedown keyup keydown', (event) => { + let locationId = event.target.value || event.target.id; + let selectedLayer = this.getSelectedSiteFromId(locationId); + this.geoJsonLayer.setStyle(this.options.site); + selectedLayer.setStyle(this.options.siteSelected); + selectedLayer.bringToFront(); + + d3.select(this.useLocationBtnEl) + .property('disabled', false); + }); + + /* Show tooltip on site in map */ + $(this.siteListEl).on('mouseover', (event) => { + let locationId = event.target.value || event.target.id; + let selectedLayer = this.getSelectedSiteFromId(locationId); + + selectedLayer.bringToFront() + .openTooltip(); + }); + + /* Remove tooltip */ + $(this.siteListEl).on('mouseout', (event) => { + let locationId = event.target.value || event.target.id; + let selectedLayer = this.getSelectedSiteFromId(locationId); + selectedLayer.closeTooltip(); + }); + } + + /** + * @method onSiteSelect + * + * Listen for a test site to be clicked and update the options + * using this.options.siteSelected and select the + * site in the site list and scroll it into view. + */ + onSiteSelect() { + this.geoJsonLayer.on('click', (event) => { + this.geoJsonLayer.setStyle(this.options.site); + let layer = event.layer; + layer.setStyle(this.options.siteSelected); + + d3.select(this.siteListEl) + .selectAll('label') + .classed('active', false); + + d3.select(this.siteListEl) + .select('#' + layer.feature.id) + .classed('active', true) + .node() + .scrollIntoView(); + + d3.select(this.useLocationBtnEl) + .property('disabled', false); + }); + } + + /** + * @method onSnapGrid + * + * Place holder method for when the snap to grid is check. + */ + onSnapGrid() { + $(this.snapGridEl).off(); + $(this.snapGridEl).on('click', (event) => { + this.updateTestSites(); + }); + } + + /** + * @method onUseLocation + * + * Listen for the use location button to be clicked. + * Update the lat and lon values, trigger input event on the + * lat and lon element, and close the location widget. + */ + onUseLocation() { + $(this.useLocationBtnEl).on('click', (event) => { + let selectedSite = this.getSelectedSiteFromClassName(); + let coords = this.getCoordinates(selectedSite); + + this.lonEl.value = coords[0]; + this.latEl.value = coords[1]; + + $(this.lonEl).trigger('input'); + $(this.latEl).trigger('input'); + + $(this.el).modal('hide'); + }); + } + + /** + * @method plotMap + * + * Given a region, plot the corresponding test sites. + * When plotData is called the location widget automatically appears. + * @param {String} regionId - The region to plot. + */ + plotMap(regionId) { + /* Show location widget */ + this.show(); + + d3.select(this.useLocationBtnEl) + .property('disabled', true); + + /* Replot */ + this.leafletMap.invalidateSize(); + + /* Plot test sites */ + let regionGeoJson = this.getRegion(regionId); + this.createSiteList(regionGeoJson); + this.geoJsonLayer.clearLayers(); + this.geoJsonLayer.addData(regionGeoJson); + + /* Zoom to region bounds */ + this.leafletMap.fitBounds( + this.geoJsonLayer.getBounds(), + this.options.fitBounds); + + /* Listeners */ + this.onSiteSelect(); + this.onSiteListSelect(); + //this.onSnapGrid(); + this.onUseLocation(); + + /* Replot when container size changes */ + $(window).resize((event) => { + this.leafletMap.invalidateSize(); + + this.leafletMap.fitBounds( + this.geoJsonLayer.getBounds(), + this.options.fitBounds); + }); + } + + /** + * @method updateTestSites + * + * Update the lat and lon of a test site. + */ + updateTestSites() { + this.geoJsonLayer.eachLayer((layer) => { + let coords = this.getCoordinates(layer.feature); + let latlng = L.GeoJSON.coordsToLatLng(coords); + layer.setLatLng(latlng); + }); + } + +} diff --git a/webapp/apps/js/lib/Settings.js b/webapp/apps/js/lib/Settings.js new file mode 100644 index 000000000..47adc96f9 --- /dev/null +++ b/webapp/apps/js/lib/Settings.js @@ -0,0 +1,179 @@ +'use strict'; + +export default class Settings{ + + constructor(btnEl,options){ + let _this = this; + + _this.btnEl; + _this.containerEl; + + _this.btnEl = btnEl; + _this.containerEl = document.querySelector("body"); + + _this.dev01Url = "https://dev01-earthquake.cr.usgs.gov/"; + _this.dev02Url = "https://dev02-earthquake.cr.usgs.gov/"; + _this.prod01Url = "https://prod01-earthquake.cr.usgs.gov/"; + _this.prod02Url = "https://prod02-earthquake.cr.usgs.gov/"; + + _this.options = { + serverId: "dev01", + onSave: "" + }; + + $.extend(_this.options,options); + + Settings.getSettings(_this); + + + d3.select(btnEl) + .on("click",function(){ + Settings.open(_this); + }); + + } + + + static open(_this){ + Settings.getSettings(_this); + + let settingsD3 = d3.select(_this.containerEl) + .append("div") + .attr("class","Settings"); + + _this.el = settingsD3.node(); + + settingsD3.append("div") + .attr("class","settings-overlay") + .on("click",function(){ + Settings.close(_this); + }); + + let panelD3 = settingsD3.append("div") + .attr("class","settings-panel panel panel-default"); + + + panelD3.append("div") + .attr("class","panel-heading") + .append("h3") + .attr("class","panel-title") + .text("Settings"); + + // Panel Body + let panelBodyD3 =panelD3.append("div") + .attr("class","panel-body"); + + let serverInfo = [ + ["dev01", "Development 01"], + ["dev02", "Development 02"], + ["prod01", "Production 01"], + ["prod02", "Production 02"] + ]; + + let formD3 = panelBodyD3.append("form") + .attr("class","settings-form form-horizontal"); + + let serverD3 = formD3.append("div") + .attr("class","form-group"); + serverD3.append("label") + .attr("class","control-group col-sm-2") + .attr("for","server-form") + .text("Server:"); + serverD3.append("div") + .attr("class","col-sm-4") + .append("select") + .attr("class","form-control") + .attr("id","server-form") + .attr("name","server-form"); + + serverD3.select("#server-form") + .selectAll("option") + .data(serverInfo) + .enter() + .append("option") + .attr("value",function(d,i){return d[0]}) + .text(function(d,i){return d[1]}); + + _this.serverEl = serverD3.select("#server-form").node(); + _this.serverEl.value = _this.options.serverId; + + // Panel footer + let footerD3 = panelD3.append("div") + .attr("class","panel-footer"); + + // Append update plot button to footer + footerD3.append("button") + .attr("id","save-btn") + .attr("class","btn btn-primary") + .text("Save") + .on("click",function(){ + Settings.save(_this); + }); + + // Append raw data button to footer + let btnRightD3 = footerD3.append("span") + .append("div") + .attr("class","btn-float-right") + .append("button") + .attr("id","cancel-btn") + .attr("class","btn btn-default") + .text("Cancel") + .on("click",function(){ + Settings.close(_this); + }); + + + + } + + + static close(_this){ + d3.select(_this.el).remove(); + } + + static save(_this){ + d3.select(_this.el).remove(); + + _this.options.serverId = _this.serverEl.value; + Settings.setSettings(_this); + + localStorage.setItem("serverId",_this.serverEl.value); + } + + + + static getSettings(_this){ + + let serverId = localStorage.getItem("serverId"); + serverId = serverId ? serverId : _this.options.serverId; + _this.options.serverId = serverId; + + Settings.setSettings(_this); + } + + + + static setSettings(_this){ + let serverId = _this.options.serverId; + + /* + switch (serverId){ + case "dev01": + _this.serverUrl = _this.dev01Url; + break; + case "dev02": + _this.serverUrl = _this.dev02Url; + break; + case "prod01": + _this.serverUrl = _this.prod01Url; + break; + case "prod02": + _this.serverUrl = _this.prod02Url; + break; + } + */ + _this.serverUrl = _this.dev01Url; + } + + +} diff --git a/webapp/apps/js/lib/Spinner.js b/webapp/apps/js/lib/Spinner.js new file mode 100644 index 000000000..a1e64cd24 --- /dev/null +++ b/webapp/apps/js/lib/Spinner.js @@ -0,0 +1,96 @@ +'use strict'; + +/** +* @class Spinner +* +* @fileoverview Creates a loading spinner +* +* @author bclayton@usgs.gov (Brandon Clayton) +*/ +export default class Spinner{ + + /** + * @param {HTMLElement=} containerEl - Optional container element to put + * spinner. Default is body. + */ + constructor(containerEl = document.querySelector('body')) { + /** @type {HTMLElement */ + this.containerEl = containerEl; + /** @type {HTMLElement */ + this.spinnerEl = undefined; + /** @type {HTMLElement */ + this.cancelRequestEl = undefined; + } + + /** + * @method off + * + * Remove the loading spinner overlay + */ + off() { + $(this.spinnerEl).modal('hide'); + + d3.select(this.spinnerEl) + .remove(); + } + + /** + * Show the spinner + * + * @param {Function} promiseReject The Promise reject function + * @param {String=} text The text to appear under the spinner. + * Default is "Loading". + */ + on(promiseReject, text = 'Loading') { + let spinnerOverlayD3 = d3.select(this.containerEl) + .append('div') + .attr('class', 'modal Spinner') + .attr('tabindex', '-1') + .attr('role', 'dialog'); + + let spinnerContentD3 = spinnerOverlayD3.append('div') + .attr('class', 'modal-dialog modal-sm') + .attr('role', 'document') + .append('div') + .attr('class', 'modal-content'); + + let spinnerBodyD3 = spinnerContentD3.append('div') + .attr('class', 'modal-body'); + + spinnerBodyD3.append('p') + .attr('class', 'loading-spinner'); + + spinnerBodyD3.append('p') + .attr('class', 'spinner-text') + .text(text); + + spinnerContentD3.append('div') + .attr('class', 'modal-footer') + .append('button') + .attr('class', 'btn btn-primary cancel-request') + .attr('type', 'button') + .text('Cancel Request'); + + spinnerOverlayD3.lower(); + this.spinnerEl = spinnerOverlayD3.node(); + this.cancelRequestEl = this.spinnerEl.querySelector('.cancel-request'); + + $(this.spinnerEl).modal({backdrop: 'static'}); + + this._onCancelRequest(promiseReject); + } + + /** + * Listen for a press on the "Cancel Request" button and + * call the reject function if button is pressed. + * + * @param {Function} promiseReject The Promise reject function + */ + _onCancelRequest(promiseReject) { + $(this.cancelRequestEl).on('click', (event) => { + promiseReject('cancel'); + this.off(); + }); + } + +} diff --git a/webapp/apps/js/lib/TestSiteView.js b/webapp/apps/js/lib/TestSiteView.js new file mode 100644 index 000000000..711eef02a --- /dev/null +++ b/webapp/apps/js/lib/TestSiteView.js @@ -0,0 +1,284 @@ +'use strict'; + +/** +* @class TestSiteView +* +* @fileoverview Creates a Bootstrap modal overlay to plot test sites. +* +* @author Brandon Clayton +*/ +export default class TestSiteView { + + /** + * @param {HTMLElement} latEl - The HTML element associated with the + * latitude input. + * @param {HTMLElement} lonEl - The HTML element associated with the + * longitude input. + * @param {HTMLElement} activationBtnEl - The HTML element associated with + * a button that onced pressed would show the location widget. + */ + constructor(latEl, lonEl, activationBtnEl) { + /** @type {String} */ + this.webServiceUrl = '/nshmp-haz-ws/util/testsites'; + + /** @type {Object} */ + this.viewOptions = { + mapWidth: '75%', + siteListWidth: '25%', + }; + + /** @type {HTMLElement} */ + this.latEl = latEl; + /** @type {HTMLElement} */ + this.lonEl = lonEl; + /** @type {HTMLElement} */ + this.activationBtnEl = activationBtnEl; + + /* Set btn to disabled until tes sties are loaded */ + d3.select(this.activationBtnEl) + .property('disabled', true); + + /** @type {HTMLElement} */ + this.el = this.createOverlay(); + /** @type {HTMLElement} */ + this.mapBodyEl = this.el.querySelector('#map-body'); + /** @type {HTMLElement} */ + this.siteListEl = this.el.querySelector('#site-list'); + /** @type {HTMLElement} */ + this.viewOverlayFooterEl = this.el.querySelector('.modal-footer'); + /** @type {HTMLElement} */ + this.snapGridEl = this.el.querySelector('#snap-grid'); + /** @type {HTMLElement} */ + this.useLocationBtnEl = this.el.querySelector('#use-location'); + /** @type {HTMLElement} */ + //this.regionListEl = this.el.querySelector('#test-site-region-menu'); + + this.onDocumentPress(); + } + + /** + * @method createSiteList + * + * Create the site list in the modal body. + * @param {Object} sites - The feature collection of test sites. + */ + createSiteList(sites) { + d3.select(this.siteListEl) + .selectAll('label') + .remove(); + + d3.select(this.siteListEl) + .selectAll('label') + .data(sites) + .enter() + .append('label') + .attr('class', 'btn btn-sm btn-default') + .attr('id', (feature) => { return feature.id; }) + .html((feature) => { + return '<input type="radio" ' + + 'value="' + feature.id + '" />' + + feature.properties.title; + }); + } + + /** + * @method createOverlay + * + * Create the location picker modal. Is hidden until show() method + * is called. + * @return {HTMLElement} The modal element. + */ + createOverlay() { + /* Modal */ + let overlayD3 = d3.select('body') + .append('div') + .attr('class', 'modal test-site-view') + .attr('tabindex', '-1') + .attr('role', 'dialog'); + + /* Modal content */ + let contentD3 = overlayD3.append('div') + .attr('class', 'modal-dialog modal-lg') + .attr('role', 'document') + .append('div') + .attr('class', 'modal-content'); + + let contentEl = contentD3.node(); + this.createOverlayHeader(contentEl); + this.createOverlayBody(contentEl); + this.createOverlayFooter(contentEl); + + overlayD3.lower(); + let el = overlayD3.node(); + + return el; + } + + /** + * @method createOverlayBody + * + * Create modal body + */ + createOverlayBody(modalEl) { + let bodyD3 = d3.select(modalEl) + .append('div') + .attr('class', 'modal-body') + .append('div') + .attr('class', 'row'); + + /* Map body */ + bodyD3.append('div') + .attr('id', 'map-body') + .style('right', this.viewOptions.siteListWidth); + + /* Site list */ + bodyD3.append('div') + .attr('id', 'site-list-body') + .style('left', this.viewOptions.mapWidth) + .append('div') + .attr('class', 'form-group') + .append('div') + .attr('class', 'btn-group-vertical') + .attr('id', 'site-list') + .attr('data-toggle', 'buttons'); + } + + /** + * @method createOverlayFooter + * + * Create modal footer + */ + createOverlayFooter(modalEl) { + /* Modal footer */ + let footerD3 = d3.select(modalEl) + .append('div') + .attr('class', 'modal-footer') + .style('text-align', 'left'); + + + let formD3 = footerD3.append('div') + .attr('class', 'form-inline'); + + /* + formD3.append('div') + .attr('class', 'pull-left') + .append('select') + .attr('class', 'form-control') + .style('width', 'auto') + .attr('id', 'test-site-region-menu'); + */ + /* Snap grid checkbox */ + footerD3.append('label') + .attr('class', 'hidden') + .style('padding-right', '2em') + .html('<input type="checkbox" id="snap-grid"> Snap to 0.1°'); + + /* Use location button */ + formD3.append('div') + .attr('class', 'pull-right') + .append('button') + .attr('class', 'btn btn-primary') + .attr('id', 'use-location') + .attr('type', 'button') + .text('Use location'); + } + + /** + * @method createOverlayHeader + * + * Create modal header + */ + createOverlayHeader(modalEl) { + let headerD3 = d3.select(modalEl) + .append('div') + .attr('class', 'modal-header'); + + headerD3.append('button') + .attr('type', 'button') + .attr('class', 'close') + .attr('data-dismiss', 'modal') + .append('span') + .attr('aria-hidden', true) + .html('×'); + + headerD3.append('h4') + .attr('class', 'modal-title') + .text('Test Sites'); + + } + + /* + * @method createRegionMenu + */ + createRegionMenu() { + d3.select(this.regionListEl) + .selectAll('option') + .data(this.testSites) + .enter() + .append('option') + .attr('value', (feature) => { return feature.properties.regionId; }) + .text((feature) => { return feature.properties.regionTitle; }) + } + + /** + * @method getCoordinates + * + * Get the lat and lon based on if snap grid is checked + * @param {Object} data - The test site GeoJson. + * @return {Array<Number>} [lon, lat]. + */ + getCoordinates(data) { + let isSnapChecked = this.snapGridEl.checked; + let coords = data.geometry.coordinates; + + let lon = isSnapChecked ? Math.round(coords[0] * 10.0) / 10.0 : + coords[0]; + + let lat = isSnapChecked ? Math.round(coords[1] * 10.0) / 10.0 : + coords[1]; + + return [lon, lat]; + } + + /** + * @method getRegion + * + * Find the region feature collection. + * @param {String} regionId - The region to find. + */ + getRegion(regionId) { + return this.testSites.filter((feature) => { + return feature.properties.regionId == regionId; + }); + } + + /** + * @method onDocumentPress + * + * Listen for return key press and if the use location btn + * is not disabled, click it. This stops the event from + * propagating up to the parents. + */ + onDocumentPress() { + let returnKeyCode = 13; + + $(this.el).keypress((event) => { + event.stopPropagation(); + let isDisabled = d3.select(this.useLocationBtnEl) + .property('disabled'); + if (event.which == returnKeyCode && !isDisabled) { + $(this.useLocationBtnEl).click(); + } + }); + } + + /** + * @method show + * + * Show the Bootstrap modal and test site plot. + */ + show() { + $(this.el).modal({backdrop: 'static'}); + } + +} diff --git a/webapp/apps/js/lib/Tools.js b/webapp/apps/js/lib/Tools.js new file mode 100644 index 000000000..587423223 --- /dev/null +++ b/webapp/apps/js/lib/Tools.js @@ -0,0 +1,417 @@ + +import { D3Utils } from '../d3/D3Utils.js'; +import { Preconditions } from '../error/Preconditions.js'; + +/** + * @class Tools + * + * @fileoverview This class contains static methods that can be used + * in any web app. + * + * @author bclayton@usgs.gov (Brandon Clayton) + */ +export default class Tools { + + /** + * Check hazard component. + * + * @param {String} component The component + */ + static checkHazardComponent(component) { + Preconditions.checkArgument( + component == 'Total' || + component == 'Grid' || + component == 'Interface' || + component == 'Fault' || + component == 'Slab' || + component == 'System' || + component == 'Cluster' || + component == 'Area', + `Component [${component}] not supported`); + } + + /** + * Check a web service response to see if + * response has "status" = "error". + * + * If a web service has an error a NshmpError is thrown + * + * @param {Object} responses The web service responses + */ + static checkResponse(response) { + Preconditions.checkArgumentObject(response); + Preconditions.checkStateObjectProperty(response, 'status'); + let status = response.status; + + if (status == 'error') { + hasError = true; + let errorMessage = `<p> ${response.message} </p> \n`; + throw new NshmpError(errorMessage); + } + + } + + /** + * Check an array of web service responses to see if any web service + * response has "status" = "error". + * + * If a web service has an error a NshmpError is thrown + * + * @param {Array<Object>} responses The web service responses + */ + static checkResponses(responses) { + Preconditions.checkArgumentArrayOf(responses, 'object'); + + let errorMessage = ''; + let hasError = false; + + for (let response of responses) { + Preconditions.checkStateObjectProperty(response, 'status'); + let status = response.status; + + if (status == 'error') { + hasError = true; + errorMessage += `<p> ${response.message} </p> \n`; + } + } + + if (hasError) { + throw new NshmpError(errorMessage); + } + + } + + /** + * Decomposes a data series structrued for D3 to seperate X and + * Y arrays. + */ + static d3XYDataToArrays(dataSeries) { + let seriesArrays = []; + + for (let data of dataSeries) { + let x = []; + let y = []; + for (let dataPoint of data) { + x.push(dataPoint[0]); + y.push(dataPoint[1]); + } + seriesArrays.push({xValues: x, yValues: y}); + } + + return seriesArrays; + } + + /** + * Convienice method for a HTTP request that returns an object + * with a JavaScript Promise and the Promises's reject function. + * + * This is simply a Promise wrapper for a jQuery + * getJSON method that is resolved when getJSON is done + * and rejected on getJSON fail. + * + * @typedef {Object} GetJSONObject - The Promise and Promise reject function + * @property {Promise} promise - The Promise + * @property {Function} reject - The reject function from the Promise + * + * @param {String} url The HTTP request URL + * @return {GetJSONObject} The Promise and Promise reject. + */ + static getJSON(url) { + let jsonReject; + + let promise = new Promise((resolve, reject) => { + jsonReject = reject; + + let jsonCall = $.getJSON(url); + jsonCall.done(resolve); + + let rejectMessage = 'Could not reach: ' + url; + jsonCall.fail((err) => { + reject(rejectMessage); + }); + }); + + return {promise: promise, reject: jsonReject}; + } + + /** + * Convienice method for multiple HTTP request that returns an object + * with the JavaScript Promises and the Promises's reject functions. + * + * This is simply a Promise wrapper for a jQuery + * getJSON method that is resolved when getJSON is done + * and rejected on getJSON fail. + * + * @typedef {Object} GetJSONsObject - The Promises and + * Promise reject functions + * @property {Array<Promise>} promise - The Promises + * @property {Array<Function>} rejects - The reject functions + * from the Promises + * @property {Function} reject - A single reject function + * + * @param {Array<String>} urls The HTTP request URLs + * @return {GetJSONsObject} The Promises and Promise's rejects. + */ + static getJSONs(urls) { + let promises = []; + let jsonRejects = []; + + for (let url of urls) { + let jsonCall = Tools.getJSON(url); + promises.push(jsonCall.promise); + jsonRejects.push(jsonCall.reject); + } + + return {promises: promises, rejects: jsonRejects, reject: jsonRejects[0]}; + } + + /** + * Given an IMT, return the corresponding values. + * + * @param {String} imt - IMT string. + * @return {Number} the corresponding IMT period value. + */ + static imtToValue(imt) { + const IMT_VALUES = { + 'PGA': 0.001, + 'SA0P1': 0.1, + 'SA0P2': 0.2, + 'SA0P3': 0.3, + 'SA0P5': 0.5, + 'SA0P75': 0.75, + 'SA1P0': 1.0, + 'SA2P0': 2.0, + 'SA3P0': 3.0, + 'SA4P0': 4.0, + 'SA5P0': 5.0, + }; + + return IMT_VALUES[imt]; + } + + /** + * Conveince method for calculating percent difference. + */ + static percentDifference(x0, x1) { + if (x0 == null || x1 == null) return null; + + Preconditions.checkArgumentNumber(x0); + Preconditions.checkArgumentNumber(x1); + + let x = ((x0 - x1) / ((x0 + x1) / 2)) * 100.0; + + return Number(x.toFixed(4)); + } + + /** + * Calculate percent difference on an array. + * + * @param {Array<Number>} x0Values X values + * @param {Array<Number>} x1Values X values + * @para {Array<Number>} The percent difference array + */ + static percentDifferenceArray(x0Values, x1Values) { + D3Utils.checkArrayIsNumberOrNull(x0Values); + D3Utils.checkArrayIsNumberOrNull(x1Values); + + Preconditions.checkArgument( + x0Values.length == x1Values.length, + 'Array lengths must be the same'); + + let xValues = []; + + for (let [ x0, x1 ] of d3.zip(x0Values, x1Values)) { + xValues.push(Tools.percentDifference(x0, x1)); + } + + return xValues; + } + + /** + * Reset a radio button to a unchecked state. + * + * @param {HTMLElement} inputEl The input form element with type radio. + */ + static resetRadioButton(inputEl) { + inputEl.checked = false; + } + + /** + * Interpolate between two values at a return period and + * return that value at the return period. + */ + static returnPeriodInterpolation(x0, x1, y0, y1, returnPeriod) { + return x0 + + ((Math.log10(returnPeriod / y0) * (x1 - x0)) / Math.log10(y1 / y0)); + } + + /** + * Add options to a select menu with and id, value, and text. + * + * @param {HTMLElement} el - Select menu dom element to add options. + * @param {Object} paramValues - Parameters to add as options, containing + * a value and display key. + */ + static setSelectMenu(el, paramValues) { + d3.select(el) + .selectAll('option') + .remove(); + + d3.select(el) + .selectAll('option') + .data(paramValues.sort(Tools.sortByDisplayOrder)) + .enter() + .append('option') + .attr('id', (d, i) => { return d.value; }) + .attr('value', (d, i) => { return d.value; }) + .text((d, i) => { return d.display; }); + } + + /** + * Sort parameters by display order. + */ + static sortByDisplayOrder(parA, parB) { + return (parA.displayorder - parB.displayorder); + } + + /** + * Given an array of strings of values find the corresponding usage + * paramaters with that value. + * For example, editions: ['E2008', 'E2014'], would return + * an array of objects corresponding to E2008 and E2014. + * + * @param {Object} usageParams - Usage parmater from web service. + * For example: response.parameters.imt || response.parameters.edition + * @param {Array<String>} values - String values to match in usage. + * @return {Array<Object>} Array of usage objects. + */ + static stringArrayToParameters(usageParams, values) { + let parameters = usageParams.values.filter((par, i) => { + return values.find((val, iv) => { + return par.value == val; + }) + }); + + return parameters; + } + + /** + * Given a string of a value, find the corresponding usage + * paramaters with that value. + * For example, editions: 'E2008', would return + * an object corresponding to E2008. + * + * @param {Object} usageParams - Usage parmater from web service. + * For example: response.parameters.imt || response.parameters.edition + * @param {String} values - String value to match in usage. + * @return {Object} Usage objects. + */ + static stringToParameter(usageParams, value) { + return usageParams.values.find((par, i) => { + return par.value == value; + }); + } + + /** + * Given an array of an array of string values, find the common + * values that appear in each array. Then find all + * parameter object in the usage that match the common + * values. + * + * @param {Object} usageParams - Usage parmater from web service. + * For example: response.parameters.imt || response.parameters.edition + * @param {Array<Array<String>>} + * @return {Array<Object>} Array of usage objects. + */ + static supportedParameters(usageParams, supports) { + let uniqueValues = []; + supports.toString().split(',').forEach((val) => { + if ($.inArray(val, uniqueValues) == -1) { + uniqueValues.push(val); + } + }); + + let commonValues = uniqueValues.filter((val, iuv) => { + return supports.every((support, is) => { + return support.includes(val); + }) + }); + + return Tools.stringArrayToParameters(usageParams, commonValues); + } + + /** + * Take a URL string and convert into object of key/value pairs. + * If there are multiple of the same key then the values will be put in + * an array. + * + * @param {String} url - String to convert to object. + * @return {Object} - Object of key/value pairs from URL string. + */ + static urlQueryStringToObject(url) { + let urlObject = {}; + let pairs = url.split('&'); + pairs.forEach((pair, i) => { + let key = pair.split('=')[0]; + let value = pair.split('=')[1]; + + if (urlObject[key] != undefined && !Array.isArray(urlObject[key])) { + urlObject[key] = [urlObject[key]]; + } + + if (urlObject[key] != undefined && Array.isArray(urlObject[key])) { + urlObject[key].push(value); + } else + urlObject[key] = value; + }); + + return urlObject; + } + + /** + * Given an IMT period value in seconds, return the + * corresponding IMT string. + * + * @param {Number} value - The IMT value. + */ + static valueToImt(value) { + const IMT_VALUES = { + 'PGA': 'PGA', + '0.1': 'SA0P1', + '0.2': 'SA0P2', + '0.3': 'SA0P3', + '0.5': 'SA0P5', + '0.75': 'SA0P75', + '1': 'SA1P0', + '2': 'SA2P0', + '3': 'SA3P0', + '4': 'SA4P0', + '5': 'SA5P0', + }; + + return IMT_VALUES[value]; + } + + /** + * Colors associated with hazard components. + * + * @param {String} component The hazard component + * @param {String} The color for the component + */ + static hazardComponentToColor(component) { + let colors = d3.schemeCategory10; + + const COMPONENT_COLORS = { + 'Grid': colors[0], + 'Slab': colors[1], + 'Interface': colors[2], + 'Fault': colors[3], + 'System': colors[4], + 'Cluster': colors[5], + 'Area': colors[6], + }; + + return COMPONENT_COLORS[component]; + } + +} diff --git a/webapp/apps/js/response/HazardServiceResponse.js b/webapp/apps/js/response/HazardServiceResponse.js new file mode 100644 index 000000000..14a3f60a8 --- /dev/null +++ b/webapp/apps/js/response/HazardServiceResponse.js @@ -0,0 +1,395 @@ + +import { WebServiceResponse, ServiceParameter } from './WebServiceResponse.js'; +import { Preconditions } from '../error/Preconditions.js'; +import Tools from '../lib/Tools.js'; + +/** + * @fileoverview Container class for hazard web service response. + * + * @class HazardServiceResponse + * @author Brandon Clayton + */ +export class HazardServiceResponse extends WebServiceResponse { + + /** + * Create new HazardServiceResponse. + * + * @param {Object} hazardResponse The hazard JSON response + */ + constructor(hazardResponse) { + Preconditions.checkArgumentObject(hazardResponse); + super(hazardResponse); + + Preconditions.checkStateObjectProperty(hazardResponse, 'response'); + Preconditions.checkStateArrayOf(hazardResponse.response, 'object'); + + /** @type Array<HazardResponse> The hazard responses */ + this.response = hazardResponse.response.map((response) => { + return new HazardResponse(response); + }); + } + + /** + * Get a specific IMT response. + * + * @param {String} imt The IMT + * @returns {HazardResponse} The hazard response for the IMT + */ + getResponse(imt) { + Preconditions.checkArgumentString(imt); + + return this.response.find((response) => { + return response.metadata.imt.value == imt; + }); + } + + /** + * Calculate the response spectrum for a specific component + * and return period. + * + * @param {String} component The hazard component + * @param {Number} returnPeriod The return period + * @returns {Array<Array<Number>>} The response spectrum: + * [ X values, Y values ] + */ + calculateResponseSpectrum(component, returnPeriod) { + let xValues = []; + let yValues = []; + + for (let response of this.response) { + xValues.push(Tools.imtToValue(response.metadata.imt.value)); + yValues.push(response.calculateResponseSpectrum(component, returnPeriod)); + } + + return [ xValues, yValues ]; + } + + /** + * Convert to response spectrum and return new HazardResponseSpectrum + * + * @param {Number} returnPeriod The return period + * @returns {HazardResponseSpectrum} The response spectrum + */ + toResponseSpectrum(returnPeriod) { + Preconditions.checkArgumentNumber(returnPeriod); + + return new HazardResponseSpectrum(this, returnPeriod); + } + +} + +/** + * @fileoverview Container class for a hazard response for a IMT. + * + * @class HazardResponse + * @author Brandon Clayton + */ +export class HazardResponse { + + /** + * Create new HazardResponse + * + * @param {Object} response The JSON response + */ + constructor(response) { + Preconditions.checkArgumentObject(response); + Preconditions.checkStateObjectProperty(response, 'metadata'); + Preconditions.checkStateObjectProperty(response, 'data'); + Preconditions.checkStateArrayOf(response.data, 'object'); + + /** @type {HazardResponseMetadata} The response metadata */ + this.metadata = new HazardResponseMetadata(response); + + /** @type {Array<HazardResponseData>} The response data */ + this.data = response.data.map((data) => { + return new HazardResponseData(data, this.metadata.xValues); + }); + + } + + /** + * Calculate the response spectrum at a specific + * hazard component and return period. + * + * @param {String} component The hazard component + * @param {Number} returnPeriod The return period + * @returns {Number} The response spectrum value + */ + calculateResponseSpectrum(component, returnPeriod) { + Tools.checkHazardComponent(component); + Preconditions.checkArgumentNumber(returnPeriod); + + let responseData = this.getDataComponent(component); + let xValues = this.metadata.xValues; + let yValues = responseData.yValues; + + let afeIndexBelowReturnPeriod = yValues.findIndex((y) => { + return y < returnPeriod; + }); + + let x0 = xValues[afeIndexBelowReturnPeriod - 1]; + let x1 = xValues[afeIndexBelowReturnPeriod]; + let y0 = yValues[afeIndexBelowReturnPeriod - 1]; + let y1 = yValues[afeIndexBelowReturnPeriod]; + + let x = Tools.returnPeriodInterpolation(x0, x1, y0, y1, returnPeriod); + x = isNaN(x) ? null : Number(x.toFixed(6)); + + return x; + } + + /** + * Get a specific hazard component. + * + * @param {String} component The component + * @returns {HazardResponseData} The data + */ + getDataComponent(component) { + Tools.checkHazardComponent(component); + + return this.data.find((data) => { + return data.component == component; + }); + } + + /** + * Get all data components except for Total + * + * @returns {Array<HazardResponseData} The data + */ + getDataComponents() { + return this.data.filter((data) => { + return data.component != 'Total'; + }); + } + +} + +/** + * @fileoverview Container class for the HazardResponse metadata. + * + * @class HazardResponseMetadata + * @author Brandon Clayton + */ +export class HazardResponseMetadata { + + /** + * Create new HazardResponseMetadata. + * + * @param {Object} response The JSON response + */ + constructor(response) { + Preconditions.checkArgumentObject(response); + Preconditions.checkStateObjectProperty(response, 'metadata'); + let metadata = response.metadata; + + Preconditions.checkStateObjectProperty(metadata, 'model'); + Preconditions.checkStateObjectProperty(metadata, 'latitude'); + Preconditions.checkStateObjectProperty(metadata, 'longitude'); + Preconditions.checkStateObjectProperty(metadata, 'imt'); + Preconditions.checkStateObjectProperty(metadata, 'vs30'); + Preconditions.checkStateObjectProperty(metadata, 'xlabel'); + Preconditions.checkStateObjectProperty(metadata, 'ylabel'); + Preconditions.checkStateObjectProperty(metadata, 'xvalues'); + Preconditions.checkStateArrayOf(metadata.xvalues, 'number'); + + /** @type {HazardSourceModel} The source model */ + this.model = new HazardSourceModel(metadata.model); + + /** @type {Number} The latitude */ + this.latitude = metadata.latitude; + + /** @type {Number} The longitude */ + this.longitude = metadata.longitude; + + /** @type {ServiceParameter} The IMT parameter */ + this.imt = new ServiceParameter(metadata.imt); + + /** @type {ServiceParameter} The vs30 parameter */ + this.vs30 = new ServiceParameter(metadata.vs30); + + /** @type {String} The X label */ + this.xLabel = metadata.xlabel; + + /** @type {Array<Number>} The X values */ + this.xValues = metadata.xvalues; + + /** @type {String} The Y label */ + this.yLabel = metadata.ylabel; + } + +} + +/** + * @fileoverview Container class for the hazard model. + * + * @class HazardSourceModel + * @author Brandon Clayton + */ +export class HazardSourceModel extends ServiceParameter { + + constructor(model) { + Preconditions.checkArgumentObject(model); + super(model); + + Preconditions.checkStateObjectProperty(model, 'region'); + Preconditions.checkStateObjectProperty(model, 'path'); + Preconditions.checkStateObjectProperty(model, 'supports'); + Preconditions.checkStateObjectProperty(model, 'year'); + + /** @type {String} The model region */ + this.region = model.region; + + /** @type {String} The path to the model */ + this.path = model.path; + + /** @type {HazardSourceModelSupports} The supported IMTs and vs30s */ + this.supports = new HazardSourceModelSupports(model.supports); + + /** @type {Number} The model year */ + this.year = model.year; + } + +} + +/** + * @fileoverview Container class for the hazard source model supports object. + * + * @class HazardSourceModelSupports + * @author Brandon Clayton + */ +export class HazardSourceModelSupports { + + constructor(supports) { + Preconditions.checkArgumentObject(supports); + Preconditions.checkStateObjectProperty(supports, 'imt'); + Preconditions.checkStateObjectProperty(supports, 'vs30'); + + /** @type {Array<String>} The supported IMTs */ + this.imt = supports.imt; + + /** @type {Array<String>} The supported vs30s */ + this.vs30 = supports.vs30; + } + +} + +/** + * @fileoverview Container class for the hazard data. + * + * @class HazardResponseData + * @author Brandon Clayton + */ +export class HazardResponseData { + + /** + * Create new HazardResponseData. + * + * @param {Object} data The data JSON + * @param {Array<Number>} xValues The X values from metadata + */ + constructor(data, xValues) { + Preconditions.checkArgumentObject(data); + Preconditions.checkArgumentArrayOf(xValues, 'number'); + + Preconditions.checkStateObjectProperty(data, 'component'); + Preconditions.checkStateObjectProperty(data, 'yvalues'); + Preconditions.checkStateArrayOf(data.yvalues, 'number'); + + /** @type {String} The hazard curve component */ + this.component = data.component; + + /** @type {Array<Number>} The X values */ + this.xValues = xValues; + + /** @type {Array<Number>} The Y values */ + this.yValues = data.yvalues; + } + +} + +/** + * @fileoverview Container class from the hazard response + * spectrum calculations. + * + * @class HazardResponseSpectrum + * @author Brandon Clayton + */ +export class HazardResponseSpectrum { + + /** + * Create new HazardResponseSpectrum. + * + * @param {HazardServiceResponse} serviceResponse The hazard service response + * @param {Number} returnPeriod The return period + */ + constructor(serviceResponse, returnPeriod) { + Preconditions.checkArgumentInstanceOf(serviceResponse, HazardServiceResponse); + Preconditions.checkArgumentNumber(returnPeriod); + + this.data = serviceResponse.response[0].data.map((data) => { + return new ResponseSpectrumData( + serviceResponse, + data.component, + returnPeriod); + }); + } + + /** + * Get all hazard data components except for Total. + */ + getDataComponents() { + return this.data.filter((data) => { + return data.component != 'Total'; + }); + } + + /** + * Get a specific hazard data component. + * + * @param {String} component The component + */ + getDataComponent(component) { + Tools.checkHazardComponent(component); + + return this.data.find((data) => { + return data.component == component; + }); + } + +} + +/** + * @fileoverview Container class for response spectrum data. + * + * @class ResponseSpectrumData + * @author Brandon Clayton + */ +export class ResponseSpectrumData { + + /** + * Create new ResponseSpectrumData. + * + * @param {HazardServiceResponse} serviceResponse The service response + * @param {String} component The hazard component + * @param {Number} returnPeriod The return period + */ + constructor(serviceResponse, component, returnPeriod) { + Preconditions.checkArgumentInstanceOf( + serviceResponse, + HazardServiceResponse); + + Preconditions.checkArgumentNumber(returnPeriod); + + let spectra = serviceResponse.calculateResponseSpectrum( + component, + returnPeriod); + + this.component = component; + + this.xValues = spectra[0]; + + this.yValues = spectra[1]; + } + +} diff --git a/webapp/apps/js/response/WebServiceResponse.js b/webapp/apps/js/response/WebServiceResponse.js new file mode 100644 index 000000000..6c6a49ab0 --- /dev/null +++ b/webapp/apps/js/response/WebServiceResponse.js @@ -0,0 +1,151 @@ + +import { Preconditions } from '../error/Preconditions.js'; +import Tools from '../lib/Tools.js'; + +/** + * @fileoverview Container class for general nshmp-haz-ws + * web service responses. + * + * @class WebServiceResponse + * @author Brandon Clayton + */ +export class WebServiceResponse { + + /** + * Create new WebServiceResponse given the JSON return from a + * nshmp-haz-ws call. + * + * @param {Object} response The JSON response from web service call + */ + constructor(response) { + Tools.checkResponse(response); + + Preconditions.checkArgumentObject(response); + Preconditions.checkStateObjectProperty(response, 'status'); + Preconditions.checkStateObjectProperty(response, 'date'); + Preconditions.checkStateObjectProperty(response, 'url'); + Preconditions.checkStateObjectProperty(response, 'server'); + + /** @type {String} The date of request */ + this.date = response.date; + + /** @type {Server} The server metadata */ + this.server = new Server(response); + + /** @type {String} The response status */ + this.status = response.status; + + /** @type {String} The URL query */ + this.url = response.url; + + /** @type {Object} The raw response */ + this._rawResponse = response; + } + +} + +/** + * @fileoverview Container class for the server object in the response. + * + * @class Server + * @author Brandon Clayton + */ +export class Server { + + /** + * Create new Server object + * + * @param {Object} The JSON response + */ + constructor(response) { + Preconditions.checkArgumentObject(response); + + Preconditions.checkStateObjectProperty(response, 'server'); + let server = response.server; + + Preconditions.checkStateObjectProperty(server, 'threads'); + Preconditions.checkStateObjectProperty(server, 'servlet'); + Preconditions.checkStateObjectProperty(server, 'calc'); + Preconditions.checkStateObjectProperty(server, 'nshmp-haz'); + Preconditions.checkStateObjectProperty(server, 'nshmp-haz-ws'); + + /** @type {Number} The number of threads used*/ + this.threads = server.threads; + + /** @type {String} The servlet call time */ + this.servlet = server.servlet; + + /** @type {String} Calculation time */ + this.calc = server.calc; + + /** @type {ServiceCodeMetadata} Metadata about nshmp-haz */ + this.nshmpHaz = new ServiceCodeMetadata(server['nshmp-haz']) + + /** @type {ServiceCodeMetadata} Metadata about nshmp-haz-ws */ + this.nshmpHazWs = new ServiceCodeMetadata(server['nshmp-haz-ws']) + } + +} + +/** + * @fileoverview Container class for the service code metadata under + * the server object. + * + * @class ServiceCodeMetadata + * @author Brandon Clayton + */ +export class ServiceCodeMetadata { + + /** + * Create new service code metadata object. + * + * @param {Object} service The service object: 'nshmp-haz' || 'nshmp-haz-ws' + */ + constructor(service) { + Preconditions.checkArgumentObject(service); + Preconditions.checkStateObjectProperty(service, 'url'); + Preconditions.checkStateObjectProperty(service, 'version'); + + /** @type {String} The repository url */ + this.url = service.url; + + /** @type {String} The code version */ + this.version = service.version; + } + +} + +/** + * @fileoverview Container class for a generic service parameter. + * + * @class ServiceParameter + * @author Brandon Clayton + */ +export class ServiceParameter { + + /** + * Create new service parameter. + * + * @param {Object} parameter The parameter from response + */ + constructor(parameter) { + Preconditions.checkArgumentObject(parameter); + Preconditions.checkStateObjectProperty(parameter, 'id'); + Preconditions.checkStateObjectProperty(parameter, 'value'); + Preconditions.checkStateObjectProperty(parameter, 'display'); + Preconditions.checkStateObjectProperty(parameter, 'displayorder'); + + /** @type {Number} The parameter id */ + this.id = parameter.id; + + /** @type {String} The parameters value */ + this.value = parameter.value; + + /** @type {String} The parameter display */ + this.display = parameter.display; + + /** @type {Number} The display order */ + this.displayOrder = parameter.displayorder; + } + +} diff --git a/webapp/apps/model-compare.html b/webapp/apps/model-compare.html new file mode 100644 index 000000000..1d9f7d78c --- /dev/null +++ b/webapp/apps/model-compare.html @@ -0,0 +1,146 @@ + + +<!doctype html> + + +<html lang="en"> + +<!-- ................... Head ................ --> +<head> + + <meta name="viewport" content="width=device-width, initial-scale=1.0"> + <meta charset="UTF-8"> + + <!-- Title is defined in common.js --> + + <!-- ............. Bootstrap ............ --> + <link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css"> + <!-- ............. Bootstrap ............ --> + + + <!-- ........... CSS Files ....... --> + <link rel="stylesheet" href="https://unpkg.com/leaflet@1.3.1/dist/leaflet.css" /> + <link rel="stylesheet" href="css/template.css" type="text/css"> + <link rel="stylesheet" href="css/D3View.css" type="text/css"> + <link rel="stylesheet" href="css/model-compare.css" type="text/css"> + <!-- ........... CSS Files ....... --> + +</head> +<!-- ................... Head ................ --> + + +<!-- ................................ Body ................................. --> +<body> + + + <!-- ............................. Control Panel ......................... --> + <div class='hidden' id="control" > + <form id="inputs"> + <div class="form-horizontal"> + + + <!-- .................. Region Menu ......................... --> + <div class="form-group"> + <label class="control-spacer control-group" for="region"> Region: </label> + <select class="form-control" id="region" name="region" autofocus> + </select> + </div> + <!-- .................. End: Region Menu .................... --> + + <!-- .................. Edition Menu ........................ --> + <div class="form-group"> + <label class="control-spacer control-group" for="edition"> Edition: </label> + <select class="form-control" id="edition" name="edition" multiple size=5> + </select> + </div> + <!-- ............. End: Edition Menu ........................ --> + + <!-- .................. Enter Latitude ...................... --> + <div class="form-group form-group-sm form-inline" id="lat-form"> + <label class="control-spacer control-group" + for="lat"> Latitude: <small id="lat-bounds"></small> + </label> <br> + <div class="input-group"> + <input id="lat" class="form-control" type="text" name="lat" /> + <div class="input-group-addon">°</div> + </div> + </div> + <!-- .................. End: Enter Latitude ................. --> + + <!-- .................. Enter Logitude ...................... --> + <div class="form-group form-group-sm form-inline" id="lon-form"> + <label class="control-spacer control-group" + for="lon"> Longitude: <small id="lon-bounds"></small> + </label> <br> + <div class="input-group"> + <input id="lon" class="form-control" type="text" name="lon" /> + <div class="input-group-addon">°</div> + </div> + </div> + <!-- .................. End: Enter Logitude ................. --> + + <!-- Choose location on map --> + <div class='form-group form-group-sm'> + <button class="btn btn-default" id='test-site-picker' type='button'> + Choose a test site + </button> + </div> + <!-- End: Choose location on map --> + + <!-- .................. IMT Menu ............................ --> + <div class="form-group form-group-sm"> + <label class="control-spacer control-group" for="imt"> Intensity Measure Type: </label> + <select class="form-control" id="imt" name="imt"> + </select> + </div> + <!-- .................. End: Imt Menu ....................... --> + + <!-- .................. Vs30 Menu ........................... --> + <div class="form-group form-group-sm"> + <label class="control-spacer control-group" for="vs30"> V<sub>s</sub>30: </label> + <select class="form-control" id="vs30" name="vs30"> + </select> + </div> + <!-- .................. End: Vs30 Menu ...................... --> + + </div> + </form> + </div> + <!-- ........................ End: Control Panel ......................... --> + + + + <!-- ........................... Plots ................................... --> + <div id="content"> + </div> + <!-- ........................ End: Plots ................................. --> + + + + <!-- ...................... Import JavaScript ............................ --> + + <!-- jQuery --> + <script src="https://ajax.googleapis.com/ajax/libs/jquery/3.2.1/jquery.min.js"></script> + + <!-- Bootstrap --> + <script src="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/js/bootstrap.min.js"></script> + + <!-- Application Specific JavaScript ......... --> + <script src="https://d3js.org/d3.v4.min.js" charset="utf-8"></script> + <script src="https://unpkg.com/leaflet@1.3.1/dist/leaflet.js"></script> + + <script type='module'> + import { CONFIG } from './config.js'; + import { ModelCompare } from './js/ModelCompare.js'; + + new ModelCompare(CONFIG); + </script> + <!-- ................... End: Import JavaScript .......................... --> + + + +</body> +<!-- ........................... End: Body ................................. --> + +</html> + diff --git a/webapp/apps/model-explorer.html b/webapp/apps/model-explorer.html new file mode 100644 index 000000000..698d52dfd --- /dev/null +++ b/webapp/apps/model-explorer.html @@ -0,0 +1,145 @@ + + +<!doctype html> + + +<html lang="en"> + +<!-- ................... Head ................ --> +<head> + + <meta name="viewport" content="width=device-width, initial-scale=1.0"> + <meta charset="UTF-8"> + + <!-- Title is defined in common.js --> + + <!-- ............. Bootstrap ............ --> + <link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css"> + <!-- ............. Bootstrap ............ --> + + + <!-- ........... CSS Files ....... --> + <link rel="stylesheet" href="https://unpkg.com/leaflet@1.3.1/dist/leaflet.css" /> + <link rel="stylesheet" href="css/template.css" type="text/css"> + <link rel="stylesheet" href="css/D3View.css" type="text/css"> + <link rel="stylesheet" href="css/model-explorer.css" type="text/css"> + <!-- ........... CSS Files ....... --> + +</head> +<!-- ................... Head ................ --> + + +<!-- ................................ Body ................................. --> +<body> + + + <!-- ............................. Control Panel ......................... --> + <div class='hidden' id="control" > + <form id="inputs"> + <div class="form-horizontal"> + + <!-- .................. Edition Menu ........................ --> + <div class="form-group"> + <label class="control-spacer control-group" for="edition"> Edition: </label> + <select class="form-control" id="edition" name="edition"> + </select> + </div> + <!-- ............. End: Edition Menu ........................ --> + + <!-- .................. Region Menu ......................... --> + <div class="form-group"> + <label class="control-spacer control-group" for="region"> Region: </label> + <select class="form-control" id="region" name="region" autofocus> + </select> + </div> + <!-- .................. End: Region Menu .................... --> + + <!-- .................. Enter Latitude ...................... --> + <div class="form-group form-group-sm form-inline" id="lat-form"> + <label class="control-spacer control-group" + for="lat"> Latitude: <small id="lat-bounds"></small> + </label> <br> + <div class="input-group"> + <input id="lat" class="form-control" type="text" name="lat" /> + <div class="input-group-addon">°</div> + </div> + </div> + <!-- .................. End: Enter Latitude ................. --> + + <!-- .................. Enter Logitude ...................... --> + <div class="form-group form-group-sm form-inline" id="lon-form"> + <label class="control-spacer control-group" + for="lon"> Longitude: <small id="lon-bounds"></small> + </label> <br> + <div class="input-group"> + <input id="lon" class="form-control" type="text" name="lon" /> + <div class="input-group-addon">°</div> + </div> + </div> + <!-- .................. End: Enter Logitude ................. --> + + <!-- Choose location on map --> + <div class='form-group form-group-sm'> + <button class="btn btn-default" id='test-site-picker' type='button'> + Choose a test site + </button> + </div> + <!-- End: Choose location on map --> + + <!-- .................. IMT Menu ............................ --> + <div class="form-group form-group-sm"> + <label class="control-spacer control-group" for="imt"> Intensity Measure Type: </label> + <select class="form-control" id="imt" name="imt"> + </select> + </div> + <!-- .................. End: Imt Menu ....................... --> + + <!-- .................. Vs30 Menu ........................... --> + <div class="form-group form-group-sm"> + <label class="control-spacer control-group" for="vs30"> V<sub>s</sub>30: </label> + <select class="form-control" id="vs30" name="vs30"> + </select> + </div> + <!-- .................. End: Vs30 Menu ...................... --> + + </div> + </form> + </div> + <!-- ........................ End: Control Panel ......................... --> + + + + <!-- ........................... Plots ................................... --> + <div id="content"> + </div> + <!-- ........................ End: Plots ................................. --> + + + + <!-- ...................... Import JavaScript ............................ --> + + <!-- jQuery --> + <script src="https://ajax.googleapis.com/ajax/libs/jquery/3.2.1/jquery.min.js"></script> + + <!-- Bootstrap --> + <script src="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/js/bootstrap.min.js"></script> + + <!-- Application Specific JavaScript ......... --> + <script src="https://d3js.org/d3.v4.min.js" charset="utf-8"></script> + <script src="https://unpkg.com/leaflet@1.3.1/dist/leaflet.js"></script> + + <script type='module'> + import { CONFIG } from './config.js'; + import { ModelExplorer } from './js/ModelExplorer.js'; + + new ModelExplorer(CONFIG); + </script> + <!-- ................... End: Import JavaScript .......................... --> + + + +</body> +<!-- ........................... End: Body ................................. --> + +</html> + diff --git a/webapp/apps/services.html b/webapp/apps/services.html new file mode 100644 index 000000000..dc2a26adc --- /dev/null +++ b/webapp/apps/services.html @@ -0,0 +1,23 @@ +<!DOCTYPE html> +<html lang='en'> + <head> + <link rel='stylesheet' href='https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css'> + <link href='css/template.css' rel='stylesheet'> + <link href='css/services.css' rel='stylesheet'> + <meta charset='UTF-8'> + </head> + + <body> + <script src='https://d3js.org/d3.v4.min.js' charset='utf-8'></script> + <script src='https://ajax.googleapis.com/ajax/libs/jquery/3.2.1/jquery.min.js'></script> + <script src='https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/js/bootstrap.min.js'></script> + + <script type='module'> + import { CONFIG } from './config.js'; + import Services from './js/Services.js'; + + new Services(CONFIG); + </script> + </body> + +</html> diff --git a/webapp/apps/spectra-plot.html b/webapp/apps/spectra-plot.html new file mode 100644 index 000000000..b5a65caa2 --- /dev/null +++ b/webapp/apps/spectra-plot.html @@ -0,0 +1,44 @@ +<!doctype html> +<html lang="en"> + + <head> + <meta name="viewport" content="width=device-width, initial-scale=1.0"> + <meta charset="UTF-8"> + + <!-- Import Bootstrap CSS --> + <link rel="stylesheet" + href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.6/css/bootstrap.min.css"> + + <!-- CSS Files --> + <link rel="stylesheet" href="css/template.css" type="text/css"> + <link rel="stylesheet" href="css/D3View.css" type="text/css"> + <link rel="stylesheet" href="css/Gmm.css" type="text/css"> + </head> + + <body> + <div id="content"></div> + + <!-- Import jQuery --> + <script + src="https://ajax.googleapis.com/ajax/libs/jquery/3.2.1/jquery.min.js"> + </script> + + <!-- Import Bootstrap JavaScript --> + <script + src="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/js/bootstrap.min.js"> + </script> + + <!-- Import D3 --> + <script src="https://d3js.org/d3.v4.min.js" charset="utf-8"></script> + + <!-- Run main JavaScript --> + <script type='module'> + import { CONFIG } from './config.js'; + import { Spectra } from './js/Spectra.js'; + + new Spectra(CONFIG); + </script> + + </body> + +</html> diff --git a/webapp/apps/util.html b/webapp/apps/util.html new file mode 100644 index 000000000..d941be288 --- /dev/null +++ b/webapp/apps/util.html @@ -0,0 +1,85 @@ +<!doctype html> + + +<html lang="en"> + +<!-- ................... Head ................ --> +<head> + + <meta name="viewport" content="width=device-width, initial-scale=1.0"> + <meta charset="UTF-8"> + + <!-- Title is defined in common.js --> + + <!-- ............. Bootstrap ............ --> + <link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css"> + <!-- ............. Bootstrap ............ --> + + + <!-- ........... CSS Files ....... --> + <link rel="stylesheet" href="css/template.css" type="text/css"> + <link rel="stylesheet" href="css/util.css" type="text/css"> + <!-- ........... CSS Files ....... --> + +</head> +<!-- ................... Head ................ --> + + + +<!-- ................... Body ................ --> +<body> + + + + <!-- ................. Main Content ......................... --> + <div id="content"> + <div class="col-md-8 col-md-offset-2 col-sm-12"> + <div class="panel panel-default"> + <div class="panel-heading"> + <h3 class="panel-title"> Service: Test Sites </h3> + </div> + <div class="panel-body"> + <p> Format: </p> + <ul> + <li class="formatUrl">/util/testsites </li> + </ul> + <p> Example: </p> + <ul> + <li class="serviceLink">/util/testsites </li> + </ul> + </div> + </div> + </div> + </div> + <!-- .............. End: Main Content ....................... --> + + + + <!-- ................. Import JavaScript .................... --> + + <!-- ............ jQuery ................ --> + <script src="https://ajax.googleapis.com/ajax/libs/jquery/3.2.1/jquery.min.js"></script> + <!-- ............ jQuery ................ --> + + + <!-- ............. Bootstrap ............ --> + <script src="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/js/bootstrap.min.js"></script> + <!-- ............. Bootstrap ............ --> + + <!-- ..... Application Specific JavaScript ......... --> + <script src="https://d3js.org/d3.v4.min.js" charset="utf-8"></script> + <!-- ..... Application Specific JavaScript ......... --> + + <script type='module'> + import Util from './js/Util.js'; + new Util(); + </script> + <!-- ............... End: Import JavaScript ................. --> + + + +</body> +<!-- ................... Body ................ --> + +</html> + diff --git a/webapp/data/americas.json b/webapp/data/americas.json new file mode 100644 index 000000000..3b51d5c2c --- /dev/null +++ b/webapp/data/americas.json @@ -0,0 +1 @@ +{"type":"FeatureCollection","features":[{"type":"Feature","properties":{"scalerank":1,"featurecla":"Admin-0 country","labelrank":4,"sovereignt":"The Bahamas","sov_a3":"BHS","adm0_dif":0,"level":2,"type":"Sovereign country","admin":"The Bahamas","adm0_a3":"BHS","geou_dif":0,"geounit":"The Bahamas","gu_a3":"BHS","su_dif":0,"subunit":"The Bahamas","su_a3":"BHS","brk_diff":0,"name":"Bahamas","name_long":"Bahamas","brk_a3":"BHS","brk_name":"Bahamas","brk_group":null,"abbrev":"Bhs.","postal":"BS","formal_en":"Commonwealth of the Bahamas","formal_fr":null,"note_adm0":null,"note_brk":null,"name_sort":"Bahamas, The","name_alt":null,"mapcolor7":1,"mapcolor8":1,"mapcolor9":2,"mapcolor13":5,"pop_est":309156,"gdp_md_est":9093,"pop_year":-99,"lastcensus":2010,"gdp_year":-99,"economy":"6. Developing region","income_grp":"2. High income: nonOECD","wikipedia":-99,"fips_10":null,"iso_a2":"BS","iso_a3":"BHS","iso_n3":"044","un_a3":"044","wb_a2":"BS","wb_a3":"BHS","woe_id":-99,"adm0_a3_is":"BHS","adm0_a3_us":"BHS","adm0_a3_un":-99,"adm0_a3_wb":-99,"continent":"North America","region_un":"Americas","subregion":"Caribbean","region_wb":"Latin America & Caribbean","name_len":7,"long_len":7,"abbrev_len":4,"tiny":-99,"homepart":1,"filename":"BHS.geojson"},"geometry":{"type":"MultiPolygon","coordinates":[[[[-77.53466,23.75975],[-77.78,23.71],[-78.03405,24.28615],[-78.40848,24.57564],[-78.19087,25.2103],[-77.89,25.17],[-77.54,24.34],[-77.53466,23.75975]]],[[[-77.82,26.58],[-78.91,26.42],[-78.98,26.79],[-78.51,26.87],[-77.85,26.84],[-77.82,26.58]]],[[[-77,26.59],[-77.17255,25.87918],[-77.35641,26.00735],[-77.34,26.53],[-77.78802,26.92516],[-77.79,27.04],[-77,26.59]]]]}},{"type":"Feature","properties":{"scalerank":1,"featurecla":"Admin-0 country","labelrank":2,"sovereignt":"Mexico","sov_a3":"MEX","adm0_dif":0,"level":2,"type":"Sovereign country","admin":"Mexico","adm0_a3":"MEX","geou_dif":0,"geounit":"Mexico","gu_a3":"MEX","su_dif":0,"subunit":"Mexico","su_a3":"MEX","brk_diff":0,"name":"Mexico","name_long":"Mexico","brk_a3":"MEX","brk_name":"Mexico","brk_group":null,"abbrev":"Mex.","postal":"MX","formal_en":"United Mexican States","formal_fr":null,"note_adm0":null,"note_brk":null,"name_sort":"Mexico","name_alt":null,"mapcolor7":6,"mapcolor8":1,"mapcolor9":7,"mapcolor13":3,"pop_est":111211789,"gdp_md_est":1563000,"pop_year":-99,"lastcensus":2010,"gdp_year":-99,"economy":"4. Emerging region: MIKT","income_grp":"3. Upper middle income","wikipedia":-99,"fips_10":null,"iso_a2":"MX","iso_a3":"MEX","iso_n3":"484","un_a3":"484","wb_a2":"MX","wb_a3":"MEX","woe_id":-99,"adm0_a3_is":"MEX","adm0_a3_us":"MEX","adm0_a3_un":-99,"adm0_a3_wb":-99,"continent":"North America","region_un":"Americas","subregion":"Central America","region_wb":"Latin America & Caribbean","name_len":6,"long_len":6,"abbrev_len":4,"tiny":-99,"homepart":1,"filename":"MEX.geojson"},"geometry":{"type":"Polygon","coordinates":[[[-97.14000830767071,25.869997463478395],[-97.52807247596655,24.992144069920297],[-97.70294552284223,24.272343044526735],[-97.77604183631905,22.932579860927657],[-97.87236670611111,22.44421173755336],[-97.69904395220419,21.898689480064263],[-97.38895952023677,21.411018988525825],[-97.18933346229329,20.635433254473128],[-96.52557552772032,19.890930894444068],[-96.29212724484177,19.320371405509547],[-95.90088497595995,18.82802419684873],[-94.83906348344271,18.562717393462208],[-94.4257295397562,18.144370835843347],[-93.5486512926824,18.423836981677937],[-92.7861138577835,18.52483856859226],[-92.0373481920904,18.704569200103432],[-91.40790340855926,18.87608327888023],[-90.77186987991087,19.28412038825678],[-90.53358985061305,19.8674181177513],[-90.45147599970124,20.707521877520435],[-90.27861833368489,20.99985545499555],[-89.60132117385149,21.26172577563449],[-88.54386633986284,21.49367544197662],[-87.65841651075772,21.458845526611977],[-87.05189022494807,21.543543199138295],[-86.81198238803296,21.331514797444754],[-86.84590796583262,20.849864610268355],[-87.38329118523586,20.25540477139873],[-87.62105445021075,19.64655304613592],[-87.43675045444176,19.47240346931227],[-87.58656043165593,19.04013011319074],[-87.83719112827151,18.25981598558343],[-88.09066402866318,18.51664785407405],[-88.30003109409364,18.49998220466],[-88.4901228502793,18.48683055264172],[-88.84834387892658,17.883198147040332],[-89.02985734735176,18.00151133877256],[-89.15090938999549,17.955467637600407],[-89.14308041050333,17.808318996649405],[-90.0679335192309,17.81932607672752],[-91.00151994501596,17.817594916245696],[-91.00226925328417,17.25465770107428],[-91.45392127151511,17.252177232324186],[-91.0816700915006,16.91847667079952],[-90.71182186558764,16.687483018454767],[-90.60084672724093,16.47077789963879],[-90.438866950222,16.41010976812811],[-90.46447262242265,16.069562079324726],[-91.74796017125595,16.066564846251765],[-92.2292486234063,15.251446641495873],[-92.08721594925203,15.064584662328512],[-92.20322953974727,14.83010285080411],[-92.22775000686983,14.538828640190957],[-93.35946387406176,15.615429592343672],[-93.87516883011851,15.940164292865914],[-94.69165646033014,16.200975246642884],[-95.25022701697304,16.128318182840644],[-96.05338212765331,15.752087917539596],[-96.55743404822829,15.65351512294279],[-97.26359249549665,15.917064927631316],[-98.01302995480961,16.107311713113912],[-98.94767574745651,16.566043402568763],[-99.69739742714705,16.70616404872817],[-100.82949886758131,17.17107107184205],[-101.66608862995446,17.649026394109626],[-101.91852800170022,17.916090196193977],[-102.47813208698891,17.975750637275098],[-103.50098954955808,18.29229462327885],[-103.91752743204682,18.74857168220001],[-104.9920096504755,19.316133938061682],[-105.49303849976144,19.946767279535436],[-105.73139604370766,20.434101874264115],[-105.39777299683135,20.531718654863425],[-105.50066077352443,20.81689504646613],[-105.27075232625793,21.07628489835514],[-105.26581722697402,21.42210358325235],[-105.6031609769754,21.871145941652568],[-105.69341386597313,22.269080308516152],[-106.02871639689897,22.773752346278627],[-106.90998043498837,23.767774359628902],[-107.91544877809139,24.54891531015295],[-108.40190487347098,25.17231395110593],[-109.26019873740665,25.58060944264406],[-109.44408932171734,25.824883938087677],[-109.29164384645627,26.442934068298428],[-109.80145768923182,26.676175645447927],[-110.3917317370857,27.16211497650454],[-110.64101884646163,27.859876003525528],[-111.17891883018785,27.941240546169066],[-111.75960689985163,28.46795258230395],[-112.2282346260904,28.95440867768349],[-112.27182369672869,29.266844387320074],[-112.80959448937398,30.021113593052345],[-113.16381059451868,30.78688080496943],[-113.14866939985717,31.17096588797892],[-113.87188106978186,31.567608344035193],[-114.2057366606035,31.52404511161313],[-114.77645117883503,31.799532172161147],[-114.93669979537212,31.3934846054276],[-114.77123185917351,30.913617255165267],[-114.67389929895177,30.162681179315992],[-114.33097449426292,29.75043244070741],[-113.58887508833544,29.061611436473015],[-113.42405310754054,28.82617361095123],[-113.27196936730553,28.7547826197399],[-113.14003943566439,28.411289374295958],[-112.9622983467965,28.42519033458251],[-112.76158708377488,27.780216783147523],[-112.45791052941166,27.52581370697476],[-112.2449519519368,27.17172679291076],[-111.6164890206192,26.662817287700477],[-111.28467464887302,25.732589830014433],[-110.98781938357239,25.294606228124564],[-110.71000688357134,24.82600434010186],[-110.65504899782887,24.298594672131117],[-110.17285620811343,24.265547593680424],[-109.77184709352855,23.811182562754198],[-109.4091043770557,23.36467234953625],[-109.43339230023292,23.1855876734287],[-109.85421932660171,22.818271592698068],[-110.03139197471444,22.823077500901206],[-110.29507097048366,23.43097321216669],[-110.94950130902805,24.00096426034599],[-111.67056840701268,24.484423122652515],[-112.18203589562147,24.738412787367167],[-112.14898881717085,25.47012523040405],[-112.3007108223797,26.012004299416613],[-112.77729671919155,26.32195954030317],[-113.46467078332194,26.768185533143424],[-113.59672990604383,26.639459540304472],[-113.84893673384424,26.90006378835244],[-114.46574662968003,27.142090358991368],[-115.055142178185,27.72272675222291],[-114.98225257043741,27.798200181585116],[-114.57036556685495,27.74148529714489],[-114.19932878299925,28.115002549750553],[-114.16201839888463,28.566111965442303],[-114.93184221073663,29.279479275015486],[-115.518653937627,29.556361599235398],[-115.88736528202958,30.180793768834178],[-116.25835038945293,30.83646434175358],[-116.72152625208498,31.635743720012044],[-117.12775999999985,32.53534],[-115.99135,32.61239000000012],[-114.72139,32.72083],[-114.815,32.52528],[-113.30498,32.03914],[-111.02361,31.33472],[-109.035,31.341940000000136],[-108.24194,31.34222],[-108.24,31.75485371816637],[-106.50759,31.75452],[-106.1429,31.39995],[-105.63159,31.08383],[-105.03737,30.64402],[-104.70575,30.12173],[-104.4569699999999,29.57196],[-103.94,29.27],[-103.11,28.97],[-102.48,29.76],[-101.6624,29.7793],[-100.9576,29.380710000000132],[-100.45584,28.696120000000118],[-100.11,28.110000000000127],[-99.52,27.54],[-99.3,26.84],[-99.02,26.37],[-98.24,26.06],[-97.53,25.84],[-97.14000830767071,25.869997463478395]]]}},{"type":"Feature","properties":{"scalerank":1,"featurecla":"Admin-0 country","labelrank":6,"sovereignt":"Belize","sov_a3":"BLZ","adm0_dif":0,"level":2,"type":"Sovereign country","admin":"Belize","adm0_a3":"BLZ","geou_dif":0,"geounit":"Belize","gu_a3":"BLZ","su_dif":0,"subunit":"Belize","su_a3":"BLZ","brk_diff":0,"name":"Belize","name_long":"Belize","brk_a3":"BLZ","brk_name":"Belize","brk_group":null,"abbrev":"Belize","postal":"BZ","formal_en":"Belize","formal_fr":null,"note_adm0":null,"note_brk":null,"name_sort":"Belize","name_alt":null,"mapcolor7":1,"mapcolor8":4,"mapcolor9":5,"mapcolor13":7,"pop_est":307899,"gdp_md_est":2536,"pop_year":-99,"lastcensus":2010,"gdp_year":-99,"economy":"6. Developing region","income_grp":"4. Lower middle income","wikipedia":-99,"fips_10":null,"iso_a2":"BZ","iso_a3":"BLZ","iso_n3":"084","un_a3":"084","wb_a2":"BZ","wb_a3":"BLZ","woe_id":-99,"adm0_a3_is":"BLZ","adm0_a3_us":"BLZ","adm0_a3_un":-99,"adm0_a3_wb":-99,"continent":"North America","region_un":"Americas","subregion":"Central America","region_wb":"Latin America & Caribbean","name_len":6,"long_len":6,"abbrev_len":6,"tiny":-99,"homepart":1,"filename":"BLZ.geojson"},"geometry":{"type":"Polygon","coordinates":[[[-89.14308041050332,17.80831899664932],[-89.15090938999553,17.95546763760042],[-89.02985734735182,18.001511338772488],[-88.84834387892661,17.883198147040233],[-88.49012285027935,18.486830552641603],[-88.3000310940937,18.4999822046599],[-88.29633622918482,18.35327281338327],[-88.10681291375437,18.348673610909287],[-88.1234785631685,18.07667470954101],[-88.2853549873228,17.644142971258034],[-88.19786678745265,17.489475409408456],[-88.30264075392444,17.131693630435663],[-88.23951799187991,17.036066392479555],[-88.35542822951057,16.530774237529627],[-88.55182451043585,16.265467434143147],[-88.73243364129594,16.233634751851355],[-88.93061275913527,15.887273464415074],[-89.22912167026928,15.88693756760517],[-89.15080603713095,17.015576687075836],[-89.14308041050332,17.80831899664932]]]}},{"type":"Feature","properties":{"scalerank":1,"featurecla":"Admin-0 country","labelrank":2,"sovereignt":"Canada","sov_a3":"CAN","adm0_dif":0,"level":2,"type":"Sovereign country","admin":"Canada","adm0_a3":"CAN","geou_dif":0,"geounit":"Canada","gu_a3":"CAN","su_dif":0,"subunit":"Canada","su_a3":"CAN","brk_diff":0,"name":"Canada","name_long":"Canada","brk_a3":"CAN","brk_name":"Canada","brk_group":null,"abbrev":"Can.","postal":"CA","formal_en":"Canada","formal_fr":null,"note_adm0":null,"note_brk":null,"name_sort":"Canada","name_alt":null,"mapcolor7":6,"mapcolor8":6,"mapcolor9":2,"mapcolor13":2,"pop_est":33487208,"gdp_md_est":1300000,"pop_year":-99,"lastcensus":2011,"gdp_year":-99,"economy":"1. Developed region: G7","income_grp":"1. High income: OECD","wikipedia":-99,"fips_10":null,"iso_a2":"CA","iso_a3":"CAN","iso_n3":"124","un_a3":"124","wb_a2":"CA","wb_a3":"CAN","woe_id":-99,"adm0_a3_is":"CAN","adm0_a3_us":"CAN","adm0_a3_un":-99,"adm0_a3_wb":-99,"continent":"North America","region_un":"Americas","subregion":"Northern America","region_wb":"North America","name_len":6,"long_len":6,"abbrev_len":4,"tiny":-99,"homepart":1,"filename":"CAN.geojson"},"geometry":{"type":"MultiPolygon","coordinates":[[[[-63.6645,46.55001],[-62.9393,46.41587],[-62.01208,46.44314],[-62.50391,46.03339],[-62.87433,45.96818],[-64.1428,46.39265],[-64.39261,46.72747],[-64.01486,47.03601],[-63.6645,46.55001]]],[[[-61.806305,49.10506],[-62.29318,49.08717],[-63.58926,49.40069],[-64.51912,49.87304],[-64.17322,49.95718],[-62.85829,49.70641],[-61.835585,49.28855],[-61.806305,49.10506]]],[[[-123.51000158755114,48.51001089130344],[-124.0128907883995,48.370846259141416],[-125.65501277733837,48.8250045843385],[-125.95499446679275,49.179995835967645],[-126.85000443587187,49.53000031188043],[-127.0299934495444,49.81499583597008],[-128.05933630436624,49.9949590114266],[-128.44458410710217,50.539137681676124],[-128.35841365625544,50.770648098343685],[-127.3085810960299,50.55257355407195],[-126.69500097721232,50.400903225295394],[-125.75500667382319,50.29501821552938],[-125.4150015875588,49.95000051533261],[-124.92076818911934,49.475274970083404],[-123.92250870832102,49.06248362893581],[-123.51000158755114,48.51001089130344]]],[[[-56.13403581401712,50.6870097926793],[-56.795881720595276,49.81230866149096],[-56.1431050278843,50.150117499382844],[-55.471492275602934,49.93581533466846],[-55.82240108908093,49.58712860777911],[-54.935142584845664,49.31301097268684],[-54.47377539734378,49.55669118915918],[-53.476549445191324,49.24913890237405],[-53.78601375997124,48.51678050393363],[-53.086133999226256,48.687803656603535],[-52.958648240762244,48.157164211614486],[-52.64809872090419,47.5355484075755],[-53.069158291218336,46.65549876564495],[-53.52145626485304,46.61829173439483],[-54.17893551290254,46.80706574155701],[-53.961868659060485,47.62520701760192],[-54.24048214376214,47.75227936460763],[-55.4007730780115,46.884993801453135],[-55.99748084168584,46.9197203639533],[-55.29121904155278,47.389562486351],[-56.25079871278052,47.63254507098739],[-57.3252292547771,47.572807115258],[-59.26601518414677,47.603347886742505],[-59.419494188053704,47.899453843774864],[-58.796586473207405,48.25152537697949],[-59.231624518456535,48.52318838153781],[-58.39180497906523,49.12558055276418],[-57.35868974468604,50.718274034215845],[-56.73865007183201,51.28743825947853],[-55.870976935435294,51.632094224649194],[-55.406974249886616,51.58827261006573],[-55.60021826844209,51.31707469339793],[-56.13403581401712,50.6870097926793]]],[[[-133.1800040417117,54.16997549093531],[-132.71000788443132,54.040009315423525],[-131.74998958400326,54.12000438090922],[-132.049480347351,52.984621487024526],[-131.1790425218266,52.180432847698285],[-131.57782954982292,52.18237071390925],[-132.18042842677855,52.639707139692405],[-132.54999243231387,53.100014960332146],[-133.05461117875552,53.41146881775537],[-133.2396644827927,53.8510802272624],[-133.1800040417117,54.16997549093531]]],[[[-79.26582,62.158675],[-79.65752,61.63308],[-80.09956,61.7181],[-80.36215,62.01649],[-80.315395,62.085565],[-79.92939,62.3856],[-79.52002,62.36371],[-79.26582,62.158675]]],[[[-81.89825,62.7108],[-83.06857,62.15922],[-83.77462,62.18231],[-83.99367,62.4528],[-83.25048,62.91409],[-81.87699,62.90458],[-81.89825,62.7108]]],[[[-85.16130794954985,65.65728465439281],[-84.97576371940596,65.217518215589],[-84.4640120104195,65.37177236598018],[-83.88262630891975,65.10961782496355],[-82.78757687043877,64.76669302027469],[-81.64201371939254,64.45513580998696],[-81.55344031444425,63.979609280037145],[-80.81736121287886,64.05748566350101],[-80.10345130076661,63.725981350348604],[-80.99101986359568,63.41124603947497],[-82.54717810741701,63.65172231714524],[-83.10879757356506,64.10187571883972],[-84.10041663281388,63.56971181909802],[-85.52340471061902,63.05237905542409],[-85.86676876498237,63.637252916103556],[-87.22198320183674,63.541238104905226],[-86.35275977247127,64.03583323837071],[-86.22488644076513,64.82291697860826],[-85.88384782585487,65.73877838811705],[-85.16130794954985,65.65728465439281]]],[[[-75.86588,67.14886],[-76.98687,67.09873],[-77.2364,67.58809],[-76.81166,68.14856],[-75.89521,68.28721],[-75.1145,68.01036],[-75.10333,67.58202],[-75.21597,67.44425],[-75.86588,67.14886]]],[[[-95.64768120380052,69.10769035832178],[-96.2695212038006,68.75704035832175],[-97.61740120380057,69.0600303583218],[-98.43180120380052,68.9507003583218],[-99.79740120380053,69.4000303583218],[-98.91740120380055,69.7100303583218],[-98.2182612038005,70.14354035832176],[-97.15740120380056,69.86003035832181],[-96.55740120380054,69.68003035832176],[-96.25740120380053,69.49003035832177],[-95.64768120380052,69.10769035832178]]],[[[-90.5471,69.49766],[-90.55151,68.47499],[-89.21515,69.25873],[-88.01966,68.61508],[-88.31749,67.87338],[-87.35017,67.19872],[-86.30607,67.92146],[-85.57664,68.78456],[-85.52197,69.88211],[-84.10081,69.80539],[-82.62258,69.65826],[-81.28043,69.16202],[-81.2202,68.66567],[-81.96436,68.13253],[-81.25928,67.59716],[-81.38653,67.11078],[-83.34456,66.41154],[-84.73542,66.2573],[-85.76943,66.55833],[-86.0676,66.05625],[-87.03143,65.21297],[-87.32324,64.77563],[-88.48296,64.09897],[-89.91444,64.03273],[-90.70398,63.61017],[-90.77004,62.96021],[-91.93342,62.83508],[-93.15698,62.02469],[-94.24153,60.89865],[-94.62931,60.11021],[-94.6846,58.94882],[-93.21502,58.78212],[-92.76462,57.84571],[-92.29703,57.08709],[-90.89769,57.28468],[-89.03953,56.85172],[-88.03978,56.47162],[-87.32421,55.99914],[-86.07121,55.72383],[-85.01181,55.3026],[-83.36055,55.24489],[-82.27285,55.14832],[-82.4362,54.28227],[-82.12502,53.27703],[-81.40075,52.15788],[-79.91289,51.20842],[-79.14301,51.53393],[-78.60191,52.56208],[-79.12421,54.14145],[-79.82958,54.66772],[-78.22874,55.13645],[-77.0956,55.83741],[-76.54137,56.53423],[-76.62319,57.20263],[-77.30226,58.05209],[-78.51688,58.80458],[-77.33676,59.85261],[-77.77272,60.75788],[-78.10687,62.31964],[-77.41067,62.55053],[-75.69621,62.2784],[-74.6682,62.18111],[-73.83988,62.4438],[-72.90853,62.10507],[-71.67708,61.52535],[-71.37369,61.13717],[-69.59042,61.06141],[-69.62033,60.22125],[-69.2879,58.95736],[-68.37455,58.80106],[-67.64976,58.21206],[-66.20178,58.76731],[-65.24517,59.87071],[-64.58352,60.33558],[-63.80475,59.4426],[-62.50236,58.16708],[-61.39655,56.96745],[-61.79866,56.33945],[-60.46853,55.77548],[-59.56962,55.20407],[-57.97508,54.94549],[-57.3332,54.6265],[-56.93689,53.78032],[-56.15811,53.64749],[-55.75632,53.27036],[-55.68338,52.14664],[-56.40916,51.7707],[-57.12691,51.41972],[-58.77482,51.0643],[-60.03309,50.24277],[-61.72366,50.08046],[-63.86251,50.29099],[-65.36331,50.2982],[-66.39905,50.22897],[-67.23631,49.51156],[-68.51114,49.06836],[-69.95362,47.74488],[-71.10458,46.82171],[-70.25522,46.98606],[-68.65,48.3],[-66.55243,49.1331],[-65.05626,49.23278],[-64.17099,48.74248],[-65.11545,48.07085],[-64.79854,46.99297],[-64.47219,46.23849],[-63.17329,45.73902],[-61.52072,45.88377],[-60.51815,47.00793],[-60.4486,46.28264],[-59.80287,45.9204],[-61.03988,45.26525],[-63.25471,44.67014],[-64.24656,44.26553],[-65.36406,43.54523],[-66.1234,43.61867],[-66.16173,44.46512],[-64.42549,45.29204],[-66.02605,45.25931],[-67.13741,45.13753],[-67.79134,45.70281],[-67.79046,47.06636],[-68.23444,47.35486],[-68.905,47.185],[-69.237216,47.447781],[-69.99997,46.69307],[-70.305,45.915],[-70.66,45.46],[-71.08482,45.30524],[-71.405,45.255],[-71.50506,45.0082],[-73.34783,45.00738],[-74.867,45.00048],[-75.31821,44.81645],[-76.375,44.09631],[-76.5,44.01845889375872],[-76.82003414580558,43.628784288093755],[-77.7378850979577,43.629055589363304],[-78.72027991404238,43.625089423184875],[-79.17167355011188,43.46633942318422],[-79.01,43.27],[-78.92,42.965],[-78.9393621487437,42.86361135514804],[-80.24744767934794,42.36619985612259],[-81.27774654816716,42.20902598730686],[-82.43927771679162,41.675105088867156],[-82.69008928092018,41.675105088867156],[-83.02981014680694,41.83279572200584],[-83.14199968131256,41.975681057292825],[-83.12,42.08],[-82.9,42.43],[-82.43,42.98],[-82.1376423815039,43.571087551439916],[-82.33776312543108,44.44],[-82.55092464875818,45.347516587905375],[-83.59285071484308,45.81689362241237],[-83.46955074739463,45.99468638771259],[-83.61613094759059,46.11692698829907],[-83.89076534700575,46.11692698829907],[-84.09185126416148,46.275418606138174],[-84.14211951367338,46.51222585711574],[-84.3367,46.40877],[-84.6049,46.4396],[-84.54374874544587,46.538684190449146],[-84.77923824739992,46.637101955749046],[-84.87607988151485,46.90008331968238],[-85.65236324740343,47.22021881773051],[-86.46199083122826,47.55333801939204],[-87.43979262330024,47.94],[-88.37811418328673,48.302917588893735],[-89.27291744663668,48.019808254582664],[-89.6,48.01],[-90.83,48.27],[-91.64,48.14],[-92.61,48.45],[-93.63087,48.60926],[-94.32914,48.67074],[-94.64,48.84],[-94.81758,49.38905],[-95.15609,49.38425],[-95.15906950917204,49],[-97.22872000000481,49.0007],[-100.65,49],[-104.04826,48.99986],[-107.05,49],[-110.05,49],[-113,49],[-116.04818,49],[-117.03121,49],[-120,49],[-122.84,49],[-122.97421,49.0025377777778],[-124.91024,49.98456],[-125.62461,50.41656],[-127.43561,50.83061],[-127.99276,51.71583],[-127.85032,52.32961],[-129.12979,52.75538],[-129.30523,53.56159],[-130.51497,54.28757],[-130.53611,54.80278],[-129.98,55.285],[-130.00778,55.91583],[-131.70781,56.55212],[-132.73042,57.69289],[-133.35556,58.41028],[-134.27111,58.86111],[-134.945,59.27056],[-135.47583,59.78778],[-136.47972,59.46389],[-137.4525,58.905],[-138.34089,59.56211],[-139.039,60],[-140.013,60.27682],[-140.99778,60.30639],[-140.9925,66.00003],[-140.986,69.712],[-139.12052,69.47102],[-137.54636,68.99002],[-136.50358,68.89804],[-135.62576,69.31512],[-134.41464,69.62743],[-132.92925,69.50534],[-131.43136,69.94451],[-129.79471,70.19369],[-129.10773,69.77927],[-128.36156,70.01286],[-128.13817,70.48384],[-127.44712,70.37721],[-125.75632,69.48058],[-124.42483,70.1584],[-124.28968,69.39969],[-123.06108,69.56372],[-122.6835,69.85553],[-121.47226,69.79778],[-119.94288,69.37786],[-117.60268,69.01128],[-116.22643,68.84151],[-115.2469,68.90591],[-113.89794,68.3989],[-115.30489,67.90261],[-113.49727,67.68815],[-110.798,67.80612],[-109.94619,67.98104],[-108.8802,67.38144],[-107.79239,67.88736],[-108.81299,68.31164],[-108.16721,68.65392],[-106.95,68.7],[-106.15,68.8],[-105.34282,68.56122],[-104.33791,68.018],[-103.22115,68.09775],[-101.45433,67.64689],[-99.90195,67.80566],[-98.4432,67.78165],[-98.5586,68.40394],[-97.66948,68.57864],[-96.11991,68.23939],[-96.12588,67.29338],[-95.48943,68.0907],[-94.685,68.06383],[-94.23282,69.06903],[-95.30408,69.68571],[-96.47131,70.08976],[-96.39115,71.19482],[-95.2088,71.92053],[-93.88997,71.76015],[-92.87818,71.31869],[-91.51964,70.19129],[-92.40692,69.69997],[-90.5471,69.49766]]],[[[-114.1671699999999,73.12145],[-114.66634,72.65277],[-112.44101999999988,72.95540000000011],[-111.05039,72.4504],[-109.92034999999989,72.96113],[-109.00654,72.63335],[-108.18835,71.65089],[-107.68599,72.06548],[-108.39639,73.08953000000011],[-107.51645,73.23598],[-106.52259,73.07601],[-105.40246,72.67259],[-104.77484,71.6984],[-104.46475999999984,70.99297],[-102.78537,70.49776],[-100.9807799999999,70.02432],[-101.08929,69.58447000000012],[-102.73116,69.50402],[-102.09329,69.11962000000011],[-102.43024,68.75282],[-104.24,68.91],[-105.96,69.18000000000015],[-107.12254,69.11922],[-109,68.78],[-111.53414887520013,68.63005915681794],[-113.3132,68.53554],[-113.85495999999983,69.00744000000012],[-115.22,69.28],[-116.10794,69.16821],[-117.34,69.96000000000012],[-116.67472999999988,70.06655],[-115.13112,70.2373],[-113.72141,70.19237],[-112.4161,70.36638],[-114.35,70.6],[-116.48684,70.52045],[-117.9048,70.54056000000014],[-118.43238,70.9092],[-116.11311,71.30918],[-117.65568,71.2952],[-119.40199,71.55859],[-118.56267,72.30785],[-117.86642,72.70594],[-115.18909,73.31459000000012],[-114.1671699999999,73.12145]]],[[[-104.5,73.42],[-105.38,72.76],[-106.94,73.46],[-106.6,73.6],[-105.26,73.64],[-104.5,73.42]]],[[[-76.34,73.10268498995302],[-76.25140380859375,72.82638549804688],[-77.31443786621091,72.85554504394527],[-78.39167022705081,72.87665557861328],[-79.48625183105466,72.74220275878909],[-79.77583312988284,72.80290222167974],[-80.87609863281253,73.3331832885742],[-80.83388519287105,73.69318389892578],[-80.35305786132812,73.75971984863278],[-78.06443786621094,73.65193176269534],[-76.34,73.10268498995302]]],[[[-86.56217851433414,73.15744700793846],[-85.77437130404454,72.53412588163383],[-84.85011247428824,73.34027822538712],[-82.31559017610098,73.75095083281059],[-80.60008765330764,72.71654368762421],[-80.7489416165244,72.06190664335077],[-78.77063859731078,72.35217316353416],[-77.82462398955958,72.74961660429105],[-75.60584469267573,72.24367849393741],[-74.22861609566499,71.7671442735579],[-74.09914079455771,71.33084015571765],[-72.24222571479766,71.5569245469945],[-71.20001542833519,70.92001251899723],[-68.7860542466849,70.52502370877426],[-67.91497046575694,70.12194753689761],[-66.96903337265417,69.18608734809189],[-68.80512285020055,68.72019847276442],[-66.44986609563387,68.06716339789202],[-64.86231441919522,67.84753856065163],[-63.42493445499676,66.92847321234066],[-61.85198137068058,66.86212067327784],[-62.1631768459423,66.16025136988961],[-63.918444383384184,64.99866852483284],[-65.14886023625363,65.42603261988668],[-66.72121904159854,66.3880410834322],[-68.01501603867396,66.26272573512439],[-68.14128740097917,65.68978913030438],[-67.08964616562339,65.108455105237],[-65.73208045109976,64.64840566675863],[-65.32016760930128,64.38273712834606],[-64.66940629744968,63.39292674422748],[-65.01380388045891,62.67418508569599],[-66.27504472519047,62.945098781986076],[-68.78318620469273,63.74567007105181],[-67.36968075221304,62.883965562584876],[-66.3282972886672,62.280074774822054],[-66.16556820338016,61.93089712182589],[-68.87736650254465,62.33014923771282],[-71.02343705919384,62.91070811629584],[-72.235378587519,63.39783600529517],[-71.8862784491713,63.67998932560885],[-73.37830624051838,64.19396312118383],[-74.8344189114226,64.67907562932379],[-74.81850257027673,64.38909332951798],[-77.70997982452005,64.22954234481679],[-78.55594885935417,64.57290639918014],[-77.89728105336192,65.30919220647479],[-76.0182742987972,65.32696889918316],[-73.95979529488272,65.45476471624089],[-74.29388342964964,65.8117713487294],[-73.94491248238265,66.31057811142672],[-72.65116716173941,67.28457550726387],[-72.92605994331609,67.72692576768239],[-73.31161780464575,68.06943716091291],[-74.84330725777681,68.55462718370129],[-76.86910091826674,68.89473562283027],[-76.22864905465735,69.14776927354742],[-77.28736996123712,69.76954010688328],[-78.1686339993266,69.82648753526891],[-78.95724219431673,70.16688019477542],[-79.49245500356366,69.87180776638891],[-81.30547095409176,69.74318512641435],[-84.94470618359847,69.9666340196444],[-87.06000342481789,70.26000112576537],[-88.6817132230015,70.41074127876081],[-89.51341956252304,70.76203766548099],[-88.46772111688075,71.21818553332133],[-89.8881512112875,71.22255219184996],[-90.20516028518202,72.2350743679608],[-89.43657670770494,73.12946421985237],[-88.40824154331281,73.53788890247121],[-85.82615108920092,73.80381582304521],[-86.56217851433414,73.15744700793846]]],[[[-100.35642,73.84389],[-99.16387,73.63339],[-97.38,73.76],[-97.12,73.47],[-98.05359,72.99052],[-96.54,72.56],[-96.72,71.66],[-98.35966,71.27285],[-99.32286,71.35639],[-100.01482,71.73827],[-102.5,72.51],[-102.48,72.83],[-100.43836,72.70588],[-101.54,73.36],[-100.35642,73.84389]]],[[[-93.19629553910022,72.77199249947336],[-94.26904659704726,72.02459625923598],[-95.40985551632266,72.06188080513459],[-96.03374508338246,72.94027680123182],[-96.01826799191099,73.4374299180958],[-95.49579342322403,73.86241689726418],[-94.50365759965234,74.1349067247392],[-92.42001217321177,74.10002513294219],[-90.50979285354259,73.85673248971203],[-92.0039652168299,72.9662442084585],[-93.19629553910022,72.77199249947336]]],[[[-120.46,71.38360179308759],[-123.09219,70.90164],[-123.62,71.34],[-125.92894873747335,71.86868846301141],[-125.5,72.29226081179502],[-124.80729,73.02256],[-123.9399999999999,73.68000000000015],[-124.91775,74.29275000000013],[-121.53788,74.44893],[-120.10978,74.24135],[-117.55563999999987,74.18577],[-116.58442,73.89607],[-115.51081,73.47519],[-116.76793999999988,73.22292],[-119.22,72.52],[-120.46,71.82],[-120.46,71.38360179308759]]],[[[-93.61275590694049,74.97999726022445],[-94.15690873897384,74.59234650338686],[-95.60868058956561,74.66686391875177],[-96.82093217648458,74.92762319609658],[-96.2885874092298,75.37782827422335],[-94.85081987178913,75.6472175157609],[-93.97774654821794,75.29648956979597],[-93.61275590694049,74.97999726022445]]],[[[-98.5,76.72],[-97.735585,76.25656],[-97.704415,75.74344],[-98.16,75],[-99.80874,74.89744],[-100.88366,75.05736],[-100.86292,75.64075],[-102.50209,75.5638],[-102.56552,76.3366],[-101.48973,76.30537],[-99.98349,76.64634],[-98.57699,76.58859],[-98.5,76.72]]],[[[-108.21141,76.20168],[-107.81943,75.84552],[-106.92893,76.01282],[-105.881,75.9694],[-105.70498,75.47951],[-106.31347,75.00527],[-109.7,74.85],[-112.22307,74.41696],[-113.74381,74.39427],[-113.87135,74.72029],[-111.79421,75.1625],[-116.31221,75.04343],[-117.7104,75.2222],[-116.34602,76.19903],[-115.40487,76.47887],[-112.59056,76.14134],[-110.81422,75.54919],[-109.0671,75.47321],[-110.49726,76.42982],[-109.5811,76.79417],[-108.54859,76.67832],[-108.21141,76.20168]]],[[[-94.68408586299947,77.09787832305838],[-93.57392106807313,76.77629588490609],[-91.60502315953661,76.77851797149461],[-90.74184587274922,76.44959747995681],[-90.96966142450799,76.07401317005946],[-89.82223792189927,75.84777374948563],[-89.18708289259979,75.61016551380763],[-87.83827633334963,75.56618886992723],[-86.37919226758868,75.48242137318218],[-84.78962521029061,75.69920400664651],[-82.75344458691006,75.78431509063125],[-81.12853084992437,75.71398346628203],[-80.05751095245915,75.33684886341588],[-79.83393286814832,74.92312734648719],[-80.45777075877584,74.65730377877779],[-81.94884253612554,74.44245901152433],[-83.22889360221143,74.56402781849096],[-86.0974523587333,74.41003205026115],[-88.15035030796022,74.39230703398499],[-89.76472205275837,74.51555532500115],[-92.42244096552943,74.837757880341],[-92.7682854886428,75.38681997344216],[-92.88990597204173,75.88265534128266],[-93.893824022176,76.31924367950054],[-95.96245744503582,76.44138092722247],[-97.12137895382949,76.75107778594761],[-96.74512285031236,77.16138865834515],[-94.68408586299947,77.09787832305838]]],[[[-116.19858659550734,77.64528677032621],[-116.33581336145838,76.87696157501055],[-117.10605058476878,76.53003184681913],[-118.04041215703813,76.4811717800871],[-119.89931758688569,76.05321340606199],[-121.4999950771265,75.9000186225328],[-122.85492448615896,76.11654287383568],[-122.8549252936032,76.11654287383568],[-121.15753536032825,76.86450755482835],[-119.10393897182104,77.51221995717464],[-117.57013078496597,77.4983189968881],[-116.19858659550734,77.64528677032621]]],[[[-93.84000301794399,77.5199972602345],[-94.29560828324526,77.4913426785287],[-96.16965410031008,77.5551113959769],[-96.43630449093612,77.83462921824362],[-94.42257727738638,77.82000478790499],[-93.72065629756588,77.63433136668033],[-93.84000301794399,77.5199972602345]]],[[[-110.18693803591297,77.6970148790503],[-112.05119116905848,77.40922882761686],[-113.53427893761906,77.73220652944116],[-112.72458675825384,78.05105011668195],[-111.26444332563085,78.15295604116156],[-109.8544518705471,77.99632477488484],[-110.18693803591297,77.6970148790503]]],[[[-109.66314571820259,78.60197256134569],[-110.88131425661886,78.40691986766001],[-112.54209143761517,78.4079017198735],[-112.5258908760916,78.55055451121522],[-111.5000103422334,78.84999359813057],[-110.96366065147602,78.80444082306522],[-109.66314571820259,78.60197256134569]]],[[[-95.83029496944934,78.05694122996326],[-97.30984290239799,77.85059723582178],[-98.12428931353396,78.08285696075758],[-98.55286780474664,78.4581053738451],[-98.63198442258552,78.87193024363839],[-97.33723141151262,78.83198436147677],[-96.75439876990879,78.765812689927],[-95.55927792029458,78.41831452098029],[-95.83029496944934,78.05694122996326]]],[[[-100.06019182005214,78.3247543403159],[-99.67093909381362,77.9075446642074],[-101.30394019245301,78.01898489044481],[-102.94980872273305,78.34322866486022],[-105.17613277873154,78.38033234324574],[-104.21042945027716,78.6774201524918],[-105.41958045125854,78.91833567983645],[-105.49228919149316,79.30159393992919],[-103.52928239623793,79.16534902619165],[-100.82515804726881,78.80046173777869],[-100.06019182005214,78.3247543403159]]],[[[-87.02,79.66],[-85.81435,79.3369],[-87.18756,79.0393],[-89.03535,78.28723],[-90.80436,78.21533],[-92.87669,78.34333],[-93.95116,78.75099],[-93.93574,79.11373],[-93.14524,79.3801],[-94.974,79.37248],[-96.07614,79.70502],[-96.70972,80.15777],[-96.01644,80.60233],[-95.32345,80.90729],[-94.29843,80.97727],[-94.73542,81.20646],[-92.40984,81.25739],[-91.13289,80.72345],[-89.45,80.50932203389829],[-87.81,80.32],[-87.02,79.66]]],[[[-68.5,83.10632151676575],[-65.82735,83.02801],[-63.68,82.9],[-61.85,82.6286],[-61.89388,82.36165],[-64.334,81.92775],[-66.75342,81.72527],[-67.65755,81.50141],[-65.48031,81.50657],[-67.84,80.9],[-69.4697,80.61683],[-71.18,79.8],[-73.2428,79.63415],[-73.88,79.43016220480207],[-76.90773,79.32309],[-75.52924,79.19766],[-76.22046,79.01907],[-75.39345,78.52581],[-76.34354,78.18296],[-77.88851,77.89991],[-78.36269,77.50859],[-79.75951,77.20968],[-79.61965,76.98336],[-77.91089,77.022045],[-77.88911,76.777955],[-80.56125,76.17812],[-83.17439,76.45403],[-86.11184,76.29901],[-87.6,76.42],[-89.49068,76.47239],[-89.6161,76.95213],[-87.76739,77.17833],[-88.26,77.9],[-87.65,77.97022222222222],[-84.97634,77.53873],[-86.34,78.18],[-87.96192,78.37181],[-87.15198,78.75867],[-85.37868,78.9969],[-85.09495,79.34543],[-86.50734,79.73624],[-86.93179,80.25145],[-84.19844,80.20836],[-83.40869565217383,80.1],[-81.84823,80.46442],[-84.1,80.58],[-87.59895,80.51627],[-89.36663,80.85569],[-90.2,81.26],[-91.36786,81.5531],[-91.58702,81.89429],[-90.1,82.085],[-88.93227,82.11751],[-86.97024,82.27961],[-85.5,82.65227345805704],[-84.260005,82.6],[-83.18,82.32],[-82.42,82.86],[-81.1,83.02],[-79.30664,83.13056],[-76.25,83.1720588235294],[-75.71878,83.06404],[-72.83153,83.23324],[-70.665765,83.16978075838284],[-68.5,83.10632151676575]]]]}},{"type":"Feature","properties":{"scalerank":1,"featurecla":"Admin-0 country","labelrank":2,"sovereignt":"United States of America","sov_a3":"US1","adm0_dif":1,"level":2,"type":"Country","admin":"United States of America","adm0_a3":"USA","geou_dif":0,"geounit":"United States of America","gu_a3":"USA","su_dif":0,"subunit":"United States of America","su_a3":"USA","brk_diff":0,"name":"United States","name_long":"United States","brk_a3":"USA","brk_name":"United States","brk_group":null,"abbrev":"U.S.A.","postal":"US","formal_en":"United States of America","formal_fr":null,"note_adm0":null,"note_brk":null,"name_sort":"United States of America","name_alt":null,"mapcolor7":4,"mapcolor8":5,"mapcolor9":1,"mapcolor13":1,"pop_est":313973000,"gdp_md_est":15094000,"pop_year":0,"lastcensus":2010,"gdp_year":0,"economy":"1. Developed region: G7","income_grp":"1. High income: OECD","wikipedia":0,"fips_10":null,"iso_a2":"US","iso_a3":"USA","iso_n3":"840","un_a3":"840","wb_a2":"US","wb_a3":"USA","woe_id":-99,"adm0_a3_is":"USA","adm0_a3_us":"USA","adm0_a3_un":-99,"adm0_a3_wb":-99,"continent":"North America","region_un":"Americas","subregion":"Northern America","region_wb":"North America","name_len":13,"long_len":13,"abbrev_len":6,"tiny":-99,"homepart":1,"filename":"USA.geojson"},"geometry":{"type":"MultiPolygon","coordinates":[[[[-155.54211,19.08348],[-155.68817,18.91619],[-155.93665,19.05939],[-155.90806,19.33888],[-156.07347,19.70294],[-156.02368,19.81422],[-155.85008,19.97729],[-155.91907,20.17395],[-155.86108,20.26721],[-155.78505,20.2487],[-155.40214,20.07975],[-155.22452,19.99302],[-155.06226,19.8591],[-154.80741,19.50871],[-154.83147,19.45328],[-155.22217,19.23972],[-155.54211,19.08348]]],[[[-156.07926,20.64397],[-156.41445,20.57241],[-156.58673,20.783],[-156.70167,20.8643],[-156.71055,20.92676],[-156.61258,21.01249],[-156.25711,20.91745],[-155.99566,20.76404],[-156.07926,20.64397]]],[[[-156.75824,21.17684],[-156.78933,21.06873],[-157.32521,21.09777],[-157.25027,21.21958],[-156.75824,21.17684]]],[[[-157.65283,21.32217],[-157.70703,21.26442],[-157.7786,21.27729],[-158.12667,21.31244],[-158.2538,21.53919],[-158.29265,21.57912],[-158.0252,21.71696],[-157.94161,21.65272],[-157.65283,21.32217]]],[[[-159.34512,21.982],[-159.46372,21.88299],[-159.80051,22.06533],[-159.74877,22.1382],[-159.5962,22.23618],[-159.36569,22.21494],[-159.34512,21.982]]],[[[-94.81758,49.38905],[-94.63999999999987,48.84000000000012],[-94.32914,48.67074000000011],[-93.63087,48.60926],[-92.61,48.45],[-91.64,48.14],[-90.82999999999986,48.27],[-89.6,48.010000000000105],[-89.27291744663668,48.01980825458284],[-88.37811418328653,48.30291758889382],[-87.43979262330024,47.94],[-86.46199083122815,47.55333801939204],[-85.65236324740323,47.22021881773051],[-84.87607988151485,46.90008331968238],[-84.77923824739983,46.63710195574913],[-84.54374874544567,46.53868419044923],[-84.6049,46.4396],[-84.3367,46.40877000000011],[-84.1421195136733,46.51222585711574],[-84.09185126416148,46.27541860613826],[-83.89076534700567,46.116926988299156],[-83.6161309475905,46.116926988299156],[-83.46955074739463,45.99468638771259],[-83.59285071484308,45.81689362241255],[-82.55092464875818,45.34751658790545],[-82.33776312543108,44.44],[-82.13764238150397,43.57108755144],[-82.43,42.9800000000001],[-82.89999999999989,42.43000000000015],[-83.11999999999989,42.08],[-83.14199968131256,41.975681057293],[-83.02981014680694,41.83279572200601],[-82.69008928092018,41.675105088867326],[-82.43927771679162,41.675105088867326],[-81.27774654816707,42.20902598730686],[-80.24744767934784,42.36619985612267],[-78.9393621487437,42.86361135514812],[-78.92,42.965],[-79.00999999999988,43.27],[-79.17167355011188,43.46633942318431],[-78.72027991404238,43.62508942318496],[-77.73788509795762,43.62905558936339],[-76.82003414580558,43.628784288093755],[-76.5,44.018458893758606],[-76.375,44.09631],[-75.31821,44.816450000000174],[-74.867,45.000480000000124],[-73.34783,45.00738],[-71.50505999999987,45.0082000000001],[-71.405,45.25500000000014],[-71.08482,45.30524000000017],[-70.6599999999998,45.46],[-70.305,45.915],[-69.99997,46.69307],[-69.237216,47.447781],[-68.905,47.185],[-68.23444,47.35486],[-67.79046,47.06636],[-67.79134,45.70281000000014],[-67.13741,45.13753],[-66.96466,44.80970000000016],[-68.03252,44.3252],[-69.05999999999989,43.98],[-70.11617,43.684050000000155],[-70.645475633411,43.09023834896405],[-70.81489,42.8653],[-70.825,42.335],[-70.495,41.805],[-70.08,41.78],[-70.185,42.145],[-69.88497,41.92283000000012],[-69.96503,41.63717000000017],[-70.64,41.475],[-71.12039,41.49445000000017],[-71.85999999999984,41.32],[-72.295,41.27],[-72.87643,41.22065],[-73.71,40.93110235165449],[-72.24126,41.11948000000015],[-71.94499999999982,40.93],[-73.345,40.63],[-73.982,40.628],[-73.952325,40.75075],[-74.25671,40.47351],[-73.96244,40.42763],[-74.17838,39.70926],[-74.90604,38.93954],[-74.98041,39.1964],[-75.20002,39.248450000000105],[-75.52805,39.4985],[-75.32,38.96],[-75.0718347647898,38.78203223017928],[-75.05673,38.40412000000012],[-75.37747,38.01551],[-75.94023,37.21689],[-76.03127,37.2566],[-75.72204999999978,37.93705000000011],[-76.23287,38.319215],[-76.35,39.15],[-76.542725,38.71761500000011],[-76.32933,38.08326],[-76.98999793161354,38.23999176691339],[-76.30162,37.917945],[-76.25874,36.96640000000011],[-75.9718,36.89726],[-75.86803999999984,36.55125],[-75.72749,35.55074000000013],[-76.36318,34.80854000000013],[-77.39763499999988,34.51201],[-78.05496,33.92547],[-78.55434999999983,33.86133000000012],[-79.06067,33.49395],[-79.20357,33.15839],[-80.301325,32.509355],[-80.86498,32.0333],[-81.33629,31.44049],[-81.49042,30.72999000000013],[-81.31371,30.035520000000105],[-80.98,29.180000000000117],[-80.53558499999988,28.47213],[-80.5299999999998,28.040000000000106],[-80.05653928497756,26.880000000000138],[-80.088015,26.205765],[-80.13155999999987,25.816775],[-80.38103,25.20616],[-80.67999999999988,25.08],[-81.17213,25.201260000000133],[-81.33,25.64],[-81.70999999999981,25.87],[-82.24,26.730000000000132],[-82.70515,27.49504],[-82.85526,27.88624],[-82.65,28.550000000000153],[-82.92999999999988,29.100000000000136],[-83.70959,29.93656],[-84.1,30.090000000000117],[-85.10882,29.63615],[-85.28784,29.68612000000013],[-85.7731,30.152610000000124],[-86.39999999999988,30.40000000000012],[-87.53036,30.27433],[-88.41782,30.3849],[-89.18048999999984,30.31598],[-89.59383117841978,30.15999400483685],[-89.413735,29.89419],[-89.43,29.48864],[-89.21767,29.29108],[-89.40823,29.15961],[-89.77928,29.307140000000143],[-90.15463,29.11743],[-90.880225,29.148535000000123],[-91.62678499999987,29.67700000000013],[-92.49906,29.5523],[-93.22637,29.78375],[-93.84842,29.71363],[-94.69,29.480000000000132],[-95.60026,28.73863],[-96.59404,28.30748],[-97.13999999999982,27.83],[-97.37,27.38],[-97.37999999999987,26.69],[-97.33,26.21000000000012],[-97.13999999999982,25.87],[-97.52999999999989,25.84],[-98.24,26.060000000000116],[-99.01999999999988,26.37],[-99.3,26.84],[-99.51999999999987,27.54],[-100.11,28.110000000000127],[-100.45584,28.696120000000118],[-100.9576,29.380710000000132],[-101.6624,29.779300000000116],[-102.48,29.76],[-103.11,28.97],[-103.94,29.27],[-104.45696999999984,29.57196],[-104.70575,30.12173],[-105.03737,30.64402],[-105.63159,31.08383000000012],[-106.1429,31.39995],[-106.50758999999982,31.75452],[-108.24,31.7548537181664],[-108.24194,31.34222],[-109.035,31.34194000000016],[-111.02361,31.33472],[-113.30498,32.03914],[-114.815,32.52528],[-114.72138999999986,32.72083],[-115.9913499999999,32.61239000000014],[-117.12775999999978,32.53534],[-117.29593769127388,33.04622461520389],[-117.944,33.621236431201396],[-118.41060227589749,33.740909223124504],[-118.51989482279971,34.02778157757575],[-119.081,34.078],[-119.43884064201669,34.3484771782843],[-120.36778,34.44711],[-120.62286,34.60855],[-120.74433,35.15686000000011],[-121.71456999999988,36.16153],[-122.54747,37.551760000000115],[-122.51201,37.78339000000013],[-122.95319,38.11371000000011],[-123.7272,38.95166000000012],[-123.86517,39.76699000000013],[-124.39807,40.3132],[-124.17886,41.142020000000116],[-124.2137,41.99964000000014],[-124.53284,42.7659900000001],[-124.14214,43.70838],[-124.020535,44.615895],[-123.89893,45.52341],[-124.079635,46.86475],[-124.39567,47.72017000000011],[-124.68721008300783,48.18443298339855],[-124.56610107421876,48.3797149658204],[-123.12,48.04],[-122.58736,47.096],[-122.34,47.36],[-122.5,48.18],[-122.84,49.000000000000114],[-120,49.000000000000114],[-117.03121,49.000000000000114],[-116.04818,49.000000000000114],[-113,49.000000000000114],[-110.04999999999983,49.000000000000114],[-107.05,49.000000000000114],[-104.04826,48.99986],[-100.65,49.000000000000114],[-97.22872000000471,49.00070000000011],[-95.15906950917196,49.000000000000114],[-95.15609,49.38425],[-94.81758,49.38905]]],[[[-153.0063140533369,57.11584219016589],[-154.0050902984581,56.73467682558106],[-154.5164027577701,56.9927489284467],[-154.67099280497115,57.46119578717249],[-153.76277950744148,57.81657461204377],[-153.2287294179211,57.968968410872435],[-152.56479061583514,57.901427313866975],[-152.1411472239063,57.59105866152199],[-153.0063140533369,57.11584219016589]]],[[[-165.57916419173358,59.90998688418755],[-166.19277014876727,59.754440822988975],[-166.848337368822,59.94140615502096],[-167.45527706609008,60.21306915957938],[-166.46779212142462,60.38416982689778],[-165.67442969466367,60.293606879306246],[-165.57916419173358,59.90998688418755]]],[[[-171.7316568675394,63.78251536727592],[-171.1144335602452,63.592191067144995],[-170.4911124339407,63.69497549097352],[-169.68250545965358,63.431115627691156],[-168.6894394603007,63.2975062120006],[-168.7719408844546,63.18859813094545],[-169.52943986720504,62.9769314642779],[-170.29055620021597,63.194437567794466],[-170.67138566799088,63.37582184513897],[-171.55306311753867,63.317789211675084],[-171.7911106028912,63.405845852300494],[-171.7316568675394,63.78251536727592]]],[[[-155.06779029032424,71.1477763943237],[-154.34416520894123,70.6964085964702],[-153.90000627339262,70.8899885118357],[-152.2100060699353,70.82999217394485],[-152.27000240782615,70.60000621202985],[-150.73999243874454,70.43001658800571],[-149.72000301816752,70.53001048449045],[-147.61336157935708,70.2140349392418],[-145.6899898002253,70.12000967068676],[-144.92001095907642,69.9899917670405],[-143.5894461804252,70.15251414659832],[-142.07251034871342,69.85193817817265],[-140.98598752156073,69.71199839952638],[-140.9859883290049,69.71199839952638],[-140.9924987520294,66.00002859156868],[-140.99776974812312,60.30639679629861],[-140.0129978161531,60.27683787702759],[-139.03900042031586,60.000007229240026],[-138.34089,59.56211000000016],[-137.4525,58.905000000000115],[-136.4797200000001,59.46389],[-135.47583,59.78778],[-134.945,59.27056000000013],[-134.27111,58.86111],[-133.35554888220722,58.410285142645165],[-132.73042,57.69289000000011],[-131.70780999999988,56.55212],[-130.00778,55.91583],[-129.9799942633583,55.28499787049722],[-130.53611018946725,54.8027534043494],[-131.08581823797215,55.17890615500204],[-131.9672114671423,55.49777558045906],[-132.25001074285947,56.36999624289746],[-133.53918108435641,57.17888743756214],[-134.07806292029605,58.1230675319669],[-135.03821103227907,58.18771474876393],[-136.62806230995466,58.21220937767046],[-137.80000627968604,58.49999542910379],[-139.867787041413,59.53776154238915],[-140.82527381713305,59.727517401765084],[-142.57444353556446,60.08444651960499],[-143.9588809948799,59.9991804063234],[-145.92555681682785,60.45860972761429],[-147.11437394914668,60.88465607364463],[-148.22430620012767,60.672989406977166],[-148.01806555885076,59.97832896589363],[-148.5708225168609,59.914172675203304],[-149.72785783587585,59.70565827090556],[-150.60824337461645,59.36821116803949],[-151.71639278868332,59.15582103131999],[-151.85943315326716,59.744984035879604],[-151.4097190012472,60.72580272077939],[-150.34694149473253,61.03358755150986],[-150.62111080625698,61.284424953854455],[-151.89583919981686,60.72719798445129],[-152.5783298410956,60.06165721296429],[-154.01917212625762,59.35027944603428],[-153.28751135965317,58.8647276882198],[-154.2324924387585,58.14637360293054],[-155.30749142151024,57.72779450136633],[-156.3083347239231,57.42277435976365],[-156.55609737854633,56.979984849670636],[-158.11721655986776,56.46360809999419],[-158.43332129619716,55.99415355083855],[-159.60332739971744,55.56668610292012],[-160.2897196116342,55.643580634170576],[-161.2230476552578,55.364734605523495],[-162.23776607974108,55.02418691672011],[-163.06944658104638,54.68973704692717],[-164.7855692210272,54.40417308208217],[-164.94222632552004,54.57222483989534],[-163.84833960676568,55.03943146424612],[-162.87000139061593,55.348043117893205],[-161.80417497459604,55.89498647727043],[-160.56360470278116,56.00805451112504],[-160.0705598622845,56.41805532492876],[-158.68444291891944,57.01667511659787],[-158.46109737855394,57.21692129172888],[-157.7227703521839,57.57000051536306],[-157.55027442119356,58.32832632103023],[-157.041674974577,58.91888458926172],[-158.19473120830548,58.61580231386984],[-158.5172179840231,58.78778148053732],[-159.05860612692874,58.424186102931685],[-159.71166704001735,58.93139028587634],[-159.9812888255002,58.57254914004164],[-160.35527116599653,59.07112335879364],[-161.35500342511506,58.670837714260756],[-161.96889360252635,58.67166453717738],[-162.05498653872468,59.26692536074745],[-161.87417070213536,59.6336213242906],[-162.5180590484921,59.98972361921391],[-163.81834143782015,59.79805573184339],[-164.66221757714646,60.26748444278265],[-165.34638770247483,60.50749563256241],[-165.35083187565186,61.07389516869751],[-166.12137915755596,61.500019029376226],[-165.73445187077053,62.074996853271806],[-164.91917863671785,62.63307648380793],[-164.56250790103934,63.14637848576305],[-163.75333248599702,63.21944896102377],[-163.0672244944579,63.05945872664802],[-162.26055538638172,63.54193573674117],[-161.5344498362486,63.455816962326764],[-160.77250668032113,63.766108100023274],[-160.95833513084256,64.22279857040277],[-161.5180684072122,64.40278758407531],[-160.77777767641476,64.78860382756642],[-161.39192623598763,64.77723501246234],[-162.45305009666885,64.55944468856822],[-162.7577860178941,64.33860545516882],[-163.5463942128843,64.5591604681905],[-164.96082984114517,64.44694509546885],[-166.42528825586447,64.68667206487072],[-166.84500423893905,65.08889557561453],[-168.11056006576717,65.66999705673675],[-166.70527116602196,66.0883177761394],[-164.4747096425755,66.5766600612975],[-163.65251176659564,66.5766600612975],[-163.78860165103617,66.07720734319668],[-161.67777442121016,66.11611969671242],[-162.48971452538,66.73556509059512],[-163.71971696679108,67.1163945583701],[-164.4309913808565,67.6163382025778],[-165.39028683170676,68.04277212185025],[-166.76444068099602,68.35887685817968],[-166.20470740462662,68.88303091091618],[-164.4308105133435,68.91553538682774],[-163.16861365461452,69.3711148139129],[-162.93056616926202,69.85806183539927],[-161.90889726463553,70.33332998318764],[-160.9347965159337,70.44768992784958],[-159.03917578838715,70.89164215766894],[-158.11972286683397,70.82472117785105],[-156.58082455139805,71.35776357694175],[-155.06779029032424,71.1477763943237]]]]}},{"type":"Feature","properties":{"scalerank":1,"featurecla":"Admin-0 country","labelrank":5,"sovereignt":"Costa Rica","sov_a3":"CRI","adm0_dif":0,"level":2,"type":"Sovereign country","admin":"Costa Rica","adm0_a3":"CRI","geou_dif":0,"geounit":"Costa Rica","gu_a3":"CRI","su_dif":0,"subunit":"Costa Rica","su_a3":"CRI","brk_diff":0,"name":"Costa Rica","name_long":"Costa Rica","brk_a3":"CRI","brk_name":"Costa Rica","brk_group":null,"abbrev":"C.R.","postal":"CR","formal_en":"Republic of Costa Rica","formal_fr":null,"note_adm0":null,"note_brk":null,"name_sort":"Costa Rica","name_alt":null,"mapcolor7":3,"mapcolor8":2,"mapcolor9":4,"mapcolor13":2,"pop_est":4253877,"gdp_md_est":48320,"pop_year":-99,"lastcensus":2011,"gdp_year":-99,"economy":"5. Emerging region: G20","income_grp":"3. Upper middle income","wikipedia":-99,"fips_10":null,"iso_a2":"CR","iso_a3":"CRI","iso_n3":"188","un_a3":"188","wb_a2":"CR","wb_a3":"CRI","woe_id":-99,"adm0_a3_is":"CRI","adm0_a3_us":"CRI","adm0_a3_un":-99,"adm0_a3_wb":-99,"continent":"North America","region_un":"Americas","subregion":"Central America","region_wb":"Latin America & Caribbean","name_len":10,"long_len":10,"abbrev_len":4,"tiny":-99,"homepart":1,"filename":"CRI.geojson"},"geometry":{"type":"Polygon","coordinates":[[[-82.96578304719736,8.225027980985985],[-83.50843726269431,8.446926581247283],[-83.71147396516908,8.656836249216866],[-83.59631303580665,8.830443223501419],[-83.63264156770784,9.051385809765321],[-83.90988562695372,9.29080272057358],[-84.30340165885636,9.487354030795714],[-84.64764421256866,9.615537421095707],[-84.71335079622777,9.908051866083852],[-84.97566036654133,10.086723130733006],[-84.91137488477024,9.795991522658923],[-85.11092342806532,9.55703969974131],[-85.33948828809227,9.83454214114866],[-85.66078650586698,9.933347479690724],[-85.79744483106285,10.134885565629034],[-85.79170874707843,10.439337266476613],[-85.65931372754666,10.75433095951172],[-85.94172543002176,10.895278428587801],[-85.7125404528073,11.088444932494824],[-85.56185197624418,11.217119248901597],[-84.90300330273895,10.952303371621896],[-84.67306901725627,11.082657172078143],[-84.35593075228104,10.999225572142905],[-84.19017859570485,10.793450018756674],[-83.89505449088595,10.726839097532446],[-83.65561174186158,10.938764146361422],[-83.40231970898296,10.395438137244652],[-83.01567664257517,9.992982082555555],[-82.54619625520348,9.566134751824677],[-82.93289099804358,9.476812038608173],[-82.92715491405916,9.074330145702916],[-82.71918311230053,8.925708726431495],[-82.86865719270477,8.807266343618522],[-82.82977067740516,8.62629547773237],[-82.91317643912421,8.42351715741907],[-82.96578304719736,8.225027980985985]]]}},{"type":"Feature","properties":{"scalerank":1,"featurecla":"Admin-0 country","labelrank":5,"sovereignt":"Honduras","sov_a3":"HND","adm0_dif":0,"level":2,"type":"Sovereign country","admin":"Honduras","adm0_a3":"HND","geou_dif":0,"geounit":"Honduras","gu_a3":"HND","su_dif":0,"subunit":"Honduras","su_a3":"HND","brk_diff":0,"name":"Honduras","name_long":"Honduras","brk_a3":"HND","brk_name":"Honduras","brk_group":null,"abbrev":"Hond.","postal":"HN","formal_en":"Republic of Honduras","formal_fr":null,"note_adm0":null,"note_brk":null,"name_sort":"Honduras","name_alt":null,"mapcolor7":2,"mapcolor8":5,"mapcolor9":2,"mapcolor13":5,"pop_est":7792854,"gdp_md_est":33720,"pop_year":-99,"lastcensus":2001,"gdp_year":-99,"economy":"6. Developing region","income_grp":"4. Lower middle income","wikipedia":-99,"fips_10":null,"iso_a2":"HN","iso_a3":"HND","iso_n3":"340","un_a3":"340","wb_a2":"HN","wb_a3":"HND","woe_id":-99,"adm0_a3_is":"HND","adm0_a3_us":"HND","adm0_a3_un":-99,"adm0_a3_wb":-99,"continent":"North America","region_un":"Americas","subregion":"Central America","region_wb":"Latin America & Caribbean","name_len":8,"long_len":8,"abbrev_len":5,"tiny":-99,"homepart":1,"filename":"HND.geojson"},"geometry":{"type":"Polygon","coordinates":[[[-87.31665442579549,12.984685777229004],[-87.48940873894713,13.297534898323931],[-87.79311113152653,13.384480495655168],[-87.72350297722932,13.785050360565606],[-87.85951534702161,13.893312486217097],[-88.06534257684012,13.96462596277979],[-88.50399797234962,13.845485948130943],[-88.54123084181595,13.980154730683523],[-88.84307288283276,14.140506700085211],[-89.05851192905766,14.340029405164215],[-89.35332597528281,14.424132798719086],[-89.14553504103719,14.678019110569153],[-89.22522009963124,14.874286200413678],[-89.15481096063353,15.066419175674866],[-88.6806796943556,15.34624705653539],[-88.22502275262195,15.72772247971403],[-88.12115312371537,15.688655096901359],[-87.90181250685241,15.864458319558196],[-87.61568010125234,15.8787985295192],[-87.52292090528846,15.797278957578783],[-87.36776241733213,15.84694000901129],[-86.90319129102818,15.756712958229569],[-86.44094560417739,15.78283539475319],[-86.11923397494434,15.893448798073962],[-86.00195431185784,16.00540578863439],[-85.68331743034628,15.953651841693953],[-85.44400387240256,15.885749009662446],[-85.18244361035721,15.90915843349063],[-84.98372188997882,15.995923163308701],[-84.52697974316715,15.857223619037427],[-84.36825558138258,15.835157782448732],[-84.06305457226682,15.648244126849136],[-83.77397661002612,15.42407176356687],[-83.41038123242036,15.270902818253774],[-83.14721900097413,14.99582916916421],[-83.48998877636602,15.016267198135663],[-83.62858496777288,14.880073960830371],[-83.97572140169359,14.749435939996486],[-84.22834164095241,14.74876414637663],[-84.4493359036486,14.621614284722511],[-84.64958207877963,14.666805324761867],[-84.8200367906943,14.81958669683263],[-84.92450069857233,14.790492865452336],[-85.05278744173688,14.551541042534723],[-85.14875057650288,14.560196844943619],[-85.16536454948482,14.35436961512505],[-85.51441301140028,14.079011745657908],[-85.69866533073696,13.960078436738002],[-85.8012947252685,13.836054999237604],[-86.09626380079061,14.038187364147234],[-86.31214209668985,13.771356106008225],[-86.52070817741992,13.778487453664468],[-86.75508663607962,13.75484548589094],[-86.73382178419149,13.263092556201398],[-86.88055701368438,13.254204209847217],[-87.00576900912743,13.025794379117258],[-87.31665442579549,12.984685777229004]]]}},{"type":"Feature","properties":{"scalerank":1,"featurecla":"Admin-0 country","labelrank":4,"sovereignt":"Jamaica","sov_a3":"JAM","adm0_dif":0,"level":2,"type":"Sovereign country","admin":"Jamaica","adm0_a3":"JAM","geou_dif":0,"geounit":"Jamaica","gu_a3":"JAM","su_dif":0,"subunit":"Jamaica","su_a3":"JAM","brk_diff":0,"name":"Jamaica","name_long":"Jamaica","brk_a3":"JAM","brk_name":"Jamaica","brk_group":null,"abbrev":"Jam.","postal":"J","formal_en":"Jamaica","formal_fr":null,"note_adm0":null,"note_brk":null,"name_sort":"Jamaica","name_alt":null,"mapcolor7":1,"mapcolor8":2,"mapcolor9":4,"mapcolor13":10,"pop_est":2825928,"gdp_md_est":20910,"pop_year":-99,"lastcensus":2011,"gdp_year":-99,"economy":"6. Developing region","income_grp":"3. Upper middle income","wikipedia":-99,"fips_10":null,"iso_a2":"JM","iso_a3":"JAM","iso_n3":"388","un_a3":"388","wb_a2":"JM","wb_a3":"JAM","woe_id":-99,"adm0_a3_is":"JAM","adm0_a3_us":"JAM","adm0_a3_un":-99,"adm0_a3_wb":-99,"continent":"North America","region_un":"Americas","subregion":"Caribbean","region_wb":"Latin America & Caribbean","name_len":7,"long_len":7,"abbrev_len":4,"tiny":-99,"homepart":1,"filename":"JAM.geojson"},"geometry":{"type":"Polygon","coordinates":[[[-77.56960079619921,18.490525417550487],[-76.89661861846211,18.400866807524082],[-76.36535905628554,18.160700588447597],[-76.19965857614164,17.886867173732966],[-76.9025614081757,17.868237819891746],[-77.20634131540348,17.70111623785982],[-77.76602291534061,17.86159739834224],[-78.33771928578561,18.225967922432233],[-78.21772661000388,18.454532782459193],[-77.79736467152563,18.524218451404778],[-77.56960079619921,18.490525417550487]]]}},{"type":"Feature","properties":{"scalerank":1,"featurecla":"Admin-0 country","labelrank":4,"sovereignt":"Panama","sov_a3":"PAN","adm0_dif":0,"level":2,"type":"Sovereign country","admin":"Panama","adm0_a3":"PAN","geou_dif":0,"geounit":"Panama","gu_a3":"PAN","su_dif":0,"subunit":"Panama","su_a3":"PAN","brk_diff":0,"name":"Panama","name_long":"Panama","brk_a3":"PAN","brk_name":"Panama","brk_group":null,"abbrev":"Pan.","postal":"PA","formal_en":"Republic of Panama","formal_fr":null,"note_adm0":null,"note_brk":null,"name_sort":"Panama","name_alt":null,"mapcolor7":4,"mapcolor8":4,"mapcolor9":6,"mapcolor13":3,"pop_est":3360474,"gdp_md_est":38830,"pop_year":-99,"lastcensus":2010,"gdp_year":-99,"economy":"6. Developing region","income_grp":"3. Upper middle income","wikipedia":-99,"fips_10":null,"iso_a2":"PA","iso_a3":"PAN","iso_n3":"591","un_a3":"591","wb_a2":"PA","wb_a3":"PAN","woe_id":-99,"adm0_a3_is":"PAN","adm0_a3_us":"PAN","adm0_a3_un":-99,"adm0_a3_wb":-99,"continent":"North America","region_un":"Americas","subregion":"Central America","region_wb":"Latin America & Caribbean","name_len":6,"long_len":6,"abbrev_len":4,"tiny":-99,"homepart":1,"filename":"PAN.geojson"},"geometry":{"type":"Polygon","coordinates":[[[-77.88157141794525,7.223771267114785],[-78.21493608266012,7.512254950384161],[-78.42916073272607,8.052041123888927],[-78.18209570993864,8.319182440621773],[-78.4354652574657,8.38770538984079],[-78.62212053090394,8.718124497915028],[-79.12030717641375,8.996092027213022],[-79.55787736684519,8.932374986197146],[-79.76057817251004,8.5845150822244],[-80.16448116730334,8.333315944853595],[-80.38265906443961,8.298408514840432],[-80.4806892564973,8.09030752200107],[-80.00368994822716,7.547524115423371],[-80.276670701809,7.419754136581715],[-80.42115800649708,7.271571966984764],[-80.8864009264208,7.220541490096537],[-81.05954281281473,7.817921047390596],[-81.18971574575795,7.647905585150339],[-81.51951473664468,7.706610012233909],[-81.72131120474445,8.108962714058435],[-82.13144120962892,8.175392767769635],[-82.39093441438257,8.29236237226229],[-82.82008134635042,8.290863755725823],[-82.85095801464482,8.073822740099956],[-82.96578304719736,8.225027980985985],[-82.91317643912421,8.42351715741907],[-82.82977067740516,8.62629547773237],[-82.86865719270477,8.807266343618522],[-82.71918311230053,8.925708726431495],[-82.92715491405916,9.074330145702916],[-82.93289099804358,9.476812038608173],[-82.54619625520348,9.566134751824677],[-82.18712256542341,9.20744863528678],[-82.20758643261095,8.9955752628901],[-81.80856686066929,8.950616766796173],[-81.71415401887204,9.031955471223583],[-81.43928707551154,8.786234035675719],[-80.94730160187676,8.858503526235905],[-80.52190121125008,9.111072089062432],[-79.91459977895599,9.31276520429762],[-79.57330278188431,9.611610012241526],[-79.02119177927793,9.552931423374105],[-79.05845048696037,9.454565334506526],[-78.50088762074719,9.420458889193881],[-78.05592770049802,9.2477304142583],[-77.72951351592641,8.946844387238869],[-77.35336076527385,8.67050466555807],[-77.47472286651133,8.524286200388218],[-77.24256649444008,7.935278225125444],[-77.43110795765699,7.638061224798735],[-77.75341386586139,7.709839789252142],[-77.88157141794525,7.223771267114785]]]}},{"type":"Feature","properties":{"scalerank":1,"featurecla":"Admin-0 country","labelrank":5,"sovereignt":"United States of America","sov_a3":"US1","adm0_dif":1,"level":2,"type":"Dependency","admin":"Puerto Rico","adm0_a3":"PRI","geou_dif":0,"geounit":"Puerto Rico","gu_a3":"PRI","su_dif":0,"subunit":"Puerto Rico","su_a3":"PRI","brk_diff":0,"name":"Puerto Rico","name_long":"Puerto Rico","brk_a3":"PRI","brk_name":"Puerto Rico","brk_group":null,"abbrev":"P.R.","postal":"PR","formal_en":"Commonwealth of Puerto Rico","formal_fr":null,"note_adm0":"Commonwealth of U.S.A.","note_brk":null,"name_sort":"Puerto Rico","name_alt":null,"mapcolor7":4,"mapcolor8":5,"mapcolor9":1,"mapcolor13":1,"pop_est":3971020,"gdp_md_est":70230,"pop_year":-99,"lastcensus":2010,"gdp_year":-99,"economy":"6. Developing region","income_grp":"2. High income: nonOECD","wikipedia":-99,"fips_10":null,"iso_a2":"PR","iso_a3":"PRI","iso_n3":"630","un_a3":"630","wb_a2":"PR","wb_a3":"PRI","woe_id":-99,"adm0_a3_is":"PRI","adm0_a3_us":"PRI","adm0_a3_un":-99,"adm0_a3_wb":-99,"continent":"North America","region_un":"Americas","subregion":"Caribbean","region_wb":"Latin America & Caribbean","name_len":11,"long_len":11,"abbrev_len":4,"tiny":-99,"homepart":-99,"filename":"PRI.geojson"},"geometry":{"type":"Polygon","coordinates":[[[-66.28243445500821,18.514761664295364],[-65.7713028632093,18.426679185453878],[-65.59100379094295,18.228034979723915],[-65.84716386581377,17.97590566657186],[-66.59993445500949,17.981822618069273],[-67.18416236028527,17.946553453030077],[-67.24242753769435,18.374460150622937],[-67.10067908391774,18.52060110114435],[-66.28243445500821,18.514761664295364]]]}},{"type":"Feature","properties":{"scalerank":1,"featurecla":"Admin-0 country","labelrank":5,"sovereignt":"Nicaragua","sov_a3":"NIC","adm0_dif":0,"level":2,"type":"Sovereign country","admin":"Nicaragua","adm0_a3":"NIC","geou_dif":0,"geounit":"Nicaragua","gu_a3":"NIC","su_dif":0,"subunit":"Nicaragua","su_a3":"NIC","brk_diff":0,"name":"Nicaragua","name_long":"Nicaragua","brk_a3":"NIC","brk_name":"Nicaragua","brk_group":null,"abbrev":"Nic.","postal":"NI","formal_en":"Republic of Nicaragua","formal_fr":null,"note_adm0":null,"note_brk":null,"name_sort":"Nicaragua","name_alt":null,"mapcolor7":1,"mapcolor8":4,"mapcolor9":1,"mapcolor13":9,"pop_est":5891199,"gdp_md_est":16790,"pop_year":-99,"lastcensus":2005,"gdp_year":-99,"economy":"6. Developing region","income_grp":"4. Lower middle income","wikipedia":-99,"fips_10":null,"iso_a2":"NI","iso_a3":"NIC","iso_n3":"558","un_a3":"558","wb_a2":"NI","wb_a3":"NIC","woe_id":-99,"adm0_a3_is":"NIC","adm0_a3_us":"NIC","adm0_a3_un":-99,"adm0_a3_wb":-99,"continent":"North America","region_un":"Americas","subregion":"Central America","region_wb":"Latin America & Caribbean","name_len":9,"long_len":9,"abbrev_len":4,"tiny":-99,"homepart":1,"filename":"NIC.geojson"},"geometry":{"type":"Polygon","coordinates":[[[-85.7125404528073,11.088444932494824],[-86.05848832878526,11.403438625529944],[-86.52584998243296,11.806876532432597],[-86.74599158399633,12.143961900272487],[-87.16751624220116,12.458257961471656],[-87.66849341505471,12.909909979702633],[-87.5574666002756,13.064551703336065],[-87.39238623731923,12.914018256069838],[-87.31665442579549,12.984685777228975],[-87.00576900912756,13.025794379117157],[-86.88055701368437,13.254204209847245],[-86.7338217841916,13.263092556201443],[-86.7550866360797,13.754845485890913],[-86.5207081774199,13.77848745366444],[-86.31214209668993,13.77135610600817],[-86.0962638007906,14.038187364147248],[-85.80129472526859,13.83605499923759],[-85.69866533073693,13.960078436738087],[-85.51441301140025,14.079011745657837],[-85.1653645494848,14.354369615125078],[-85.14875057650296,14.560196844943619],[-85.05278744173692,14.551541042534723],[-84.9245006985724,14.790492865452352],[-84.82003679069435,14.819586696832669],[-84.64958207877962,14.666805324761754],[-84.4493359036486,14.621614284722495],[-84.22834164095241,14.748764146376658],[-83.97572140169359,14.749435939996461],[-83.62858496777292,14.880073960830302],[-83.48998877636612,15.016267198135536],[-83.14721900097413,14.99582916916411],[-83.23323442252394,14.899866034398102],[-83.2841615465476,14.6766238468972],[-83.18212643098728,14.31070302983845],[-83.41249996614445,13.970077826386557],[-83.51983191601468,13.567699286345883],[-83.55220720084554,13.127054348193086],[-83.49851538769427,12.869292303921227],[-83.47332312695198,12.419087225794428],[-83.62610449902292,12.320850328007566],[-83.71961300325506,11.893124497927726],[-83.65085751009072,11.629032090700118],[-83.8554703437504,11.373311265503787],[-83.80893571647155,11.103043524617274],[-83.65561174186158,10.938764146361422],[-83.89505449088595,10.726839097532446],[-84.19017859570485,10.793450018756674],[-84.35593075228104,10.999225572142905],[-84.67306901725627,11.082657172078143],[-84.90300330273895,10.952303371621896],[-85.56185197624418,11.217119248901597],[-85.7125404528073,11.088444932494824]]]}},{"type":"Feature","properties":{"scalerank":1,"featurecla":"Admin-0 country","labelrank":6,"sovereignt":"El Salvador","sov_a3":"SLV","adm0_dif":0,"level":2,"type":"Sovereign country","admin":"El Salvador","adm0_a3":"SLV","geou_dif":0,"geounit":"El Salvador","gu_a3":"SLV","su_dif":0,"subunit":"El Salvador","su_a3":"SLV","brk_diff":0,"name":"El Salvador","name_long":"El Salvador","brk_a3":"SLV","brk_name":"El Salvador","brk_group":null,"abbrev":"El. S.","postal":"SV","formal_en":"Republic of El Salvador","formal_fr":null,"note_adm0":null,"note_brk":null,"name_sort":"El Salvador","name_alt":null,"mapcolor7":1,"mapcolor8":4,"mapcolor9":6,"mapcolor13":8,"pop_est":7185218,"gdp_md_est":43630,"pop_year":-99,"lastcensus":2007,"gdp_year":-99,"economy":"6. Developing region","income_grp":"4. Lower middle income","wikipedia":-99,"fips_10":null,"iso_a2":"SV","iso_a3":"SLV","iso_n3":"222","un_a3":"222","wb_a2":"SV","wb_a3":"SLV","woe_id":-99,"adm0_a3_is":"SLV","adm0_a3_us":"SLV","adm0_a3_un":-99,"adm0_a3_wb":-99,"continent":"North America","region_un":"Americas","subregion":"Central America","region_wb":"Latin America & Caribbean","name_len":11,"long_len":11,"abbrev_len":6,"tiny":-99,"homepart":1,"filename":"SLV.geojson"},"geometry":{"type":"Polygon","coordinates":[[[-87.79311113152657,13.384480495655055],[-87.90411210808952,13.149016831917137],[-88.48330156121682,13.163951320849492],[-88.8432279121297,13.259733588102478],[-89.2567427233293,13.458532823129303],[-89.81239356154767,13.520622056527998],[-90.09555457229098,13.735337632700734],[-90.0646779039966,13.881969509328924],[-89.72193396682073,14.134228013561694],[-89.53421932652051,14.244815578666305],[-89.58734269891654,14.362586167859488],[-89.35332597528279,14.424132798719116],[-89.05851192905766,14.340029405164085],[-88.84307288283284,14.140506700085169],[-88.541230841816,13.980154730683479],[-88.50399797234971,13.845485948130857],[-88.06534257684012,13.964625962779778],[-87.8595153470216,13.893312486216983],[-87.72350297722939,13.785050360565506],[-87.79311113152657,13.384480495655055]]]}},{"type":"Feature","properties":{"scalerank":1,"featurecla":"Admin-0 country","labelrank":5,"sovereignt":"Dominican Republic","sov_a3":"DOM","adm0_dif":0,"level":2,"type":"Sovereign country","admin":"Dominican Republic","adm0_a3":"DOM","geou_dif":0,"geounit":"Dominican Republic","gu_a3":"DOM","su_dif":0,"subunit":"Dominican Republic","su_a3":"DOM","brk_diff":0,"name":"Dominican Rep.","name_long":"Dominican Republic","brk_a3":"DOM","brk_name":"Dominican Rep.","brk_group":null,"abbrev":"Dom. Rep.","postal":"DO","formal_en":"Dominican Republic","formal_fr":null,"note_adm0":null,"note_brk":null,"name_sort":"Dominican Republic","name_alt":null,"mapcolor7":5,"mapcolor8":2,"mapcolor9":5,"mapcolor13":7,"pop_est":9650054,"gdp_md_est":78000,"pop_year":-99,"lastcensus":2010,"gdp_year":-99,"economy":"6. Developing region","income_grp":"3. Upper middle income","wikipedia":-99,"fips_10":null,"iso_a2":"DO","iso_a3":"DOM","iso_n3":"214","un_a3":"214","wb_a2":"DO","wb_a3":"DOM","woe_id":-99,"adm0_a3_is":"DOM","adm0_a3_us":"DOM","adm0_a3_un":-99,"adm0_a3_wb":-99,"continent":"North America","region_un":"Americas","subregion":"Caribbean","region_wb":"Latin America & Caribbean","name_len":14,"long_len":18,"abbrev_len":9,"tiny":-99,"homepart":1,"filename":"DOM.geojson"},"geometry":{"type":"Polygon","coordinates":[[[-71.71236141629296,19.714455878167357],[-71.58730445014663,19.8849105900821],[-70.80670610216174,19.880285549391985],[-70.21436499701613,19.62288524014616],[-69.95081519232758,19.647999986240006],[-69.76925004747008,19.293267116772437],[-69.22212582057988,19.313214219637103],[-69.25434607611385,19.015196234609874],[-68.80941199408083,18.979074408437853],[-68.31794328476897,18.612197577381693],[-68.68931596543452,18.205142320218613],[-69.16494584824892,18.42264842373511],[-69.62398759629764,18.380712998930246],[-69.95293392605154,18.42830699307106],[-70.1332329983179,18.245915025296895],[-70.51713721381422,18.184290879788833],[-70.66929846869763,18.426885891183034],[-70.99995012071719,18.283328762276213],[-71.4002099270339,17.5985643579766],[-71.65766191271202,17.7575727401387],[-71.70830481635805,18.04499705654609],[-71.68773759630587,18.31666006110447],[-71.94511206733556,18.61690013272026],[-71.70130265978248,18.78541697842405],[-71.62487321642283,19.169837958243306],[-71.71236141629296,19.714455878167357]]]}},{"type":"Feature","properties":{"scalerank":1,"featurecla":"Admin-0 country","labelrank":5,"sovereignt":"Trinidad and Tobago","sov_a3":"TTO","adm0_dif":0,"level":2,"type":"Sovereign country","admin":"Trinidad and Tobago","adm0_a3":"TTO","geou_dif":0,"geounit":"Trinidad and Tobago","gu_a3":"TTO","su_dif":0,"subunit":"Trinidad and Tobago","su_a3":"TTO","brk_diff":0,"name":"Trinidad and Tobago","name_long":"Trinidad and Tobago","brk_a3":"TTO","brk_name":"Trinidad and Tobago","brk_group":null,"abbrev":"Tr.T.","postal":"TT","formal_en":"Republic of Trinidad and Tobago","formal_fr":null,"note_adm0":null,"note_brk":null,"name_sort":"Trinidad and Tobago","name_alt":null,"mapcolor7":5,"mapcolor8":6,"mapcolor9":2,"mapcolor13":5,"pop_est":1310000,"gdp_md_est":29010,"pop_year":-99,"lastcensus":2011,"gdp_year":-99,"economy":"6. Developing region","income_grp":"2. High income: nonOECD","wikipedia":-99,"fips_10":null,"iso_a2":"TT","iso_a3":"TTO","iso_n3":"780","un_a3":"780","wb_a2":"TT","wb_a3":"TTO","woe_id":-99,"adm0_a3_is":"TTO","adm0_a3_us":"TTO","adm0_a3_un":-99,"adm0_a3_wb":-99,"continent":"North America","region_un":"Americas","subregion":"Caribbean","region_wb":"Latin America & Caribbean","name_len":19,"long_len":19,"abbrev_len":5,"tiny":2,"homepart":1,"filename":"TTO.geojson"},"geometry":{"type":"Polygon","coordinates":[[[-61.68,10.76],[-61.105,10.89],[-60.895,10.855],[-60.935,10.11],[-61.77,10],[-61.95,10.09],[-61.66,10.365],[-61.68,10.76]]]}},{"type":"Feature","properties":{"scalerank":1,"featurecla":"Admin-0 country","labelrank":2,"sovereignt":"Argentina","sov_a3":"ARG","adm0_dif":0,"level":2,"type":"Sovereign country","admin":"Argentina","adm0_a3":"ARG","geou_dif":0,"geounit":"Argentina","gu_a3":"ARG","su_dif":0,"subunit":"Argentina","su_a3":"ARG","brk_diff":0,"name":"Argentina","name_long":"Argentina","brk_a3":"ARG","brk_name":"Argentina","brk_group":null,"abbrev":"Arg.","postal":"AR","formal_en":"Argentine Republic","formal_fr":null,"note_adm0":null,"note_brk":null,"name_sort":"Argentina","name_alt":null,"mapcolor7":3,"mapcolor8":1,"mapcolor9":3,"mapcolor13":13,"pop_est":40913584,"gdp_md_est":573900,"pop_year":-99,"lastcensus":2010,"gdp_year":-99,"economy":"5. Emerging region: G20","income_grp":"3. Upper middle income","wikipedia":-99,"fips_10":null,"iso_a2":"AR","iso_a3":"ARG","iso_n3":"032","un_a3":"032","wb_a2":"AR","wb_a3":"ARG","woe_id":-99,"adm0_a3_is":"ARG","adm0_a3_us":"ARG","adm0_a3_un":-99,"adm0_a3_wb":-99,"continent":"South America","region_un":"Americas","subregion":"South America","region_wb":"Latin America & Caribbean","name_len":9,"long_len":9,"abbrev_len":4,"tiny":-99,"homepart":1,"filename":"ARG.geojson"},"geometry":{"type":"MultiPolygon","coordinates":[[[[-65.5,-55.2],[-66.45,-55.25],[-66.95992,-54.89681],[-67.56244,-54.87001],[-68.63335,-54.8695],[-68.63401022758316,-52.63637045887445],[-68.25,-53.1],[-67.75,-53.85],[-66.45,-54.45],[-65.05,-54.7],[-65.5,-55.2]]],[[[-64.96489213729458,-22.075861504812348],[-64.37702104354227,-22.798091322523547],[-63.98683814152247,-21.993644301035957],[-62.846468471921554,-22.034985446869456],[-62.6850571356579,-22.249029229422405],[-60.84656470400994,-23.8807125790383],[-60.02896603050399,-24.032796319273245],[-58.80712846539495,-24.771459242453275],[-57.77721716981796,-25.16233977630904],[-57.63366004091114,-25.60365650808167],[-58.61817359071972,-27.12371876394712],[-57.60975969097615,-27.395898532828426],[-56.48670162619299,-27.54849903738625],[-55.6958455063982,-27.38783700939082],[-54.78879492859505,-26.621785577096087],[-54.625290696823555,-25.739255466415486],[-54.13004960795441,-25.54763925547725],[-53.62834896504873,-26.124865004177437],[-53.648735317587885,-26.92347258881611],[-54.49072526713553,-27.474756768505767],[-55.1622863429846,-27.88191537853342],[-56.2908996242391,-28.852760512000852],[-57.62513342958291,-30.216294854454244],[-57.87493730328191,-31.016556084926165],[-58.14244035504075,-32.04450367607619],[-58.13264767112142,-33.040566908502015],[-58.34961117209883,-33.263188978815435],[-58.42707414410438,-33.90945444105755],[-58.49544206402654,-34.43148976007011],[-57.225829637263644,-35.28802662530789],[-57.362358771378744,-35.977390232081504],[-56.73748735210546,-36.41312590916658],[-56.78828528504834,-36.901571547189334],[-57.74915686708343,-38.183870538079915],[-59.231857062401865,-38.720220228837206],[-61.23744523786561,-38.928424574541154],[-62.33595699731015,-38.82770720800437],[-62.12576310896293,-39.424104913084875],[-62.330530971919444,-40.17258635840032],[-62.14599443220524,-40.67689666113674],[-62.745802781816984,-41.02876148861209],[-63.77049475773253,-41.166789239263665],[-64.73208980981971,-40.802677097335135],[-65.11803524439159,-41.06431487402888],[-64.97856055363584,-42.05800099056932],[-64.30340796574248,-42.359016208669495],[-63.75594784204235,-42.04368661882451],[-63.45805904809589,-42.563138116222355],[-64.3788038804563,-42.87355844499964],[-65.1818039618397,-43.495380954767796],[-65.32882341171013,-44.501366062193696],[-65.5652689276616,-45.03678557716979],[-66.50996578638936,-45.03962778094584],[-67.29379391139244,-45.5518962542552],[-67.58054643418009,-46.30177296324254],[-66.59706641301726,-47.03392465595381],[-65.64102657740145,-47.236134535511894],[-65.98508826360074,-48.13328907653114],[-67.16617896184766,-48.697337334996945],[-67.81608761256646,-49.86966887797042],[-68.72874508327317,-50.26421843851887],[-69.1385391913478,-50.7325102679478],[-68.81556148952353,-51.771104011594105],[-68.14999487982041,-52.3499834061277],[-68.57154537624135,-52.29944385534626],[-69.49836218939609,-52.14276091263725],[-71.91480383979635,-52.009022305865926],[-72.32940385607404,-51.42595631287241],[-72.30997351753237,-50.677009779666356],[-72.97574683296463,-50.74145029073431],[-73.32805091011448,-50.37878508890987],[-73.41543575712004,-49.31843637471296],[-72.64824744331494,-48.87861825947679],[-72.33116085477195,-48.244238376661826],[-72.44735531278027,-47.73853281025353],[-71.91725847033021,-46.8848381487918],[-71.55200944689125,-45.56073292417713],[-71.65931555854533,-44.97368865334144],[-71.22277889675973,-44.784242852559416],[-71.32980078803621,-44.40752166115169],[-71.79362260607195,-44.20717213315611],[-71.46405615913051,-43.78761117937833],[-71.91542395698391,-43.40856454851742],[-72.14889807807853,-42.25488819760139],[-71.74680375841547,-42.051386407235995],[-71.91573401557756,-40.83233936947073],[-71.68076127794646,-39.80816415787807],[-71.41351660834904,-38.916022230791114],[-70.81466427273472,-38.55299529394074],[-71.11862504747543,-37.5768274879472],[-71.1218806627098,-36.65812387466234],[-70.36476925320167,-36.005088799789945],[-70.38804948594908,-35.16968759535944],[-69.81730912950147,-34.193571465798286],[-69.81477698431921,-33.27388600029985],[-70.07439938015364,-33.09120981214803],[-70.53506893581945,-31.365010267870286],[-69.91900834825192,-30.336339206668313],[-70.01355038112987,-29.36792286551855],[-69.65613033718314,-28.459141127233693],[-69.00123491074828,-27.52121388113613],[-68.2955415513704,-26.89933969493579],[-68.59479977077268,-26.506908868111267],[-68.38600114609736,-26.185016371365233],[-68.41765296087613,-24.518554782816878],[-67.32844295924414,-24.02530323659091],[-66.98523393417764,-22.98634856536283],[-67.10667355006362,-22.7359245744764],[-66.27333940292485,-21.832310479420684],[-64.96489213729458,-22.075861504812348]]]]}},{"type":"Feature","properties":{"scalerank":1,"featurecla":"Admin-0 country","labelrank":3,"sovereignt":"Denmark","sov_a3":"DN1","adm0_dif":1,"level":2,"type":"Country","admin":"Greenland","adm0_a3":"GRL","geou_dif":0,"geounit":"Greenland","gu_a3":"GRL","su_dif":0,"subunit":"Greenland","su_a3":"GRL","brk_diff":0,"name":"Greenland","name_long":"Greenland","brk_a3":"GRL","brk_name":"Greenland","brk_group":null,"abbrev":"Grlnd.","postal":"GL","formal_en":"Greenland","formal_fr":null,"note_adm0":"Den.","note_brk":null,"name_sort":"Greenland","name_alt":null,"mapcolor7":4,"mapcolor8":1,"mapcolor9":3,"mapcolor13":12,"pop_est":57600,"gdp_md_est":1100,"pop_year":-99,"lastcensus":2010,"gdp_year":-99,"economy":"2. Developed region: nonG7","income_grp":"2. High income: nonOECD","wikipedia":-99,"fips_10":null,"iso_a2":"GL","iso_a3":"GRL","iso_n3":"304","un_a3":"304","wb_a2":"GL","wb_a3":"GRL","woe_id":-99,"adm0_a3_is":"GRL","adm0_a3_us":"GRL","adm0_a3_un":-99,"adm0_a3_wb":-99,"continent":"North America","region_un":"Americas","subregion":"Northern America","region_wb":"Europe & Central Asia","name_len":9,"long_len":9,"abbrev_len":6,"tiny":-99,"homepart":-99,"filename":"GRL.geojson"},"geometry":{"type":"Polygon","coordinates":[[[-46.76379,82.62796],[-43.40644,83.22516],[-39.89753,83.18018],[-38.62214,83.54905],[-35.08787,83.64513],[-27.10046,83.51966],[-20.84539,82.72669],[-22.69182,82.34165],[-26.51753,82.29765],[-31.9,82.2],[-31.39646,82.02154],[-27.85666,82.13178],[-24.84448,81.78697],[-22.90328,82.09317],[-22.07175,81.73449],[-23.16961,81.15271],[-20.62363,81.52462],[-15.76818,81.91245],[-12.77018,81.71885],[-12.20855,81.29154],[-16.28533,80.58004],[-16.85,80.35],[-20.04624,80.17708],[-17.73035,80.12912],[-18.9,79.4],[-19.70499,78.75128],[-19.67353,77.63859],[-18.47285,76.98565],[-20.03503,76.94434],[-21.67944,76.62795],[-19.83407,76.09808],[-19.59896,75.24838],[-20.66818,75.15585],[-19.37281,74.29561],[-21.59422,74.22382],[-20.43454,73.81713],[-20.76234,73.46436],[-22.17221,73.30955],[-23.56593,73.30663],[-22.31311,72.62928],[-22.29954,72.18409],[-24.27834,72.59788],[-24.79296,72.3302],[-23.44296,72.08016],[-22.13281,71.46898],[-21.75356,70.66369],[-23.53603,70.471],[-24.30702,70.85649],[-25.54341,71.43094],[-25.20135,70.75226],[-26.36276,70.22646],[-23.72742,70.18401],[-22.34902,70.12946],[-25.02927,69.2588],[-27.74737,68.47046],[-30.67371,68.12503],[-31.77665,68.12078],[-32.81105,67.73547],[-34.20196,66.67974],[-36.35284,65.9789],[-37.04378,65.93768],[-38.37505,65.69213],[-39.81222,65.45848],[-40.66899,64.83997],[-40.68281,64.13902],[-41.1887,63.48246],[-42.81938,62.68233],[-42.41666,61.90093],[-42.86619,61.07404],[-43.3784,60.09772],[-44.7875,60.03676],[-46.26364,60.85328],[-48.26294,60.85843],[-49.23308,61.40681],[-49.90039,62.38336],[-51.63325,63.62691],[-52.14014,64.27842],[-52.27659,65.1767],[-53.66166,66.09957],[-53.30161,66.8365],[-53.96911,67.18899],[-52.9804,68.35759],[-51.47536,68.72958],[-51.08041,69.14781],[-50.87122,69.9291],[-52.013585,69.574925],[-52.55792,69.42616],[-53.45629,69.283625],[-54.68336,69.61003],[-54.75001,70.28932],[-54.35884,70.821315],[-53.431315,70.835755],[-51.39014,70.56978],[-53.10937,71.20485],[-54.00422,71.54719],[-55,71.40653696727257],[-55.83468,71.65444],[-54.71819,72.58625],[-55.32634,72.95861],[-56.12003,73.64977],[-57.32363,74.71026],[-58.59679,75.09861],[-58.58516,75.51727],[-61.26861,76.10238],[-63.39165,76.1752],[-66.06427,76.13486],[-68.50438,76.06141],[-69.66485,76.37975],[-71.40257,77.00857],[-68.77671,77.32312],[-66.76397,77.37595],[-71.04293,77.63595],[-73.297,78.04419],[-73.15938,78.43271],[-69.37345,78.91388],[-65.7107,79.39436],[-65.3239,79.75814],[-68.02298,80.11721],[-67.15129,80.51582],[-63.68925,81.21396],[-62.23444,81.3211],[-62.65116,81.77042],[-60.28249,82.03363],[-57.20744,82.19074],[-54.13442,82.19962],[-53.04328,81.88833],[-50.39061,82.43883],[-48.00386,82.06481],[-46.59984,81.985945],[-44.523,81.6607],[-46.9007,82.19979],[-46.76379,82.62796]]]}},{"type":"Feature","properties":{"scalerank":1,"featurecla":"Admin-0 country","labelrank":3,"sovereignt":"Bolivia","sov_a3":"BOL","adm0_dif":0,"level":2,"type":"Sovereign country","admin":"Bolivia","adm0_a3":"BOL","geou_dif":0,"geounit":"Bolivia","gu_a3":"BOL","su_dif":0,"subunit":"Bolivia","su_a3":"BOL","brk_diff":0,"name":"Bolivia","name_long":"Bolivia","brk_a3":"BOL","brk_name":"Bolivia","brk_group":null,"abbrev":"Bolivia","postal":"BO","formal_en":"Plurinational State of Bolivia","formal_fr":null,"note_adm0":null,"note_brk":null,"name_sort":"Bolivia","name_alt":null,"mapcolor7":1,"mapcolor8":5,"mapcolor9":2,"mapcolor13":3,"pop_est":9775246,"gdp_md_est":43270,"pop_year":-99,"lastcensus":2001,"gdp_year":-99,"economy":"5. Emerging region: G20","income_grp":"4. Lower middle income","wikipedia":-99,"fips_10":null,"iso_a2":"BO","iso_a3":"BOL","iso_n3":"068","un_a3":"068","wb_a2":"BO","wb_a3":"BOL","woe_id":-99,"adm0_a3_is":"BOL","adm0_a3_us":"BOL","adm0_a3_un":-99,"adm0_a3_wb":-99,"continent":"South America","region_un":"Americas","subregion":"South America","region_wb":"Latin America & Caribbean","name_len":7,"long_len":7,"abbrev_len":7,"tiny":-99,"homepart":1,"filename":"BOL.geojson"},"geometry":{"type":"Polygon","coordinates":[[[-62.846468471921554,-22.03498544686945],[-63.98683814152247,-21.99364430103595],[-64.37702104354226,-22.79809132252354],[-64.9648921372946,-22.075861504812327],[-66.27333940292485,-21.83231047942072],[-67.1066735500636,-22.735924574476414],[-67.82817989772273,-22.872918796482175],[-68.21991309271128,-21.494346612231865],[-68.75716712103375,-20.372657972904463],[-68.44222510443092,-19.40506845467143],[-68.96681840684187,-18.981683444904107],[-69.10024695501949,-18.260125420812674],[-69.59042375352405,-17.580011895419332],[-68.9596353827533,-16.50069793057127],[-69.38976416693471,-15.660129082911654],[-69.16034664577495,-15.323973890853019],[-69.33953467474701,-14.953195489158832],[-68.9488866848366,-14.453639418193283],[-68.92922380234954,-13.602683607643007],[-68.88007951523997,-12.899729099176653],[-68.66507971868961,-12.561300144097173],[-69.52967810736496,-10.951734307502194],[-68.78615759954948,-11.03638030359628],[-68.27125362819326,-11.01452117273682],[-68.04819230820539,-10.712059014532485],[-67.17380123561074,-10.30681243249961],[-66.64690833196279,-9.931331475466862],[-65.33843522811642,-9.76198780684639],[-65.44483700220539,-10.511451104375432],[-65.32189876978302,-10.895872084194679],[-65.40228146021303,-11.566270440317153],[-64.3163529120316,-12.461978041232191],[-63.19649878605057,-12.627032565972433],[-62.80306026879638,-13.000653171442686],[-62.127080857986385,-13.198780612849724],[-61.71320431176078,-13.489202162330052],[-61.08412126325565,-13.479383640194598],[-60.503304002511136,-13.775954685117659],[-60.45919816755003,-14.354007256734555],[-60.26432634137736,-14.64597909918364],[-60.251148851142936,-15.077218926659322],[-60.542965664295146,-15.093910414289596],[-60.158389655179036,-16.258283786690082],[-58.24121985536669,-16.299573256091293],[-58.38805843772404,-16.877109063385276],[-58.28080400250226,-17.271710300366017],[-57.734558274961,-17.55246835700777],[-57.498371141170985,-18.174187513911292],[-57.67600887717431,-18.961839694904025],[-57.949997321185826,-19.40000416430682],[-57.85380164247451,-19.969995212486186],[-58.166392381408045,-20.176700941653678],[-58.183471442280506,-19.868399346600363],[-59.115042487206104,-19.356906019775398],[-60.04356462262649,-19.342746677327426],[-61.786326463453776,-19.633736667562964],[-62.26596126977079,-20.513734633061276],[-62.29117936872922,-21.05163461678739],[-62.685057135657885,-22.249029229422387],[-62.846468471921554,-22.03498544686945]]]}},{"type":"Feature","properties":{"scalerank":1,"featurecla":"Admin-0 country","labelrank":2,"sovereignt":"Brazil","sov_a3":"BRA","adm0_dif":0,"level":2,"type":"Sovereign country","admin":"Brazil","adm0_a3":"BRA","geou_dif":0,"geounit":"Brazil","gu_a3":"BRA","su_dif":0,"subunit":"Brazil","su_a3":"BRA","brk_diff":0,"name":"Brazil","name_long":"Brazil","brk_a3":"BRA","brk_name":"Brazil","brk_group":null,"abbrev":"Brazil","postal":"BR","formal_en":"Federative Republic of Brazil","formal_fr":null,"note_adm0":null,"note_brk":null,"name_sort":"Brazil","name_alt":null,"mapcolor7":5,"mapcolor8":6,"mapcolor9":5,"mapcolor13":7,"pop_est":198739269,"gdp_md_est":1993000,"pop_year":-99,"lastcensus":2010,"gdp_year":-99,"economy":"3. Emerging region: BRIC","income_grp":"3. Upper middle income","wikipedia":-99,"fips_10":null,"iso_a2":"BR","iso_a3":"BRA","iso_n3":"076","un_a3":"076","wb_a2":"BR","wb_a3":"BRA","woe_id":-99,"adm0_a3_is":"BRA","adm0_a3_us":"BRA","adm0_a3_un":-99,"adm0_a3_wb":-99,"continent":"South America","region_un":"Americas","subregion":"South America","region_wb":"Latin America & Caribbean","name_len":6,"long_len":6,"abbrev_len":6,"tiny":-99,"homepart":1,"filename":"BRA.geojson"},"geometry":{"type":"Polygon","coordinates":[[[-57.62513342958296,-30.216294854454258],[-56.29089962423908,-28.85276051200089],[-55.16228634298457,-27.881915378533463],[-54.490725267135524,-27.47475676850579],[-53.648735317587885,-26.923472588816086],[-53.62834896504874,-26.124865004177472],[-54.13004960795439,-25.547639255477254],[-54.625290696823576,-25.73925546641551],[-54.42894609233059,-25.162184747012166],[-54.29347632507745,-24.570799655863965],[-54.29295956075451,-24.02101409271073],[-54.652834235235126,-23.83957813893396],[-55.02790178080954,-24.001273695575225],[-55.40074723979542,-23.956935316668805],[-55.517639329639636,-23.571997572526634],[-55.610682745981144,-22.655619398694842],[-55.79795813660691,-22.356929620047822],[-56.47331743022939,-22.086300144135283],[-56.8815095689029,-22.28215382252148],[-57.937155727761294,-22.090175876557172],[-57.87067399761779,-20.73268767668195],[-58.166392381408045,-20.176700941653678],[-57.85380164247451,-19.969995212486186],[-57.949997321185826,-19.40000416430682],[-57.67600887717431,-18.961839694904025],[-57.498371141170985,-18.174187513911292],[-57.734558274961,-17.55246835700777],[-58.28080400250226,-17.271710300366017],[-58.38805843772404,-16.877109063385276],[-58.24121985536669,-16.299573256091293],[-60.158389655179036,-16.258283786690082],[-60.542965664295146,-15.093910414289596],[-60.251148851142936,-15.077218926659322],[-60.26432634137736,-14.64597909918364],[-60.45919816755003,-14.354007256734555],[-60.503304002511136,-13.775954685117659],[-61.08412126325565,-13.479383640194598],[-61.71320431176078,-13.489202162330052],[-62.127080857986385,-13.198780612849724],[-62.80306026879638,-13.000653171442686],[-63.19649878605057,-12.627032565972433],[-64.3163529120316,-12.461978041232191],[-65.40228146021303,-11.566270440317153],[-65.32189876978302,-10.895872084194679],[-65.44483700220539,-10.511451104375432],[-65.33843522811642,-9.76198780684639],[-66.64690833196279,-9.931331475466862],[-67.17380123561074,-10.30681243249961],[-68.04819230820539,-10.712059014532485],[-68.27125362819326,-11.01452117273682],[-68.78615759954948,-11.03638030359628],[-69.52967810736496,-10.951734307502194],[-70.0937522040469,-11.123971856331012],[-70.54868567572841,-11.009146823778465],[-70.48189388699117,-9.490118096558845],[-71.30241227892154,-10.079436130415374],[-72.18489071316984,-10.053597914269432],[-72.56303300646564,-9.520193780152717],[-73.22671342639016,-9.462212823121234],[-73.01538265653254,-9.03283334720806],[-73.57105933296707,-8.424446709835834],[-73.98723548042966,-7.523829847853064],[-73.7234014553635,-7.340998630404414],[-73.72448666044164,-6.91859547285064],[-73.1200274319236,-6.629930922068239],[-73.21971126981461,-6.089188734566078],[-72.96450720894119,-5.741251315944893],[-72.89192765978726,-5.274561455916981],[-71.74840572781655,-4.593982842633011],[-70.92884334988358,-4.401591485210368],[-70.7947688463023,-4.251264743673303],[-69.89363521999663,-4.298186944194327],[-69.44410193548961,-1.556287123219818],[-69.42048580593223,-1.122618503426409],[-69.5770653957766,-0.549991957200163],[-70.02065589057005,-0.185156345219539],[-70.0155657619893,0.541414292804205],[-69.45239600287246,0.706158758950693],[-69.25243404811906,0.602650865070075],[-69.21863766140018,0.985676581217433],[-69.80459672715773,1.089081122233466],[-69.81697323269162,1.714805202639624],[-67.86856502955884,1.692455145673392],[-67.53781002467468,2.03716278727633],[-67.25999752467358,1.719998684084956],[-67.0650481838525,1.130112209473225],[-66.87632585312258,1.253360500489336],[-66.32576514348496,0.724452215982012],[-65.54826738143757,0.78925446207603],[-65.35471330428837,1.0952822941085],[-64.61101192895985,1.328730576987042],[-64.19930579289051,1.49285492594602],[-64.08308549666609,1.91636912679408],[-63.36878801131166,2.200899562993129],[-63.42286739770512,2.411067613124174],[-64.26999915226578,2.497005520025567],[-64.40882788761792,3.126786200366624],[-64.36849443221409,3.797210394705246],[-64.81606401229402,4.056445217297423],[-64.62865943058755,4.14848094320925],[-63.88834286157416,4.020530096854571],[-63.0931975978991,3.770571193858785],[-62.804533047116706,4.006965033377952],[-62.08542965355914,4.162123521334308],[-60.96689327660153,4.536467596856639],[-60.60117916527194,4.91809804933213],[-60.73357418480372,5.200277207861901],[-60.21368343773133,5.244486395687602],[-59.98095862490488,5.014061184098139],[-60.11100236676737,4.574966538914083],[-59.767405768458715,4.423502915866607],[-59.53803992373123,3.958802598481938],[-59.81541317405786,3.606498521332085],[-59.97452490908456,2.755232652188056],[-59.71854570172674,2.24963043864436],[-59.64604366722126,1.786893825686789],[-59.03086157900265,1.317697658692722],[-58.5400129868783,1.268088283692521],[-58.42947709820596,1.463941962078721],[-58.11344987652502,1.507195135907025],[-57.66097103537737,1.682584947105639],[-57.335822923396904,1.94853770589576],[-56.78270423036083,1.863710842288654],[-56.539385748914555,1.899522609866921],[-55.99569800477175,1.817667141116601],[-55.905600145070885,2.02199575439866],[-56.0733418442903,2.220794989425499],[-55.973322109589375,2.510363877773017],[-55.569755011606,2.421506252447131],[-55.09758744975514,2.523748073736613],[-54.52475419779971,2.311848863123785],[-54.08806250671724,2.105556545414629],[-53.77852067728892,2.376702785650082],[-53.55483924011354,2.334896551925951],[-53.4184651352953,2.053389187015981],[-52.939657151894956,2.124857692875636],[-52.55642473001842,2.504705308437053],[-52.249337531123956,3.241094468596245],[-51.65779741067888,4.156232408053029],[-51.31714636901086,4.203490505383954],[-51.069771287629656,3.650397650564031],[-50.508875291533656,1.901563828942457],[-49.97407589374506,1.736483465986069],[-49.947100796088705,1.046189683431223],[-50.699251268096916,0.222984117021682],[-50.38821082213214,-0.078444512536819],[-48.62056677915631,-0.235489190271821],[-48.58449662941659,-1.237805271005001],[-47.824956427590635,-0.5816179337628],[-46.566583624851226,-0.941027520352776],[-44.905703090990414,-1.551739597178134],[-44.417619187993665,-2.137750339367976],[-44.58158850765578,-2.691308282078524],[-43.418791266440195,-2.383110039889793],[-41.47265682632825,-2.912018324397116],[-39.97866533055404,-2.873054294449041],[-38.50038347019657,-3.700652357603395],[-37.2232521225352,-4.820945733258917],[-36.45293738457639,-5.109403578312153],[-35.59779578301047,-5.149504489770648],[-35.23538896334756,-5.464937432480247],[-34.89602983248683,-6.738193047719711],[-34.729993455533034,-7.343220716992966],[-35.12821204277422,-8.996401462442286],[-35.636966518687714,-9.649281508017815],[-37.046518724097,-11.040721123908801],[-37.68361161960736,-12.171194756725823],[-38.42387651218844,-13.038118584854288],[-38.673887091616514,-13.057652276260619],[-38.953275722802545,-13.793369642800023],[-38.88229814304965,-15.667053724838768],[-39.16109249526431,-17.208406670808472],[-39.2673392400564,-17.867746270420483],[-39.58352149103423,-18.262295830968938],[-39.76082333022764,-19.59911345792741],[-40.77474077001034,-20.904511814052423],[-40.94475623225061,-21.937316989837807],[-41.754164191238225,-22.370675551037458],[-41.98828426773655,-22.970070489190892],[-43.07470374202475,-22.96769337330547],[-44.64781185563781,-23.351959323827842],[-45.35213578955991,-23.796841729428582],[-46.47209326840554,-24.08896860117454],[-47.64897233742066,-24.885199069927722],[-48.4954581365777,-25.877024834905654],[-48.64100480812774,-26.623697605090932],[-48.47473588722865,-27.17591196056189],[-48.661520351747626,-28.186134535435716],[-48.88845740415739,-28.674115085567884],[-49.587329474472675,-29.224469089476337],[-50.696874152211485,-30.984465020472957],[-51.576226162306156,-31.77769825615321],[-52.256081305538046,-32.24536996839466],[-52.712099982297694,-33.19657805759118],[-53.373661668498244,-33.768377780900764],[-53.6505439927181,-33.20200408298183],[-53.209588995971544,-32.727666110974724],[-53.787951626182185,-32.047242526987624],[-54.57245154480512,-31.494511407193748],[-55.601510179249345,-30.853878676071393],[-55.97324459494093,-30.883075860316303],[-56.97602576356473,-30.109686374636127],[-57.62513342958296,-30.216294854454258]]]}},{"type":"Feature","properties":{"scalerank":1,"featurecla":"Admin-0 country","labelrank":2,"sovereignt":"Chile","sov_a3":"CHL","adm0_dif":0,"level":2,"type":"Sovereign country","admin":"Chile","adm0_a3":"CHL","geou_dif":0,"geounit":"Chile","gu_a3":"CHL","su_dif":0,"subunit":"Chile","su_a3":"CHL","brk_diff":0,"name":"Chile","name_long":"Chile","brk_a3":"CHL","brk_name":"Chile","brk_group":null,"abbrev":"Chile","postal":"CL","formal_en":"Republic of Chile","formal_fr":null,"note_adm0":null,"note_brk":null,"name_sort":"Chile","name_alt":null,"mapcolor7":5,"mapcolor8":1,"mapcolor9":5,"mapcolor13":9,"pop_est":16601707,"gdp_md_est":244500,"pop_year":-99,"lastcensus":2002,"gdp_year":-99,"economy":"5. Emerging region: G20","income_grp":"3. Upper middle income","wikipedia":-99,"fips_10":null,"iso_a2":"CL","iso_a3":"CHL","iso_n3":"152","un_a3":"152","wb_a2":"CL","wb_a3":"CHL","woe_id":-99,"adm0_a3_is":"CHL","adm0_a3_us":"CHL","adm0_a3_un":-99,"adm0_a3_wb":-99,"continent":"South America","region_un":"Americas","subregion":"South America","region_wb":"Latin America & Caribbean","name_len":5,"long_len":5,"abbrev_len":5,"tiny":-99,"homepart":1,"filename":"CHL.geojson"},"geometry":{"type":"MultiPolygon","coordinates":[[[[-68.63401022758316,-52.63637045887437],[-68.6333499999999,-54.8695],[-67.56244,-54.87001],[-66.95992,-54.89681],[-67.29102999999989,-55.30124],[-68.14862999999986,-55.61183],[-68.63999081081181,-55.58001799908689],[-69.2321,-55.49906],[-69.95809,-55.19843],[-71.00568,-55.05383],[-72.2639,-54.49514],[-73.2852,-53.95751999999989],[-74.66253,-52.83749],[-73.8381,-53.04743],[-72.43418,-53.7154],[-71.10773,-54.07433],[-70.59177999999979,-53.61583],[-70.26748,-52.93123],[-69.34564999999989,-52.5183],[-68.63401022758316,-52.63637045887437]]],[[[-68.21991309271124,-21.494346612231837],[-67.82817989772266,-22.87291879648218],[-67.10667355006362,-22.7359245744764],[-66.98523393417764,-22.98634856536283],[-67.32844295924414,-24.02530323659091],[-68.41765296087613,-24.518554782816878],[-68.38600114609736,-26.185016371365233],[-68.59479977077268,-26.506908868111267],[-68.2955415513704,-26.89933969493579],[-69.00123491074828,-27.52121388113613],[-69.65613033718314,-28.459141127233693],[-70.01355038112987,-29.36792286551855],[-69.91900834825192,-30.336339206668313],[-70.53506893581945,-31.365010267870286],[-70.07439938015364,-33.09120981214803],[-69.81477698431921,-33.27388600029985],[-69.81730912950147,-34.193571465798286],[-70.38804948594908,-35.16968759535944],[-70.36476925320167,-36.005088799789945],[-71.1218806627098,-36.65812387466234],[-71.11862504747543,-37.5768274879472],[-70.81466427273472,-38.55299529394074],[-71.41351660834904,-38.916022230791114],[-71.68076127794646,-39.80816415787807],[-71.91573401557756,-40.83233936947073],[-71.74680375841547,-42.051386407235995],[-72.14889807807853,-42.25488819760139],[-71.91542395698391,-43.40856454851742],[-71.46405615913051,-43.78761117937833],[-71.79362260607195,-44.20717213315611],[-71.32980078803621,-44.40752166115169],[-71.22277889675973,-44.784242852559416],[-71.65931555854533,-44.97368865334144],[-71.55200944689125,-45.56073292417713],[-71.91725847033021,-46.8848381487918],[-72.44735531278027,-47.73853281025353],[-72.33116085477195,-48.244238376661826],[-72.64824744331494,-48.87861825947679],[-73.41543575712004,-49.31843637471296],[-73.32805091011448,-50.37878508890987],[-72.97574683296463,-50.74145029073431],[-72.30997351753237,-50.677009779666356],[-72.32940385607404,-51.42595631287241],[-71.91480383979635,-52.009022305865926],[-69.49836218939609,-52.14276091263725],[-68.57154537624135,-52.29944385534626],[-69.46128434922664,-52.29195077266393],[-69.94277950710614,-52.53793059037325],[-70.84510169135453,-52.899200528525725],[-71.00633216010525,-53.83325204220135],[-71.42979468452094,-53.85645476030039],[-72.55794287788486,-53.531410001184454],[-73.70275672066288,-52.83506926860725],[-73.70275672066288,-52.8350700760515],[-74.94676347522515,-52.26275358841903],[-75.2600260077785,-51.629354750373224],[-74.9766324530898,-51.04339568461569],[-75.4797541978835,-50.37837167745156],[-75.60801510283196,-48.6737728818718],[-75.18276974150213,-47.71191944762316],[-74.1265809801047,-46.9392534319951],[-75.64439531116545,-46.64764332457203],[-74.69215369332306,-45.76397633238098],[-74.35170935738427,-44.103044122087894],[-73.2403560045152,-44.454960625995625],[-72.71780392117978,-42.383355808278985],[-73.38889990913825,-42.11753224056957],[-73.70133561877486,-43.365776462579745],[-74.33194312203258,-43.22495818458441],[-74.01795711942717,-41.794812920906836],[-73.67709937202997,-39.942212823243125],[-73.21759253609068,-39.258688653318515],[-73.50555945503706,-38.28288258235107],[-73.58806087919109,-37.156284681956016],[-73.1667170884993,-37.12378020604435],[-72.55313696968173,-35.508840020491036],[-71.86173214383257,-33.90909270603153],[-71.43845048692992,-32.41889942803083],[-71.66872066922244,-30.92064462659252],[-71.37008256700773,-30.095682061485004],[-71.48989437527646,-28.861442152625912],[-70.90512386746158,-27.6403797340012],[-70.72495398627598,-25.70592416758721],[-70.40396582709505,-23.628996677344542],[-70.09124589708067,-21.393319187101223],[-70.16441972520599,-19.756468194256186],[-70.37257239447774,-18.347975355708883],[-69.85844356960581,-18.092693780187034],[-69.590423753524,-17.58001189541929],[-69.10024695501943,-18.260125420812656],[-68.96681840684184,-18.981683444904093],[-68.44222510443095,-19.405068454671422],[-68.75716712103372,-20.372657972904477],[-68.21991309271124,-21.494346612231837]]]]}},{"type":"Feature","properties":{"scalerank":1,"featurecla":"Admin-0 country","labelrank":2,"sovereignt":"Colombia","sov_a3":"COL","adm0_dif":0,"level":2,"type":"Sovereign country","admin":"Colombia","adm0_a3":"COL","geou_dif":0,"geounit":"Colombia","gu_a3":"COL","su_dif":0,"subunit":"Colombia","su_a3":"COL","brk_diff":0,"name":"Colombia","name_long":"Colombia","brk_a3":"COL","brk_name":"Colombia","brk_group":null,"abbrev":"Col.","postal":"CO","formal_en":"Republic of Colombia","formal_fr":null,"note_adm0":null,"note_brk":null,"name_sort":"Colombia","name_alt":null,"mapcolor7":2,"mapcolor8":1,"mapcolor9":3,"mapcolor13":1,"pop_est":45644023,"gdp_md_est":395400,"pop_year":-99,"lastcensus":2006,"gdp_year":-99,"economy":"6. Developing region","income_grp":"3. Upper middle income","wikipedia":-99,"fips_10":null,"iso_a2":"CO","iso_a3":"COL","iso_n3":"170","un_a3":"170","wb_a2":"CO","wb_a3":"COL","woe_id":-99,"adm0_a3_is":"COL","adm0_a3_us":"COL","adm0_a3_un":-99,"adm0_a3_wb":-99,"continent":"South America","region_un":"Americas","subregion":"South America","region_wb":"Latin America & Caribbean","name_len":8,"long_len":8,"abbrev_len":4,"tiny":-99,"homepart":1,"filename":"COL.geojson"},"geometry":{"type":"Polygon","coordinates":[[[-75.37322323271385,-0.15203175212045],[-75.80146582711659,0.084801337073202],[-76.29231441924097,0.416047268064119],[-76.57637976754938,0.256935533037435],[-77.4249843004304,0.395686753741117],[-77.66861284047044,0.825893052570961],[-77.85506140817952,0.809925034992773],[-78.85525875518871,1.380923773601822],[-78.99093522817103,1.691369940595251],[-78.61783138702371,1.766404120283056],[-78.66211808949785,2.267355454920477],[-78.42761043975732,2.629555568854215],[-77.93154252797149,2.696605739752926],[-77.51043128122501,3.325016994638247],[-77.12768978545526,3.849636135265357],[-77.49627193877703,4.087606105969428],[-77.3076012844794,4.667984117039452],[-77.53322058786573,5.582811997902496],[-77.31881507028675,5.84535411216136],[-77.47666073272228,6.691116441266303],[-77.88157141794525,7.223771267114785],[-77.75341386586139,7.709839789252142],[-77.43110795765699,7.638061224798735],[-77.24256649444008,7.935278225125444],[-77.47472286651133,8.524286200388218],[-77.35336076527385,8.67050466555807],[-76.83667395700357,8.638749497914716],[-76.08638383655786,9.336820583529487],[-75.67460018584005,9.443248195834599],[-75.66470414905618,9.774003200718738],[-75.48042599150335,10.618990383339309],[-74.90689510771197,11.083044745320322],[-74.27675269234489,11.102035834187587],[-74.1972226630477,11.310472723836865],[-73.41476396350029,11.22701528568548],[-72.62783525255963,11.731971543825523],[-72.23819495307892,11.955549628136326],[-71.75409013536864,12.437303168177309],[-71.3998223537917,12.376040757695293],[-71.13746110704588,12.112981879113505],[-71.3315836249503,11.776284084515808],[-71.97392167833829,11.60867157637712],[-72.22757544624294,11.10870209395324],[-72.61465776232521,10.821975409381778],[-72.9052860175347,10.450344346554772],[-73.02760413276957,9.736770331252444],[-73.30495154488005,9.151999823437606],[-72.7887298245004,9.085027167187334],[-72.6604947577681,8.625287787302682],[-72.43986223009796,8.405275376820029],[-72.36090064155596,8.002638454617895],[-72.47967892117885,7.632506008327354],[-72.44448727078807,7.423784898300481],[-72.19835242378188,7.340430813013682],[-71.96017574734864,6.991614895043538],[-70.67423356798152,7.087784735538719],[-70.09331295437242,6.96037649172311],[-69.38947994655712,6.099860541198836],[-68.98531856960236,6.206804917826858],[-68.26505245631823,6.153268133972475],[-67.69508724635502,6.267318020040647],[-67.34143958196557,6.095468044454023],[-67.52153194850275,5.556870428891969],[-67.74469662135522,5.221128648291668],[-67.82301225449355,4.503937282728899],[-67.62183590358127,3.839481716319994],[-67.33756384954368,3.542342230641722],[-67.30317318385345,3.31845408773718],[-67.8099381171237,2.820655015469569],[-67.44709204778631,2.600280869960869],[-67.18129431829307,2.250638129074062],[-66.87632585312258,1.253360500489336],[-67.0650481838525,1.130112209473225],[-67.25999752467358,1.719998684084956],[-67.53781002467468,2.03716278727633],[-67.86856502955884,1.692455145673392],[-69.81697323269162,1.714805202639624],[-69.80459672715773,1.089081122233466],[-69.21863766140018,0.985676581217433],[-69.25243404811906,0.602650865070075],[-69.45239600287246,0.706158758950693],[-70.0155657619893,0.541414292804205],[-70.02065589057005,-0.185156345219539],[-69.5770653957766,-0.549991957200163],[-69.42048580593223,-1.122618503426409],[-69.44410193548961,-1.556287123219818],[-69.89363521999663,-4.298186944194327],[-70.39404395209499,-3.766591485207825],[-70.69268205430971,-3.742872002785859],[-70.04770850287485,-2.725156345229699],[-70.81347571479196,-2.256864515800743],[-71.41364579942979,-2.342802422702128],[-71.7747607082854,-2.169789727388938],[-72.32578650581365,-2.434218031426454],[-73.07039221870724,-2.308954359550953],[-73.6595035468346,-1.260491224781134],[-74.12239518908906,-1.002832533373848],[-74.44160051135597,-0.530820000819887],[-75.10662451852008,-0.05720549886486],[-75.37322323271385,-0.15203175212045]]]}},{"type":"Feature","properties":{"scalerank":1,"featurecla":"Admin-0 country","labelrank":3,"sovereignt":"Ecuador","sov_a3":"ECU","adm0_dif":0,"level":2,"type":"Sovereign country","admin":"Ecuador","adm0_a3":"ECU","geou_dif":0,"geounit":"Ecuador","gu_a3":"ECU","su_dif":0,"subunit":"Ecuador","su_a3":"ECU","brk_diff":0,"name":"Ecuador","name_long":"Ecuador","brk_a3":"ECU","brk_name":"Ecuador","brk_group":null,"abbrev":"Ecu.","postal":"EC","formal_en":"Republic of Ecuador","formal_fr":null,"note_adm0":null,"note_brk":null,"name_sort":"Ecuador","name_alt":null,"mapcolor7":1,"mapcolor8":5,"mapcolor9":2,"mapcolor13":12,"pop_est":14573101,"gdp_md_est":107700,"pop_year":-99,"lastcensus":2010,"gdp_year":-99,"economy":"6. Developing region","income_grp":"3. Upper middle income","wikipedia":-99,"fips_10":null,"iso_a2":"EC","iso_a3":"ECU","iso_n3":"218","un_a3":"218","wb_a2":"EC","wb_a3":"ECU","woe_id":-99,"adm0_a3_is":"ECU","adm0_a3_us":"ECU","adm0_a3_un":-99,"adm0_a3_wb":-99,"continent":"South America","region_un":"Americas","subregion":"South America","region_wb":"Latin America & Caribbean","name_len":7,"long_len":7,"abbrev_len":4,"tiny":-99,"homepart":1,"filename":"ECU.geojson"},"geometry":{"type":"Polygon","coordinates":[[[-80.30256059438722,-3.404856459164713],[-79.77029334178093,-2.65751189535964],[-79.98655921092241,-2.220794366061014],[-80.36878394236925,-2.685158786635788],[-80.96776546906436,-2.246942640800704],[-80.76480628123804,-1.965047702648533],[-80.9336590237517,-1.057454522306358],[-80.58337032746127,-0.906662692878683],[-80.39932471385376,-0.283703301600141],[-80.02089820018037,0.360340074053468],[-80.09060970734211,0.768428859862397],[-79.54276201039978,0.982937730305963],[-78.85525875518871,1.380923773601822],[-77.85506140817952,0.809925034992773],[-77.66861284047044,0.825893052570961],[-77.4249843004304,0.395686753741117],[-76.57637976754938,0.256935533037435],[-76.29231441924097,0.416047268064119],[-75.80146582711659,0.084801337073202],[-75.37322323271385,-0.15203175212045],[-75.23372270374193,-0.911416924649529],[-75.54499569365204,-1.56160979574588],[-76.63539425322672,-2.608677666843818],[-77.83790483265861,-3.003020521663103],[-78.45068396677564,-3.873096612161376],[-78.63989722361234,-4.547784112164074],[-79.20528906931771,-4.959128513207389],[-79.62497921417618,-4.454198093283494],[-80.02890804718561,-4.346090996928893],[-80.44224199087216,-4.425724379090674],[-80.46929460317695,-4.059286797708999],[-80.18401485870967,-3.821161797708044],[-80.30256059438722,-3.404856459164713]]]}},{"type":"Feature","properties":{"scalerank":1,"featurecla":"Admin-0 country","labelrank":4,"sovereignt":"Guyana","sov_a3":"GUY","adm0_dif":0,"level":2,"type":"Sovereign country","admin":"Guyana","adm0_a3":"GUY","geou_dif":0,"geounit":"Guyana","gu_a3":"GUY","su_dif":0,"subunit":"Guyana","su_a3":"GUY","brk_diff":0,"name":"Guyana","name_long":"Guyana","brk_a3":"GUY","brk_name":"Guyana","brk_group":null,"abbrev":"Guy.","postal":"GY","formal_en":"Co-operative Republic of Guyana","formal_fr":null,"note_adm0":null,"note_brk":null,"name_sort":"Guyana","name_alt":null,"mapcolor7":3,"mapcolor8":1,"mapcolor9":4,"mapcolor13":8,"pop_est":772298,"gdp_md_est":2966,"pop_year":-99,"lastcensus":2002,"gdp_year":-99,"economy":"6. Developing region","income_grp":"4. Lower middle income","wikipedia":-99,"fips_10":null,"iso_a2":"GY","iso_a3":"GUY","iso_n3":"328","un_a3":"328","wb_a2":"GY","wb_a3":"GUY","woe_id":-99,"adm0_a3_is":"GUY","adm0_a3_us":"GUY","adm0_a3_un":-99,"adm0_a3_wb":-99,"continent":"South America","region_un":"Americas","subregion":"South America","region_wb":"Latin America & Caribbean","name_len":6,"long_len":6,"abbrev_len":4,"tiny":-99,"homepart":1,"filename":"GUY.geojson"},"geometry":{"type":"Polygon","coordinates":[[[-59.758284878159195,8.367034816924047],[-59.101684129458654,7.999201971870492],[-58.482962205628056,7.347691351750697],[-58.45487606467743,6.832787380394463],[-58.07810319683737,6.809093736188643],[-57.542218593970645,6.321268215353356],[-57.14743648947688,5.973149929219161],[-57.307245856339506,5.073566595882227],[-57.91428890647214,4.812626451024414],[-57.86020952007869,4.57680105226045],[-58.04469438336068,4.060863552258382],[-57.60156897645786,3.334654649260685],[-57.2814334784097,3.333491929534119],[-57.150097825739905,2.768926906745406],[-56.539385748914555,1.899522609866921],[-56.78270423036083,1.863710842288654],[-57.335822923396904,1.94853770589576],[-57.66097103537737,1.682584947105639],[-58.11344987652502,1.507195135907025],[-58.42947709820596,1.463941962078721],[-58.5400129868783,1.268088283692521],[-59.03086157900265,1.317697658692722],[-59.64604366722126,1.786893825686789],[-59.71854570172674,2.24963043864436],[-59.97452490908456,2.755232652188056],[-59.81541317405786,3.606498521332085],[-59.53803992373123,3.958802598481938],[-59.767405768458715,4.423502915866607],[-60.11100236676737,4.574966538914083],[-59.98095862490488,5.014061184098139],[-60.21368343773133,5.244486395687602],[-60.73357418480372,5.200277207861901],[-61.410302903881956,5.959068101419618],[-61.13941504580795,6.234296779806144],[-61.15933631045648,6.696077378766319],[-60.54399919294098,6.856584377464883],[-60.29566809756239,7.043911444522919],[-60.637972785063766,7.414999904810855],[-60.55058793805819,7.779602972846178],[-59.758284878159195,8.367034816924047]]]}},{"type":"Feature","properties":{"scalerank":1,"featurecla":"Admin-0 country","labelrank":5,"sovereignt":"United Kingdom","sov_a3":"GB1","adm0_dif":1,"level":2,"type":"Dependency","admin":"Falkland Islands","adm0_a3":"FLK","geou_dif":0,"geounit":"Falkland Islands","gu_a3":"FLK","su_dif":0,"subunit":"Falkland Islands","su_a3":"FLK","brk_diff":1,"name":"Falkland Is.","name_long":"Falkland Islands","brk_a3":"B12","brk_name":"Falkland Is.","brk_group":null,"abbrev":"Flk. Is.","postal":"FK","formal_en":"Falkland Islands","formal_fr":null,"note_adm0":"U.K.","note_brk":"Admin. by U.K.; Claimed by Argentina","name_sort":"Falkland Islands","name_alt":"Islas Malvinas","mapcolor7":6,"mapcolor8":6,"mapcolor9":6,"mapcolor13":3,"pop_est":3140,"gdp_md_est":105.1,"pop_year":-99,"lastcensus":-99,"gdp_year":-99,"economy":"2. Developed region: nonG7","income_grp":"1. High income: OECD","wikipedia":-99,"fips_10":null,"iso_a2":"FK","iso_a3":"FLK","iso_n3":"238","un_a3":"238","wb_a2":"-99","wb_a3":"-99","woe_id":-99,"adm0_a3_is":"FLK","adm0_a3_us":"FLK","adm0_a3_un":-99,"adm0_a3_wb":-99,"continent":"South America","region_un":"Americas","subregion":"South America","region_wb":"Latin America & Caribbean","name_len":12,"long_len":16,"abbrev_len":8,"tiny":-99,"homepart":-99,"filename":"FLK.geojson"},"geometry":{"type":"Polygon","coordinates":[[[-61.2,-51.85],[-60,-51.25],[-59.15,-51.5],[-58.55,-51.1],[-57.75,-51.55],[-58.05,-51.9],[-59.4,-52.2],[-59.85,-51.85],[-60.7,-52.3],[-61.2,-51.85]]]}},{"type":"Feature","properties":{"scalerank":1,"featurecla":"Admin-0 country","labelrank":4,"sovereignt":"Suriname","sov_a3":"SUR","adm0_dif":0,"level":2,"type":"Sovereign country","admin":"Suriname","adm0_a3":"SUR","geou_dif":0,"geounit":"Suriname","gu_a3":"SUR","su_dif":0,"subunit":"Suriname","su_a3":"SUR","brk_diff":0,"name":"Suriname","name_long":"Suriname","brk_a3":"SUR","brk_name":"Suriname","brk_group":null,"abbrev":"Sur.","postal":"SR","formal_en":"Republic of Suriname","formal_fr":null,"note_adm0":null,"note_brk":null,"name_sort":"Suriname","name_alt":null,"mapcolor7":1,"mapcolor8":4,"mapcolor9":7,"mapcolor13":6,"pop_est":481267,"gdp_md_est":4254,"pop_year":-99,"lastcensus":2004,"gdp_year":-99,"economy":"6. Developing region","income_grp":"3. Upper middle income","wikipedia":-99,"fips_10":null,"iso_a2":"SR","iso_a3":"SUR","iso_n3":"740","un_a3":"740","wb_a2":"SR","wb_a3":"SUR","woe_id":-99,"adm0_a3_is":"SUR","adm0_a3_us":"SUR","adm0_a3_un":-99,"adm0_a3_wb":-99,"continent":"South America","region_un":"Americas","subregion":"South America","region_wb":"Latin America & Caribbean","name_len":8,"long_len":8,"abbrev_len":4,"tiny":-99,"homepart":1,"filename":"SUR.geojson"},"geometry":{"type":"Polygon","coordinates":[[[-57.14743648947688,5.973149929219161],[-55.9493184067898,5.772877915872002],[-55.841779751190415,5.953125311706059],[-55.033250291551774,6.025291449401664],[-53.958044603070896,5.756548163267765],[-54.47863298197923,4.896755682795586],[-54.399542202356514,4.212611395683466],[-54.00693050801901,3.620037746592558],[-54.181726040246275,3.189779771330421],[-54.2697051662232,2.732391669115046],[-54.52475419779971,2.311848863123785],[-55.09758744975514,2.523748073736613],[-55.569755011606,2.421506252447131],[-55.973322109589375,2.510363877773017],[-56.0733418442903,2.220794989425499],[-55.905600145070885,2.02199575439866],[-55.99569800477175,1.817667141116601],[-56.539385748914555,1.899522609866921],[-57.150097825739905,2.768926906745406],[-57.2814334784097,3.333491929534119],[-57.60156897645786,3.334654649260685],[-58.04469438336068,4.060863552258382],[-57.86020952007869,4.57680105226045],[-57.91428890647214,4.812626451024414],[-57.307245856339506,5.073566595882227],[-57.14743648947688,5.973149929219161]]]}},{"type":"Feature","properties":{"scalerank":1,"featurecla":"Admin-0 country","labelrank":4,"sovereignt":"Paraguay","sov_a3":"PRY","adm0_dif":0,"level":2,"type":"Sovereign country","admin":"Paraguay","adm0_a3":"PRY","geou_dif":0,"geounit":"Paraguay","gu_a3":"PRY","su_dif":0,"subunit":"Paraguay","su_a3":"PRY","brk_diff":0,"name":"Paraguay","name_long":"Paraguay","brk_a3":"PRY","brk_name":"Paraguay","brk_group":null,"abbrev":"Para.","postal":"PY","formal_en":"Republic of Paraguay","formal_fr":null,"note_adm0":null,"note_brk":null,"name_sort":"Paraguay","name_alt":null,"mapcolor7":6,"mapcolor8":3,"mapcolor9":6,"mapcolor13":2,"pop_est":6995655,"gdp_md_est":28890,"pop_year":-99,"lastcensus":2002,"gdp_year":-99,"economy":"5. Emerging region: G20","income_grp":"4. Lower middle income","wikipedia":-99,"fips_10":null,"iso_a2":"PY","iso_a3":"PRY","iso_n3":"600","un_a3":"600","wb_a2":"PY","wb_a3":"PRY","woe_id":-99,"adm0_a3_is":"PRY","adm0_a3_us":"PRY","adm0_a3_un":-99,"adm0_a3_wb":-99,"continent":"South America","region_un":"Americas","subregion":"South America","region_wb":"Latin America & Caribbean","name_len":8,"long_len":8,"abbrev_len":5,"tiny":-99,"homepart":1,"filename":"PRY.geojson"},"geometry":{"type":"Polygon","coordinates":[[[-62.685057135657885,-22.249029229422387],[-62.29117936872922,-21.05163461678739],[-62.26596126977079,-20.513734633061276],[-61.786326463453776,-19.633736667562964],[-60.04356462262649,-19.342746677327426],[-59.115042487206104,-19.356906019775398],[-58.183471442280506,-19.868399346600363],[-58.166392381408045,-20.176700941653678],[-57.87067399761779,-20.73268767668195],[-57.937155727761294,-22.090175876557172],[-56.8815095689029,-22.28215382252148],[-56.47331743022939,-22.086300144135283],[-55.79795813660691,-22.356929620047822],[-55.610682745981144,-22.655619398694842],[-55.517639329639636,-23.571997572526634],[-55.40074723979542,-23.956935316668805],[-55.02790178080954,-24.001273695575225],[-54.652834235235126,-23.83957813893396],[-54.29295956075451,-24.02101409271073],[-54.29347632507745,-24.570799655863965],[-54.42894609233059,-25.162184747012166],[-54.625290696823576,-25.73925546641551],[-54.78879492859505,-26.621785577096134],[-55.69584550639816,-27.38783700939086],[-56.48670162619299,-27.548499037386293],[-57.60975969097615,-27.395898532828387],[-58.61817359071974,-27.123718763947096],[-57.633660040911124,-25.60365650808164],[-57.77721716981794,-25.16233977630904],[-58.80712846539498,-24.77145924245331],[-60.02896603050402,-24.032796319273274],[-60.84656470400991,-23.880712579038292],[-62.685057135657885,-22.249029229422387]]]}},{"type":"Feature","properties":{"scalerank":1,"featurecla":"Admin-0 country","labelrank":4,"sovereignt":"Uruguay","sov_a3":"URY","adm0_dif":0,"level":2,"type":"Sovereign country","admin":"Uruguay","adm0_a3":"URY","geou_dif":0,"geounit":"Uruguay","gu_a3":"URY","su_dif":0,"subunit":"Uruguay","su_a3":"URY","brk_diff":0,"name":"Uruguay","name_long":"Uruguay","brk_a3":"URY","brk_name":"Uruguay","brk_group":null,"abbrev":"Ury.","postal":"UY","formal_en":"Oriental Republic of Uruguay","formal_fr":null,"note_adm0":null,"note_brk":null,"name_sort":"Uruguay","name_alt":null,"mapcolor7":1,"mapcolor8":2,"mapcolor9":2,"mapcolor13":10,"pop_est":3494382,"gdp_md_est":43160,"pop_year":-99,"lastcensus":2004,"gdp_year":-99,"economy":"5. Emerging region: G20","income_grp":"3. Upper middle income","wikipedia":-99,"fips_10":null,"iso_a2":"UY","iso_a3":"URY","iso_n3":"858","un_a3":"858","wb_a2":"UY","wb_a3":"URY","woe_id":-99,"adm0_a3_is":"URY","adm0_a3_us":"URY","adm0_a3_un":-99,"adm0_a3_wb":-99,"continent":"South America","region_un":"Americas","subregion":"South America","region_wb":"Latin America & Caribbean","name_len":7,"long_len":7,"abbrev_len":4,"tiny":-99,"homepart":1,"filename":"URY.geojson"},"geometry":{"type":"Polygon","coordinates":[[[-57.62513342958296,-30.216294854454258],[-56.97602576356473,-30.109686374636127],[-55.97324459494093,-30.883075860316303],[-55.601510179249345,-30.853878676071393],[-54.57245154480512,-31.494511407193748],[-53.787951626182185,-32.047242526987624],[-53.209588995971544,-32.727666110974724],[-53.6505439927181,-33.20200408298183],[-53.373661668498244,-33.768377780900764],[-53.806425950726535,-34.396814874002224],[-54.93586605489773,-34.952646579733624],[-55.67408972840329,-34.75265878676407],[-56.21529700379607,-34.85983570733742],[-57.139685024633096,-34.430456231424245],[-57.8178606838155,-34.4625472958775],[-58.42707414410439,-33.90945444105757],[-58.34961117209887,-33.26318897881541],[-58.13264767112144,-33.040566908502015],[-58.14244035504076,-32.044503676076154],[-57.87493730328188,-31.016556084926208],[-57.62513342958296,-30.216294854454258]]]}},{"type":"Feature","properties":{"scalerank":1,"featurecla":"Admin-0 country","labelrank":2,"sovereignt":"Peru","sov_a3":"PER","adm0_dif":0,"level":2,"type":"Sovereign country","admin":"Peru","adm0_a3":"PER","geou_dif":0,"geounit":"Peru","gu_a3":"PER","su_dif":0,"subunit":"Peru","su_a3":"PER","brk_diff":0,"name":"Peru","name_long":"Peru","brk_a3":"PER","brk_name":"Peru","brk_group":null,"abbrev":"Peru","postal":"PE","formal_en":"Republic of Peru","formal_fr":null,"note_adm0":null,"note_brk":null,"name_sort":"Peru","name_alt":null,"mapcolor7":4,"mapcolor8":4,"mapcolor9":4,"mapcolor13":11,"pop_est":29546963,"gdp_md_est":247300,"pop_year":-99,"lastcensus":2007,"gdp_year":-99,"economy":"5. Emerging region: G20","income_grp":"3. Upper middle income","wikipedia":-99,"fips_10":null,"iso_a2":"PE","iso_a3":"PER","iso_n3":"604","un_a3":"604","wb_a2":"PE","wb_a3":"PER","woe_id":-99,"adm0_a3_is":"PER","adm0_a3_us":"PER","adm0_a3_un":-99,"adm0_a3_wb":-99,"continent":"South America","region_un":"Americas","subregion":"South America","region_wb":"Latin America & Caribbean","name_len":4,"long_len":4,"abbrev_len":4,"tiny":-99,"homepart":1,"filename":"PER.geojson"},"geometry":{"type":"Polygon","coordinates":[[[-69.59042375352405,-17.580011895419332],[-69.85844356960587,-18.092693780187012],[-70.37257239447771,-18.34797535570887],[-71.37525021023691,-17.773798516513857],[-71.46204077827112,-17.363487644116383],[-73.44452958850042,-16.359362888252996],[-75.23788265654144,-15.265682875227782],[-76.00920508492995,-14.649286390850321],[-76.42346920439775,-13.82318694423243],[-76.25924150257416,-13.535039157772943],[-77.10619238962184,-12.22271615972082],[-78.09215287953464,-10.377712497604065],[-79.03695309112695,-8.386567884965892],[-79.44592037628485,-7.93083342858386],[-79.76057817251004,-7.194340915560083],[-80.53748165558608,-6.541667575713717],[-81.24999630402642,-6.136834405139183],[-80.92634680858244,-5.690556735866565],[-81.41094255239946,-4.736764825055459],[-81.09966956248937,-4.036394138203697],[-80.30256059438722,-3.404856459164713],[-80.18401485870967,-3.821161797708044],[-80.46929460317695,-4.059286797708999],[-80.44224199087216,-4.425724379090674],[-80.02890804718561,-4.346090996928893],[-79.62497921417618,-4.454198093283494],[-79.20528906931771,-4.959128513207389],[-78.63989722361234,-4.547784112164074],[-78.45068396677564,-3.873096612161376],[-77.83790483265861,-3.003020521663103],[-76.63539425322672,-2.608677666843818],[-75.54499569365204,-1.56160979574588],[-75.23372270374193,-0.911416924649529],[-75.37322323271385,-0.15203175212045],[-75.10662451852008,-0.05720549886486],[-74.44160051135597,-0.530820000819887],[-74.12239518908906,-1.002832533373848],[-73.6595035468346,-1.260491224781134],[-73.07039221870724,-2.308954359550953],[-72.32578650581365,-2.434218031426454],[-71.7747607082854,-2.169789727388938],[-71.41364579942979,-2.342802422702128],[-70.81347571479196,-2.256864515800743],[-70.04770850287485,-2.725156345229699],[-70.69268205430971,-3.742872002785859],[-70.39404395209499,-3.766591485207825],[-69.89363521999663,-4.298186944194327],[-70.7947688463023,-4.251264743673303],[-70.92884334988358,-4.401591485210368],[-71.74840572781655,-4.593982842633011],[-72.89192765978726,-5.274561455916981],[-72.96450720894119,-5.741251315944893],[-73.21971126981461,-6.089188734566078],[-73.1200274319236,-6.629930922068239],[-73.72448666044164,-6.91859547285064],[-73.7234014553635,-7.340998630404414],[-73.98723548042966,-7.523829847853064],[-73.57105933296707,-8.424446709835834],[-73.01538265653254,-9.03283334720806],[-73.22671342639016,-9.462212823121234],[-72.56303300646564,-9.520193780152717],[-72.18489071316984,-10.053597914269432],[-71.30241227892154,-10.079436130415374],[-70.48189388699117,-9.490118096558845],[-70.54868567572841,-11.009146823778465],[-70.0937522040469,-11.123971856331012],[-69.52967810736496,-10.951734307502194],[-68.66507971868961,-12.561300144097173],[-68.88007951523997,-12.899729099176653],[-68.92922380234954,-13.602683607643007],[-68.9488866848366,-14.453639418193283],[-69.33953467474701,-14.953195489158832],[-69.16034664577495,-15.323973890853019],[-69.38976416693471,-15.660129082911654],[-68.9596353827533,-16.50069793057127],[-69.59042375352405,-17.580011895419332]]]}},{"type":"Feature","properties":{"scalerank":1,"featurecla":"Admin-0 country","labelrank":3,"sovereignt":"Venezuela","sov_a3":"VEN","adm0_dif":0,"level":2,"type":"Sovereign country","admin":"Venezuela","adm0_a3":"VEN","geou_dif":0,"geounit":"Venezuela","gu_a3":"VEN","su_dif":0,"subunit":"Venezuela","su_a3":"VEN","brk_diff":0,"name":"Venezuela","name_long":"Venezuela","brk_a3":"VEN","brk_name":"Venezuela","brk_group":null,"abbrev":"Ven.","postal":"VE","formal_en":"Bolivarian Republic of Venezuela","formal_fr":"República Bolivariana de Venezuela","note_adm0":null,"note_brk":null,"name_sort":"Venezuela, RB","name_alt":null,"mapcolor7":1,"mapcolor8":3,"mapcolor9":1,"mapcolor13":4,"pop_est":26814843,"gdp_md_est":357400,"pop_year":-99,"lastcensus":2001,"gdp_year":-99,"economy":"5. Emerging region: G20","income_grp":"3. Upper middle income","wikipedia":-99,"fips_10":null,"iso_a2":"VE","iso_a3":"VEN","iso_n3":"862","un_a3":"862","wb_a2":"VE","wb_a3":"VEN","woe_id":-99,"adm0_a3_is":"VEN","adm0_a3_us":"VEN","adm0_a3_un":-99,"adm0_a3_wb":-99,"continent":"South America","region_un":"Americas","subregion":"South America","region_wb":"Latin America & Caribbean","name_len":9,"long_len":9,"abbrev_len":4,"tiny":-99,"homepart":1,"filename":"VEN.geojson"},"geometry":{"type":"Polygon","coordinates":[[[-71.3315836249503,11.776284084515808],[-71.36000566271082,11.53999359786121],[-71.94704993354651,11.423282375530022],[-71.62086829292019,10.969459947142795],[-71.63306393094108,10.446494452349027],[-72.07417395698451,9.865651353388373],[-71.69564409044654,9.072263088411248],[-71.26455929226773,9.137194525585983],[-71.03999935574339,9.859992784052407],[-71.35008378771079,10.211935126176215],[-71.40062333849224,10.968969021036015],[-70.1552988349065,11.37548167566004],[-70.29384334988103,11.846822414594214],[-69.94324459499683,12.162307033736099],[-69.58430009629747,11.459610907431212],[-68.88299923366445,11.443384507691563],[-68.23327145045873,10.885744126829946],[-68.19412655299763,10.554653225135922],[-67.29624854192633,10.54586823164631],[-66.227864142508,10.648626817258688],[-65.65523759628175,10.200798855017323],[-64.89045223657817,10.0772146671913],[-64.32947872583374,10.38959870039568],[-64.31800655786495,10.64141795495398],[-63.07932247582874,10.7017243514386],[-61.880946010980196,10.715625311725104],[-62.73011898461641,10.420268662960906],[-62.388511928950976,9.94820445397464],[-61.58876746280193,9.873066921422264],[-60.83059668643172,9.38133982994894],[-60.67125240745973,8.580174261911878],[-60.15009558779618,8.602756862823426],[-59.758284878159195,8.367034816924047],[-60.55058793805819,7.779602972846178],[-60.637972785063766,7.414999904810855],[-60.29566809756239,7.043911444522919],[-60.54399919294098,6.856584377464883],[-61.15933631045648,6.696077378766319],[-61.13941504580795,6.234296779806144],[-61.410302903881956,5.959068101419618],[-60.73357418480372,5.200277207861901],[-60.60117916527194,4.91809804933213],[-60.96689327660153,4.536467596856639],[-62.08542965355914,4.162123521334308],[-62.804533047116706,4.006965033377952],[-63.0931975978991,3.770571193858785],[-63.88834286157416,4.020530096854571],[-64.62865943058755,4.14848094320925],[-64.81606401229402,4.056445217297423],[-64.36849443221409,3.797210394705246],[-64.40882788761792,3.126786200366624],[-64.26999915226578,2.497005520025567],[-63.42286739770512,2.411067613124174],[-63.36878801131166,2.200899562993129],[-64.08308549666609,1.91636912679408],[-64.19930579289051,1.49285492594602],[-64.61101192895985,1.328730576987042],[-65.35471330428837,1.0952822941085],[-65.54826738143757,0.78925446207603],[-66.32576514348496,0.724452215982012],[-66.87632585312258,1.253360500489336],[-67.18129431829307,2.250638129074062],[-67.44709204778631,2.600280869960869],[-67.8099381171237,2.820655015469569],[-67.30317318385345,3.31845408773718],[-67.33756384954368,3.542342230641722],[-67.62183590358127,3.839481716319994],[-67.82301225449355,4.503937282728899],[-67.74469662135522,5.221128648291668],[-67.52153194850275,5.556870428891969],[-67.34143958196557,6.095468044454023],[-67.69508724635502,6.267318020040647],[-68.26505245631823,6.153268133972475],[-68.98531856960236,6.206804917826858],[-69.38947994655712,6.099860541198836],[-70.09331295437242,6.96037649172311],[-70.67423356798152,7.087784735538719],[-71.96017574734864,6.991614895043538],[-72.19835242378188,7.340430813013682],[-72.44448727078807,7.423784898300481],[-72.47967892117885,7.632506008327354],[-72.36090064155596,8.002638454617895],[-72.43986223009796,8.405275376820029],[-72.6604947577681,8.625287787302682],[-72.7887298245004,9.085027167187334],[-73.30495154488005,9.151999823437606],[-73.02760413276957,9.736770331252444],[-72.9052860175347,10.450344346554772],[-72.61465776232521,10.821975409381778],[-72.22757544624294,11.10870209395324],[-71.97392167833829,11.60867157637712],[-71.3315836249503,11.776284084515808]]]}},{"type":"Feature","properties":{"scalerank":1,"featurecla":"Admin-0 country","labelrank":5,"sovereignt":"Haiti","sov_a3":"HTI","adm0_dif":0,"level":2,"type":"Sovereign country","admin":"Haiti","adm0_a3":"HTI","geou_dif":0,"geounit":"Haiti","gu_a3":"HTI","su_dif":0,"subunit":"Haiti","su_a3":"HTI","brk_diff":0,"name":"Haiti","name_long":"Haiti","brk_a3":"HTI","brk_name":"Haiti","brk_group":null,"abbrev":"Haiti","postal":"HT","formal_en":"Republic of Haiti","formal_fr":null,"note_adm0":null,"note_brk":null,"name_sort":"Haiti","name_alt":null,"mapcolor7":2,"mapcolor8":1,"mapcolor9":7,"mapcolor13":2,"pop_est":9035536,"gdp_md_est":11500,"pop_year":-99,"lastcensus":2003,"gdp_year":-99,"economy":"7. Least developed region","income_grp":"5. Low income","wikipedia":-99,"fips_10":null,"iso_a2":"HT","iso_a3":"HTI","iso_n3":"332","un_a3":"332","wb_a2":"HT","wb_a3":"HTI","woe_id":-99,"adm0_a3_is":"HTI","adm0_a3_us":"HTI","adm0_a3_un":-99,"adm0_a3_wb":-99,"continent":"North America","region_un":"Americas","subregion":"Caribbean","region_wb":"Latin America & Caribbean","name_len":5,"long_len":5,"abbrev_len":5,"tiny":-99,"homepart":1,"filename":"HTI.geojson"},"geometry":{"type":"Polygon","coordinates":[[[-73.18979061551762,19.915683905511912],[-72.57967281766362,19.871500555902358],[-71.71236141629296,19.714455878167357],[-71.62487321642283,19.169837958243306],[-71.70130265978248,18.78541697842405],[-71.94511206733556,18.61690013272026],[-71.68773759630587,18.31666006110447],[-71.70830481635805,18.04499705654609],[-72.37247616238935,18.21496084235406],[-72.84441118029488,18.14561107021836],[-73.45455481636503,18.217906398994696],[-73.92243323433566,18.030992743395004],[-74.45803361682478,18.342549953682706],[-74.36992529976713,18.66490753831941],[-73.44954220243272,18.526052964751145],[-72.69493709989064,18.445799465401862],[-72.334881557897,18.668421535715254],[-72.79164954292489,19.10162506761803],[-72.78410478381028,19.48359141690341],[-73.41502234566175,19.639550889560283],[-73.18979061551762,19.915683905511912]]]}},{"type":"Feature","properties":{"scalerank":1,"featurecla":"Admin-0 country","labelrank":3,"sovereignt":"Cuba","sov_a3":"CUB","adm0_dif":0,"level":2,"type":"Sovereign country","admin":"Cuba","adm0_a3":"CUB","geou_dif":0,"geounit":"Cuba","gu_a3":"CUB","su_dif":0,"subunit":"Cuba","su_a3":"CUB","brk_diff":0,"name":"Cuba","name_long":"Cuba","brk_a3":"CUB","brk_name":"Cuba","brk_group":null,"abbrev":"Cuba","postal":"CU","formal_en":"Republic of Cuba","formal_fr":null,"note_adm0":null,"note_brk":null,"name_sort":"Cuba","name_alt":null,"mapcolor7":3,"mapcolor8":5,"mapcolor9":3,"mapcolor13":4,"pop_est":11451652,"gdp_md_est":108200,"pop_year":-99,"lastcensus":2002,"gdp_year":-99,"economy":"5. Emerging region: G20","income_grp":"3. Upper middle income","wikipedia":-99,"fips_10":null,"iso_a2":"CU","iso_a3":"CUB","iso_n3":"192","un_a3":"192","wb_a2":"CU","wb_a3":"CUB","woe_id":-99,"adm0_a3_is":"CUB","adm0_a3_us":"CUB","adm0_a3_un":-99,"adm0_a3_wb":-99,"continent":"North America","region_un":"Americas","subregion":"Caribbean","region_wb":"Latin America & Caribbean","name_len":4,"long_len":4,"abbrev_len":4,"tiny":-99,"homepart":1,"filename":"CUB.geojson"},"geometry":{"type":"Polygon","coordinates":[[[-82.26815121125706,23.188610744717703],[-81.40445716014683,23.11727142993878],[-80.6187686835812,23.105980129483],[-79.67952368846025,22.76530324959883],[-79.28148596873207,22.399201565027056],[-78.34743445505649,22.512166246017088],[-77.99329586456028,22.277193508385935],[-77.14642249216105,21.657851467367834],[-76.52382483590856,21.206819566324373],[-76.19462012399319,21.220565497314013],[-75.59822241891267,21.016624457274133],[-75.67106035022806,20.735091254148],[-74.9338960435845,20.693905137611385],[-74.17802486845126,20.28462779385974],[-74.29664811877724,20.05037852628068],[-74.96159461129294,19.92343537035569],[-75.63468014189459,19.873774318923196],[-76.323656175426,19.95289093676206],[-77.75548092315306,19.855480861891873],[-77.08510840524674,20.413353786698792],[-77.49265458851661,20.67310537361389],[-78.13729224314159,20.73994883878343],[-78.48282670766119,21.02861338956585],[-78.71986650258401,21.598113511638434],[-79.28499996612794,21.5591753199065],[-80.21747534861865,21.827324327069036],[-80.51753455272141,22.03707896574176],[-81.82094336620318,22.19205658618507],[-82.16999182811864,22.38710927987075],[-81.79500179719267,22.636964830001958],[-82.77589799674084,22.688150336187064],[-83.49445878775936,22.16851797127613],[-83.90880042187563,22.154565334557333],[-84.05215084505326,21.910575059491254],[-84.54703019889638,21.801227728761642],[-84.97491105827311,21.89602814380109],[-84.44706214062776,22.204949856041907],[-84.23035702181178,22.565754706303764],[-83.7782399156902,22.788118394455694],[-83.26754757356575,22.983041897060644],[-82.51043616405751,23.078746649665188],[-82.26815121125706,23.188610744717703]]]}},{"type":"Feature","properties":{"scalerank":1,"featurecla":"Admin-0 country","labelrank":3,"sovereignt":"Guatemala","sov_a3":"GTM","adm0_dif":0,"level":2,"type":"Sovereign country","admin":"Guatemala","adm0_a3":"GTM","geou_dif":0,"geounit":"Guatemala","gu_a3":"GTM","su_dif":0,"subunit":"Guatemala","su_a3":"GTM","brk_diff":0,"name":"Guatemala","name_long":"Guatemala","brk_a3":"GTM","brk_name":"Guatemala","brk_group":null,"abbrev":"Guat.","postal":"GT","formal_en":"Republic of Guatemala","formal_fr":null,"note_adm0":null,"note_brk":null,"name_sort":"Guatemala","name_alt":null,"mapcolor7":3,"mapcolor8":3,"mapcolor9":3,"mapcolor13":6,"pop_est":13276517,"gdp_md_est":68580,"pop_year":-99,"lastcensus":2002,"gdp_year":-99,"economy":"6. Developing region","income_grp":"4. Lower middle income","wikipedia":-99,"fips_10":null,"iso_a2":"GT","iso_a3":"GTM","iso_n3":"320","un_a3":"320","wb_a2":"GT","wb_a3":"GTM","woe_id":-99,"adm0_a3_is":"GTM","adm0_a3_us":"GTM","adm0_a3_un":-99,"adm0_a3_wb":-99,"continent":"North America","region_un":"Americas","subregion":"Central America","region_wb":"Latin America & Caribbean","name_len":9,"long_len":9,"abbrev_len":5,"tiny":4,"homepart":1,"filename":"GTM.geojson"},"geometry":{"type":"Polygon","coordinates":[[[-90.09555457229098,13.735337632700734],[-90.60862403030085,13.909771429901951],[-91.23241024449605,13.927832342987957],[-91.68974667027912,14.126218166556455],[-92.22775000686983,14.538828640190928],[-92.20322953974731,14.830102850804069],[-92.08721594925207,15.06458466232844],[-92.22924862340628,15.25144664149586],[-91.74796017125591,16.066564846251723],[-90.46447262242265,16.069562079324655],[-90.43886695022204,16.410109768128095],[-90.60084672724092,16.47077789963876],[-90.71182186558772,16.687483018454728],[-91.08167009150065,16.918476670799404],[-91.45392127151516,17.252177232324172],[-91.0022692532842,17.25465770107418],[-91.00151994501596,17.81759491624571],[-90.06793351923098,17.819326076727474],[-89.14308041050332,17.80831899664932],[-89.15080603713095,17.015576687075836],[-89.22912167026928,15.88693756760517],[-88.93061275913527,15.887273464415074],[-88.60458614780583,15.70638011317736],[-88.51836402052686,15.855389105690975],[-88.22502275262202,15.727722479713902],[-88.68067969435563,15.346247056535304],[-89.15481096063357,15.06641917567481],[-89.22522009963127,14.874286200413621],[-89.14553504103718,14.678019110569084],[-89.35332597528279,14.424132798719116],[-89.58734269891654,14.362586167859488],[-89.53421932652051,14.244815578666305],[-89.72193396682073,14.134228013561694],[-90.0646779039966,13.881969509328924],[-90.09555457229098,13.735337632700734]]]}}]} \ No newline at end of file diff --git a/webapp/data/data.tsv b/webapp/data/data.tsv new file mode 100644 index 000000000..c152b2b76 --- /dev/null +++ b/webapp/data/data.tsv @@ -0,0 +1,5 @@ +x x² x³ +0.1 0.01 0.001 +1 1 1 +10 100 1000 +100 10000 1000000 \ No newline at end of file diff --git a/webapp/data/us.json b/webapp/data/us.json new file mode 100644 index 000000000..3a32d427c --- /dev/null +++ b/webapp/data/us.json @@ -0,0 +1 @@ +{"type":"Topology","objects":{"counties":{"type":"GeometryCollection","bbox":[-179.1473399999999,17.67439566600018,179.7784800000003,71.38921046500008],"geometries":[{"type":"MultiPolygon","id":53073,"arcs":[[[0,1,2]]]},{"type":"Polygon","id":30105,"arcs":[[3,4,5,6,7,8]]},{"type":"Polygon","id":30029,"arcs":[[9,10,11,12,13,14,15,16,17,18]]},{"type":"Polygon","id":16021,"arcs":[[19,20,21,22]]},{"type":"Polygon","id":30071,"arcs":[[-8,23,24,25,26,27]]},{"type":"Polygon","id":38079,"arcs":[[28,29,30,31]]},{"type":"Polygon","id":30053,"arcs":[[-18,32,33,-20,34]]},{"type":"Polygon","id":38009,"arcs":[[-30,35,36,37,38]]},{"type":"Polygon","id":30035,"arcs":[[39,40,-10,41]]},{"type":"Polygon","id":30041,"arcs":[[42,43,44,45]]},{"type":"Polygon","id":30005,"arcs":[[-27,46,47,-46,48]]},{"type":"Polygon","id":30019,"arcs":[[49,50,-4,51]]},{"type":"Polygon","id":38067,"arcs":[[52,53,54,55]]},{"type":"Polygon","id":27069,"arcs":[[56,57,-53,58]]},{"type":"Polygon","id":38095,"arcs":[[59,60,61,-32,62,63]]},{"type":"Polygon","id":38019,"arcs":[[-55,64,65,-64,66]]},{"type":"Polygon","id":53047,"arcs":[[67,68,69,70,71,72,-1,73]]},{"type":"Polygon","id":53065,"arcs":[[74,75,76,77,78]]},{"type":"Polygon","id":53051,"arcs":[[-22,79,80,-75,81]]},{"type":"Polygon","id":53019,"arcs":[[-78,82,-68,83]]},{"type":"Polygon","id":30051,"arcs":[[84,85,86,-44,87]]},{"type":"Polygon","id":38023,"arcs":[[88,89,90,91]]},{"type":"Polygon","id":38013,"arcs":[[92,93,94,95,-89,96]]},{"type":"Polygon","id":30101,"arcs":[[97,-86,98,-40]]},{"type":"Polygon","id":38075,"arcs":[[99,100,-93,101,-38]]},{"type":"Polygon","id":27135,"arcs":[[102,103,-57,104,105]]},{"type":"Polygon","id":30091,"arcs":[[-91,106,107,-50,108]]},{"type":"Polygon","id":16017,"arcs":[[-34,109,110,111,112,-80,-21]]},{"type":"Polygon","id":38101,"arcs":[[-101,113,114,115,-94]]},{"type":"MultiPolygon","id":53055,"arcs":[[[116]],[[117]],[[118]]]},{"type":"Polygon","id":27071,"arcs":[[119,120,121,122,123]]},{"type":"MultiPolygon","id":53057,"arcs":[[[124,-2,-73,125,126,127]]]},{"type":"Polygon","id":38105,"arcs":[[-96,128,129,130,-107,-90]]},{"type":"Polygon","id":38049,"arcs":[[131,132,133,-114,-100,-37]]},{"type":"Polygon","id":27137,"arcs":[[134,135,136,137,138,139,-120,140]]},{"type":"Polygon","id":30085,"arcs":[[-108,-131,141,142,-5,-51]]},{"type":"Polygon","id":53007,"arcs":[[-72,143,144,145,146,-126]]},{"type":"Polygon","id":38061,"arcs":[[147,148,149,-129,-95,-116]]},{"type":"Polygon","id":27089,"arcs":[[150,151,152,153,154,-58,-104]]},{"type":"Polygon","id":38069,"arcs":[[-62,155,156,157,-132,-36,-29]]},{"type":"MultiPolygon","id":38071,"arcs":[[[158]],[[159,160,161,-60,-66]]]},{"type":"Polygon","id":38099,"arcs":[[-54,-155,162,163,-160,-65]]},{"type":"Polygon","id":27007,"arcs":[[-122,164,165,166,167,168,-151,-103,169]]},{"type":"Polygon","id":30073,"arcs":[[-99,-85,170,171,-11,-41]]},{"type":"MultiPolygon","id":53029,"arcs":[[[172,173]],[[174]]]},{"type":"MultiPolygon","id":53009,"arcs":[[[175,176]]]},{"type":"Polygon","id":38005,"arcs":[[-61,-162,177,178,179,-156],[-159]]},{"type":"Polygon","id":30015,"arcs":[[-48,180,181,182,183,-171,-88,-43]]},{"type":"MultiPolygon","id":53061,"arcs":[[[-147,184,185,-173,186,-127]]]},{"type":"Polygon","id":30089,"arcs":[[-17,187,188,189,190,-110,-33]]},{"type":"Polygon","id":27075,"arcs":[[191,192,-135,193]]},{"type":"Polygon","id":38063,"arcs":[[194,195,196,197,-178,-161,-164]]},{"type":"Polygon","id":38035,"arcs":[[-154,198,199,200,-195,-163]]},{"type":"Polygon","id":27119,"arcs":[[201,202,203,204,205,206,207,-199,-153]]},{"type":"Polygon","id":27113,"arcs":[[-169,208,-204,209,-202,-152]]},{"type":"Polygon","id":30083,"arcs":[[210,211,212,213,-142]]},{"type":"Polygon","id":53017,"arcs":[[214,215,-144,-71]]},{"type":"Polygon","id":38053,"arcs":[[-150,216,217,218,219,-211,-130]]},{"type":"MultiPolygon","id":53031,"arcs":[[[220,221,222,-176,223]]]},{"type":"Polygon","id":30099,"arcs":[[-184,224,225,-12,-172]]},{"type":"Polygon","id":30055,"arcs":[[-214,226,227,228,-6,-143]]},{"type":"Polygon","id":16079,"arcs":[[-191,229,230,231,232,233,-111]]},{"type":"Polygon","id":30047,"arcs":[[234,-188,-16]]},{"type":"Polygon","id":53063,"arcs":[[-81,-113,235,236,237,238,-76]]},{"type":"Polygon","id":27029,"arcs":[[239,240,241,-205,-209,-168]]},{"type":"Polygon","id":16055,"arcs":[[-234,242,-236,-112]]},{"type":"Polygon","id":30033,"arcs":[[-229,243,244,245,246,-24,-7]]},{"type":"Polygon","id":27125,"arcs":[[-203,-210]]},{"type":"Polygon","id":53025,"arcs":[[-70,247,248,249,250,251,252,-215]]},{"type":"Polygon","id":53043,"arcs":[[-83,-77,-239,253,254,-248,-69]]},{"type":"Polygon","id":30049,"arcs":[[255,256,257,258,259,-13,-226]]},{"type":"MultiPolygon","id":53035,"arcs":[[[260]],[[261,262,263,264,265]]]},{"type":"Polygon","id":27061,"arcs":[[-140,266,267,-165,-121]]},{"type":"Polygon","id":38055,"arcs":[[268,269,270,271,272,-148,-115,-134]]},{"type":"Polygon","id":38027,"arcs":[[-198,273,274,275,-179]]},{"type":"Polygon","id":38103,"arcs":[[-180,-276,276,277,278,279,-157]]},{"type":"Polygon","id":38083,"arcs":[[-158,-280,280,281,-269,-133]]},{"type":"Polygon","id":38025,"arcs":[[-273,282,283,284,-217,-149]]},{"type":"Polygon","id":30027,"arcs":[[-26,285,286,287,288,289,-181,-47]]},{"type":"Polygon","id":30021,"arcs":[[-213,290,291,-227]]},{"type":"MultiPolygon","id":53033,"arcs":[[[292]],[[-146,293,294,295,-185]]]},{"type":"Polygon","id":30013,"arcs":[[296,297,-256,-225,-183]]},{"type":"Polygon","id":38091,"arcs":[[-201,298,299,300,301,-196]]},{"type":"Polygon","id":38039,"arcs":[[-302,302,303,304,-274,-197]]},{"type":"Polygon","id":38097,"arcs":[[305,306,-299,-200,-208]]},{"type":"MultiPolygon","id":53045,"arcs":[[[307,-265,308,309,310,311,-221]]]},{"type":"Polygon","id":30063,"arcs":[[-15,312,313,314,315,316,317,-189,-235]]},{"type":"Polygon","id":30077,"arcs":[[-260,318,319,320,-313,-14]]},{"type":"Polygon","id":30069,"arcs":[[-247,321,322,-286,-25]]},{"type":"Polygon","id":53037,"arcs":[[-216,-253,323,-294,-145]]},{"type":"Polygon","id":38031,"arcs":[[-305,324,-277,-275]]},{"type":"Polygon","id":38057,"arcs":[[325,326,327,-283,-272]]},{"type":"MultiPolygon","id":53027,"arcs":[[[-312,328,329,330,331,-222]]]},{"type":"Polygon","id":27087,"arcs":[[332,333,-206,-242]]},{"type":"Polygon","id":27107,"arcs":[[-207,-334,334,335,336,-306]]},{"type":"Polygon","id":30061,"arcs":[[-318,337,-230,-190]]},{"type":"Polygon","id":27021,"arcs":[[338,339,340,341,342,343,-166,-268]]},{"type":"Polygon","id":23003,"arcs":[[344,345,346,347,348]]},{"type":"Polygon","id":30045,"arcs":[[-290,349,350,-297,-182]]},{"type":"Polygon","id":16009,"arcs":[[-233,351,352,-237,-243]]},{"type":"Polygon","id":27057,"arcs":[[-344,353,354,-240,-167]]},{"type":"MultiPolygon","id":53053,"arcs":[[[-295,355,356,357,358]],[[-262,359]],[[-309,-264,360]]]},{"type":"Polygon","id":30109,"arcs":[[-220,361,362,363,-291,-212]]},{"type":"Polygon","id":38007,"arcs":[[-285,364,365,366,-218]]},{"type":"Polygon","id":38033,"arcs":[[-367,367,368,-362,-219]]},{"type":"Polygon","id":38043,"arcs":[[369,370,371,372,-281,-279]]},{"type":"Polygon","id":38093,"arcs":[[-304,373,374,375,-370,-278,-325]]},{"type":"Polygon","id":38015,"arcs":[[-373,376,377,378,-270,-282]]},{"type":"Polygon","id":38065,"arcs":[[-379,379,-326,-271]]},{"type":"Polygon","id":53001,"arcs":[[380,381,-249,-255]]},{"type":"Polygon","id":53075,"arcs":[[-238,-353,382,383,384,385,386,387,-381,-254]]},{"type":"Polygon","id":38003,"arcs":[[-301,388,389,390,-374,-303]]},{"type":"Polygon","id":38017,"arcs":[[-307,-337,391,392,393,-389,-300]]},{"type":"Polygon","id":53067,"arcs":[[-358,394,-329,-311,395]]},{"type":"Polygon","id":30079,"arcs":[[-364,396,397,-244,-228,-292]]},{"type":"Polygon","id":27005,"arcs":[[-355,398,399,400,-335,-333,-241]]},{"type":"Polygon","id":27027,"arcs":[[-401,401,402,403,-392,-336]]},{"type":"Polygon","id":16057,"arcs":[[-232,404,405,-383,-352]]},{"type":"Polygon","id":53077,"arcs":[[-252,406,407,408,409,-356,-324]]},{"type":"Polygon","id":30059,"arcs":[[-351,410,411,412,413,414,-257,-298]]},{"type":"Polygon","id":27001,"arcs":[[-139,415,416,417,418,419,-339,-267]]},{"type":"Polygon","id":26131,"arcs":[[420,421,422,423]]},{"type":"Polygon","id":38089,"arcs":[[-328,424,425,426,427,-365,-284]]},{"type":"Polygon","id":38059,"arcs":[[-378,428,429,430,-425,-327,-380]]},{"type":"Polygon","id":26013,"arcs":[[431,432,433,434]]},{"type":"Polygon","id":16035,"arcs":[[-338,-317,435,436,437,-405,-231]]},{"type":"Polygon","id":30017,"arcs":[[438,439,440,441,-245,-398]]},{"type":"Polygon","id":30087,"arcs":[[-442,442,443,444,445,446,-322,-246]]},{"type":"Polygon","id":30039,"arcs":[[447,448,-314,-321]]},{"type":"Polygon","id":27159,"arcs":[[-343,449,450,-399,-354]]},{"type":"Polygon","id":27035,"arcs":[[-420,451,452,-340]]},{"type":"MultiPolygon","id":53049,"arcs":[[[453,454,455,-331]]]},{"type":"Polygon","id":53041,"arcs":[[-395,-357,-410,456,457,458,-454,-330]]},{"type":"Polygon","id":30007,"arcs":[[-415,459,460,-258]]},{"type":"Polygon","id":27017,"arcs":[[461,462,-416,-138]]},{"type":"Polygon","id":26053,"arcs":[[463,464,465,466,-422]]},{"type":"Polygon","id":30065,"arcs":[[-447,467,468,-287,-323]]},{"type":"Polygon","id":26095,"arcs":[[469,470,471,472,473]]},{"type":"Polygon","id":30037,"arcs":[[-469,474,475,476,477,-288]]},{"type":"Polygon","id":30107,"arcs":[[-289,-478,478,-411,-350]]},{"type":"Polygon","id":53021,"arcs":[[479,480,481,-250,-382,-388]]},{"type":"Polygon","id":53005,"arcs":[[482,483,484,485,-407,-251,-482]]},{"type":"Polygon","id":27111,"arcs":[[-451,486,487,488,489,-402,-400]]},{"type":"Polygon","id":38037,"arcs":[[490,491,492,-426,-431]]},{"type":"Polygon","id":53023,"arcs":[[493,494,495,-386]]},{"type":"Polygon","id":30025,"arcs":[[-369,496,497,498,499,-439,-397,-363]]},{"type":"Polygon","id":16049,"arcs":[[500,501,502,503,504,505,506,-436,-316]]},{"type":"Polygon","id":30081,"arcs":[[-449,507,508,509,-501,-315]]},{"type":"Polygon","id":38029,"arcs":[[-372,510,511,512,513,-429,-377]]},{"type":"Polygon","id":38047,"arcs":[[-376,514,515,-511,-371]]},{"type":"Polygon","id":16069,"arcs":[[-438,516,-506,517,518,-384,-406]]},{"type":"Polygon","id":38087,"arcs":[[519,520,521,-497,-368,-366,-428]]},{"type":"Polygon","id":38045,"arcs":[[522,523,524,-515,-375,-391]]},{"type":"Polygon","id":38041,"arcs":[[-493,525,-520,-427]]},{"type":"Polygon","id":27167,"arcs":[[-490,526,527,528,-403]]},{"type":"Polygon","id":38073,"arcs":[[-394,529,530,531,-523,-390]]},{"type":"Polygon","id":38077,"arcs":[[-529,532,533,534,-530,-393,-404]]},{"type":"Polygon","id":53013,"arcs":[[535,536,537,-480,-387,-496]]},{"type":"Polygon","id":53071,"arcs":[[-538,538,-483,-481]]},{"type":"Polygon","id":55051,"arcs":[[-466,539,540,541,542]]},{"type":"Polygon","id":23025,"arcs":[[543,544,545,546,547,548,-348]]},{"type":"Polygon","id":23021,"arcs":[[549,-544,-347]]},{"type":"Polygon","id":30043,"arcs":[[-461,550,551,552,553,-319,-259]]},{"type":"Polygon","id":26153,"arcs":[[-472,554,555,556,557]]},{"type":"Polygon","id":30111,"arcs":[[558,559,560,561,-475,-468,-446]]},{"type":"Polygon","id":30103,"arcs":[[562,-559,-445]]},{"type":"Polygon","id":16061,"arcs":[[-437,-507,-517]]},{"type":"Polygon","id":53003,"arcs":[[-519,563,-494,-385]]},{"type":"Polygon","id":38085,"arcs":[[-514,564,565,-491,-430]]},{"type":"Polygon","id":26071,"arcs":[[-433,566,567,568,569,570,-464,-421,571]]},{"type":"Polygon","id":27115,"arcs":[[572,573,574,575,-417,-463]]},{"type":"Polygon","id":23019,"arcs":[[576,577,578,579,-545,-550,-346]]},{"type":"Polygon","id":53059,"arcs":[[-409,580,581,582,583,584,-457]]},{"type":"Polygon","id":53015,"arcs":[[-585,585,586,587,588,-458]]},{"type":"MultiPolygon","id":53069,"arcs":[[[-459,-589,589,-455]]]},{"type":"Polygon","id":27153,"arcs":[[-342,590,591,592,-487,-450]]},{"type":"Polygon","id":27097,"arcs":[[-453,593,594,595,-591,-341]]},{"type":"Polygon","id":55125,"arcs":[[596,597,-540,-465,-571,598]]},{"type":"MultiPolygon","id":41007,"arcs":[[[599,600,601]]]},{"type":"Polygon","id":38001,"arcs":[[-492,-566,602,603,604,605,-521,-526]]},{"type":"Polygon","id":38081,"arcs":[[-535,606,607,608,-531]]},{"type":"Polygon","id":38051,"arcs":[[-525,609,610,611,-512,-516]]},{"type":"Polygon","id":38021,"arcs":[[-532,-609,612,613,-610,-524]]},{"type":"Polygon","id":38011,"arcs":[[-606,614,-498,-522]]},{"type":"Polygon","id":30023,"arcs":[[-554,615,616,-508,-448,-320]]},{"type":"Polygon","id":26043,"arcs":[[617,618,619,-568,620]]},{"type":"Polygon","id":27095,"arcs":[[621,622,623,624,-594,-452,-419]]},{"type":"Polygon","id":30097,"arcs":[[-477,625,626,-412,-479]]},{"type":"Polygon","id":30031,"arcs":[[-414,627,628,629,630,631,-551,-460]]},{"type":"Polygon","id":30067,"arcs":[[-627,632,633,634,-628,-413]]},{"type":"Polygon","id":30093,"arcs":[[635,636,-616,-553]]},{"type":"MultiPolygon","id":41009,"arcs":[[[637,638,639,-600,640,-587]]]},{"type":"Polygon","id":27065,"arcs":[[-576,641,-622,-418]]},{"type":"Polygon","id":55013,"arcs":[[642,643,644,645,-574,646]]},{"type":"Polygon","id":55113,"arcs":[[647,648,649,650,651]]},{"type":"Polygon","id":55129,"arcs":[[-651,652,-643,653]]},{"type":"Polygon","id":30011,"arcs":[[654,655,656,657,-440,-500]]},{"type":"Polygon","id":30095,"arcs":[[-562,658,-633,-626,-476]]},{"type":"Polygon","id":27051,"arcs":[[-489,659,660,661,-527]]},{"type":"Polygon","id":27041,"arcs":[[-593,662,663,-660,-488]]},{"type":"Polygon","id":55041,"arcs":[[664,665,666,667,668,-599,-570]]},{"type":"Polygon","id":53011,"arcs":[[-584,669,-638,-586]]},{"type":"Polygon","id":53039,"arcs":[[-486,670,671,672,673,674,-581,-408]]},{"type":"Polygon","id":30003,"arcs":[[-444,675,676,677,678,-560,-563]]},{"type":"Polygon","id":27155,"arcs":[[-662,679,680,681,-533,-528]]},{"type":"Polygon","id":55037,"arcs":[[-620,682,-665,-569]]},{"type":"Polygon","id":41059,"arcs":[[-537,683,684,685,686,-484,-539]]},{"type":"Polygon","id":41063,"arcs":[[-495,-564,-518,-505,687,688,689,-684,-536]]},{"type":"Polygon","id":26109,"arcs":[[690,691,692,-618,693]]},{"type":"Polygon","id":55099,"arcs":[[-598,694,695,696,697,-649,698,-541]]},{"type":"Polygon","id":46105,"arcs":[[699,700,701,702,703,-604]]},{"type":"Polygon","id":46031,"arcs":[[704,705,706,707,-700,-603,-565]]},{"type":"Polygon","id":46063,"arcs":[[-605,-704,708,-655,-499,-615]]},{"type":"Polygon","id":46021,"arcs":[[-612,709,710,-705,-513]]},{"type":"Polygon","id":30001,"arcs":[[-637,711,712,713,714,-509,-617]]},{"type":"Polygon","id":46089,"arcs":[[-614,715,716,717,-710,-611]]},{"type":"Polygon","id":46013,"arcs":[[-608,718,719,720,721,722,-716,-613]]},{"type":"Polygon","id":46109,"arcs":[[-682,723,724,725,726,-534]]},{"type":"Polygon","id":46091,"arcs":[[-727,727,-719,-607]]},{"type":"Polygon","id":41049,"arcs":[[-687,728,729,730,-671,-485]]},{"type":"Polygon","id":55085,"arcs":[[-669,731,732,-695,-597]]},{"type":"Polygon","id":41061,"arcs":[[733,734,-685,-690]]},{"type":"Polygon","id":30057,"arcs":[[-632,735,-712,-636,-552]]},{"type":"Polygon","id":27009,"arcs":[[-625,736,737,-595]]},{"type":"Polygon","id":41021,"arcs":[[738,739,740,-672,-731]]},{"type":"Polygon","id":30075,"arcs":[[-658,741,742,743,-676,-443,-441]]},{"type":"Polygon","id":26031,"arcs":[[744,745,746,747,748,749]]},{"type":"MultiPolygon","id":41057,"arcs":[[[750,751,752,753,754,-601]]]},{"type":"Polygon","id":41067,"arcs":[[755,756,757,-751,-640]]},{"type":"Polygon","id":27145,"arcs":[[-738,758,759,760,761,762,-663,-592,-596]]},{"type":"Polygon","id":27149,"arcs":[[763,764,765,-680,-661]]},{"type":"Polygon","id":27121,"arcs":[[-763,766,767,-764,-664]]},{"type":"Polygon","id":41055,"arcs":[[768,-673,-741]]},{"type":"Polygon","id":27059,"arcs":[[769,770,771,-623,-642]]},{"type":"Polygon","id":27025,"arcs":[[-646,772,773,774,-770,-575]]},{"type":"Polygon","id":55095,"arcs":[[775,776,777,-773,-645]]},{"type":"Polygon","id":41051,"arcs":[[-670,-583,778,779,-756,-639]]},{"type":"Polygon","id":41027,"arcs":[[780,781,-779,-582,-675]]},{"type":"Polygon","id":41065,"arcs":[[-769,-740,782,783,784,785,-781,-674]]},{"type":"Polygon","id":16059,"arcs":[[-715,786,787,788,789,-502,-510]]},{"type":"MultiPolygon","id":23029,"arcs":[[[790,-577,-345,791]]]},{"type":"Polygon","id":23007,"arcs":[[792,793,794,795,-548]]},{"type":"Polygon","id":26141,"arcs":[[796,797,-745,798]]},{"type":"Polygon","id":55005,"arcs":[[-653,799,800,801,-776,-644]]},{"type":"Polygon","id":55107,"arcs":[[-698,802,803,-800,-650]]},{"type":"Polygon","id":30009,"arcs":[[-679,804,805,-634,-659,-561]]},{"type":"Polygon","id":46129,"arcs":[[-718,806,807,808,-706,-711]]},{"type":"Polygon","id":46045,"arcs":[[-723,809,810,-807,-717]]},{"type":"Polygon","id":46037,"arcs":[[-728,-726,811,812,813,814,-720]]},{"type":"Polygon","id":27011,"arcs":[[-681,-766,815,816,817,-724]]},{"type":"Polygon","id":27141,"arcs":[[-624,-772,818,819,820,-759,-737]]},{"type":"Polygon","id":55069,"arcs":[[821,822,823,-696,-733]]},{"type":"Polygon","id":46041,"arcs":[[-809,824,825,826,827,-707]]},{"type":"Polygon","id":46137,"arcs":[[-828,828,829,-701,-708]]},{"type":"Polygon","id":55067,"arcs":[[-668,830,831,832,833,-822,-732]]},{"type":"Polygon","id":41005,"arcs":[[-782,-786,834,835,-757,-780]]},{"type":"Polygon","id":41071,"arcs":[[-836,836,837,-752,-758]]},{"type":"Polygon","id":27171,"arcs":[[-821,838,839,840,841,-760]]},{"type":"Polygon","id":27003,"arcs":[[-775,842,843,844,-819,-771]]},{"type":"Polygon","id":27067,"arcs":[[845,846,847,848,-767,-762]]},{"type":"Polygon","id":27151,"arcs":[[-849,849,850,-816,-765,-768]]},{"type":"Polygon","id":55119,"arcs":[[-824,851,852,853,-803,-697]]},{"type":"Polygon","id":55083,"arcs":[[854,855,856,857,858,859,860,-831,-667]]},{"type":"Polygon","id":23017,"arcs":[[-795,861,862,863,864,865,866]]},{"type":"Polygon","id":46051,"arcs":[[-818,867,868,869,-812,-725]]},{"type":"Polygon","id":27093,"arcs":[[-842,870,871,-846,-761]]},{"type":"Polygon","id":33007,"arcs":[[872,873,874,875,-866]]},{"type":"Polygon","id":27163,"arcs":[[-778,876,877,878,879,-843,-774]]},{"type":"Polygon","id":55017,"arcs":[[-854,880,881,882,-801,-804]]},{"type":"Polygon","id":41047,"arcs":[[-835,-785,883,884,885,-837]]},{"type":"Polygon","id":16003,"arcs":[[886,887,888,889,-688,-504]]},{"type":"Polygon","id":27073,"arcs":[[-851,890,891,892,-868,-817]]},{"type":"MultiPolygon","id":23009,"arcs":[[[893]],[[894]],[[895,-578,-791]]]},{"type":"Polygon","id":46107,"arcs":[[-811,896,897,898,-825,-808]]},{"type":"Polygon","id":46049,"arcs":[[-722,899,900,901,-897,-810]]},{"type":"Polygon","id":27053,"arcs":[[-820,-845,902,903,904,905,-839]]},{"type":"Polygon","id":46115,"arcs":[[-815,906,907,908,-900,-721]]},{"type":"Polygon","id":16085,"arcs":[[-790,909,910,911,-887,-503]]},{"type":"Polygon","id":46019,"arcs":[[-703,912,913,914,-656,-709]]},{"type":"Polygon","id":55109,"arcs":[[915,916,-877,-777]]},{"type":"Polygon","id":55033,"arcs":[[-883,917,918,919,-916,-802]]},{"type":"Polygon","id":26009,"arcs":[[920,921,922,923,924]]},{"type":"Polygon","id":26137,"arcs":[[925,926,-921,927,-747]]},{"type":"Polygon","id":26119,"arcs":[[928,929,-926,-746,-798]]},{"type":"Polygon","id":46025,"arcs":[[930,931,932,933,-907,-814]]},{"type":"Polygon","id":46029,"arcs":[[-870,934,935,-931,-813]]},{"type":"Polygon","id":27023,"arcs":[[-848,936,937,-891,-850]]},{"type":"Polygon","id":27123,"arcs":[[-880,938,-903,-844]]},{"type":"Polygon","id":55073,"arcs":[[-823,-834,939,940,941,942,-852]]},{"type":"Polygon","id":55078,"arcs":[[-861,943,-832]]},{"type":"Polygon","id":41001,"arcs":[[-890,944,945,946,-734,-689]]},{"type":"Polygon","id":41053,"arcs":[[-886,947,948,949,-753,-838]]},{"type":"Polygon","id":41069,"arcs":[[-730,950,951,952,-783,-739]]},{"type":"Polygon","id":41041,"arcs":[[-950,953,954,955,-754]]},{"type":"Polygon","id":46093,"arcs":[[-830,956,957,958,-913,-702]]},{"type":"Polygon","id":55019,"arcs":[[-943,959,960,961,-881,-853]]},{"type":"Polygon","id":55115,"arcs":[[-944,-860,962,963,964,-940,-833]]},{"type":"Polygon","id":50011,"arcs":[[965,966,967,968,969]]},{"type":"Polygon","id":50009,"arcs":[[970,971,972,973,-875]]},{"type":"Polygon","id":50013,"arcs":[[974,975,976,-969]]},{"type":"Polygon","id":36019,"arcs":[[977,978,979,980,-976]]},{"type":"Polygon","id":50019,"arcs":[[-973,981,982,-966,983]]},{"type":"Polygon","id":56029,"arcs":[[984,985,986,987,988,-629,-635,-806]]},{"type":"Polygon","id":36089,"arcs":[[989,990,991,992,993,994]]},{"type":"Polygon","id":56003,"arcs":[[995,996,997,-985,-805,-678]]},{"type":"Polygon","id":56005,"arcs":[[998,999,1000,1001,1002,-743]]},{"type":"Polygon","id":56033,"arcs":[[-744,-1003,1003,-996,-677]]},{"type":"Polygon","id":36033,"arcs":[[-980,1004,1005,-990,1006]]},{"type":"Polygon","id":56011,"arcs":[[-657,-915,1007,1008,-999,-742]]},{"type":"Polygon","id":41023,"arcs":[[-735,-947,1009,1010,1011,-951,-729,-686]]},{"type":"Polygon","id":27085,"arcs":[[-841,1012,1013,1014,-871]]},{"type":"Polygon","id":27019,"arcs":[[-906,1015,1016,-1013,-840]]},{"type":"Polygon","id":46039,"arcs":[[-893,1017,1018,1019,1020,-935,-869]]},{"type":"Polygon","id":27173,"arcs":[[1021,1022,1023,1024,-1018,-892,-938]]},{"type":"Polygon","id":27037,"arcs":[[-879,1025,1026,1027,1028,-904,-939]]},{"type":"Polygon","id":46119,"arcs":[[1029,1030,1031,-826,-899]]},{"type":"Polygon","id":46069,"arcs":[[-902,1032,1033,1034,1035,-1030,-898]]},{"type":"Polygon","id":46059,"arcs":[[-909,1036,1037,1038,-1033,-901]]},{"type":"Polygon","id":27129,"arcs":[[-872,-1015,1039,1040,1041,1042,-1022,-937,-847]]},{"type":"Polygon","id":16037,"arcs":[[1043,1044,1045,1046,-910,-789]]},{"type":"Polygon","id":55093,"arcs":[[-920,1047,1048,-1026,-878,-917]]},{"type":"Polygon","id":26001,"arcs":[[1049,1050,1051,1052]]},{"type":"Polygon","id":26079,"arcs":[[1053,1054,1055,-922]]},{"type":"Polygon","id":26039,"arcs":[[1056,1057,-1054,-927]]},{"type":"Polygon","id":55035,"arcs":[[-962,1058,1059,1060,1061,-918,-882]]},{"type":"Polygon","id":26135,"arcs":[[-1052,1062,-1057,-930]]},{"type":"Polygon","id":16087,"arcs":[[1063,1064,1065,-945,-889]]},{"type":"Polygon","id":41031,"arcs":[[-953,1066,1067,1068,-884,-784]]},{"type":"Polygon","id":27139,"arcs":[[-1029,1069,1070,1071,-1016,-905]]},{"type":"Polygon","id":46057,"arcs":[[-1021,1072,1073,-932,-936]]},{"type":"Polygon","id":50015,"arcs":[[1074,1075,1076,-967,-983]]},{"type":"Polygon","id":41043,"arcs":[[-1069,1077,1078,1079,-948,-885]]},{"type":"Polygon","id":46117,"arcs":[[-1032,1080,1081,1082,1083,-827]]},{"type":"Polygon","id":26019,"arcs":[[1084,1085,1086,1087]]},{"type":"Polygon","id":50005,"arcs":[[1088,1089,1090,-1075,-982,-972]]},{"type":"MultiPolygon","id":23027,"arcs":[[[-580,1091,1092,1093,1094,-546]]]},{"type":"Polygon","id":16043,"arcs":[[1095,1096,1097,1098,1099,-713,-736,-631]]},{"type":"Polygon","id":46055,"arcs":[[1100,1101,1102,-957,-829,-1084]]},{"type":"Polygon","id":50007,"arcs":[[-1077,1103,1104,1105,-978,-975,-968]]},{"type":"Polygon","id":41003,"arcs":[[-1080,1106,-954,-949]]},{"type":"Polygon","id":23011,"arcs":[[1107,1108,1109,1110,-793,-547,-1095]]},{"type":"Polygon","id":27143,"arcs":[[-1017,-1072,1111,1112,-1040,-1014]]},{"type":"Polygon","id":27049,"arcs":[[1113,1114,1115,1116,1117,-1027,-1049]]},{"type":"Polygon","id":27127,"arcs":[[1118,1119,1120,1121,-1023,-1043]]},{"type":"Polygon","id":55097,"arcs":[[1122,1123,1124,1125,-941]]},{"type":"Polygon","id":55141,"arcs":[[-1126,1126,1127,1128,-960,-942]]},{"type":"MultiPolygon","id":55009,"arcs":[[[-857,1129]],[[-859,1130,1131,1132,1133,1134,-963]]]},{"type":"Polygon","id":55091,"arcs":[[-1062,1135,1136,-1114,-1048,-919]]},{"type":"Polygon","id":55135,"arcs":[[1137,1138,1139,-1123,-965]]},{"type":"Polygon","id":55061,"arcs":[[1140,1141,-1132,1142,1143]]},{"type":"Polygon","id":56039,"arcs":[[1144,1145,1146,1147,1148,-1096,-630,-989]]},{"type":"Polygon","id":46005,"arcs":[[-934,1149,1150,1151,-1037,-908]]},{"type":"Polygon","id":27081,"arcs":[[1152,1153,1154,-1019,-1025]]},{"type":"Polygon","id":27083,"arcs":[[-1122,1155,1156,-1153,-1024]]},{"type":"Polygon","id":46081,"arcs":[[-959,1157,1158,-1008,-914]]},{"type":"Polygon","id":55011,"arcs":[[-1061,1159,1160,1161,-1136]]},{"type":"Polygon","id":55121,"arcs":[[1162,1163,1164,-1160,-1060]]},{"type":"Polygon","id":55053,"arcs":[[-961,-1129,1165,1166,1167,-1163,-1059]]},{"type":"Polygon","id":55087,"arcs":[[-964,-1135,1168,1169,-1138]]},{"type":"Polygon","id":16033,"arcs":[[-1100,1170,1171,-787,-714]]},{"type":"Polygon","id":56019,"arcs":[[-1002,1172,1173,1174,-997,-1004]]},{"type":"Polygon","id":41013,"arcs":[[-952,-1012,1175,1176,-1067]]},{"type":"Polygon","id":46065,"arcs":[[-1036,1177,-1081,-1031]]},{"type":"Polygon","id":36031,"arcs":[[1178,1179,1180,1181,-1005,-979,-1106]]},{"type":"Polygon","id":27079,"arcs":[[1182,1183,1184,1185,-1112,-1071]]},{"type":"Polygon","id":27131,"arcs":[[-1028,-1118,1186,1187,1188,-1183,-1070]]},{"type":"Polygon","id":46077,"arcs":[[-1074,1189,1190,1191,1192,-1150,-933]]},{"type":"Polygon","id":46011,"arcs":[[-1155,1193,1194,1195,-1190,-1073,-1020]]},{"type":"Polygon","id":26101,"arcs":[[1196,1197,1198,1199,-1086]]},{"type":"Polygon","id":26165,"arcs":[[1200,1201,1202,-1197,1203]]},{"type":"Polygon","id":26143,"arcs":[[1204,1205,1206,1207,-1058]]},{"type":"Polygon","id":26113,"arcs":[[-1208,1208,1209,-1201,-1055]]},{"type":"Polygon","id":26069,"arcs":[[1210,1211,1212,-1051]]},{"type":"Polygon","id":26129,"arcs":[[-1213,1213,1214,-1205,-1063]]},{"type":"Polygon","id":16045,"arcs":[[-912,1215,1216,1217,1218,-1064,-888]]},{"type":"Polygon","id":46103,"arcs":[[-1103,1219,1220,1221,1222,-1158,-958]]},{"type":"Polygon","id":50023,"arcs":[[1223,1224,-1104,-1076,-1091]]},{"type":"Polygon","id":27015,"arcs":[[1225,1226,1227,1228,-1119,-1042]]},{"type":"Polygon","id":23001,"arcs":[[1229,1230,-862,-794,-1111]]},{"type":"Polygon","id":27103,"arcs":[[1231,-1226,-1041,-1113,-1186]]},{"type":"Polygon","id":27157,"arcs":[[-1137,-1162,1232,1233,-1115]]},{"type":"Polygon","id":41045,"arcs":[[-1066,1234,1235,1236,1237,1238,-1010,-946]]},{"type":"Polygon","id":33009,"arcs":[[1239,1240,1241,1242,1243,1244,-1089,-971,-874]]},{"type":"Polygon","id":41017,"arcs":[[-1177,1245,1246,1247,1248,-1078,-1068]]},{"type":"MultiPolygon","id":36045,"arcs":[[[1249,-994,1250,1251,1252,1253,1254]]]},{"type":"MultiPolygon","id":23013,"arcs":[[[1255,1256,-1093]]]},{"type":"Polygon","id":16015,"arcs":[[-1047,1257,1258,-1216,-911]]},{"type":"MultiPolygon","id":23015,"arcs":[[[-1094,-1257,1259,1260,1261,-1108]]]},{"type":"Polygon","id":55071,"arcs":[[-1142,1262,1263,1264,-1133]]},{"type":"Polygon","id":50001,"arcs":[[-1225,1265,1266,1267,1268,-1179,-1105]]},{"type":"MultiPolygon","id":41039,"arcs":[[[-1249,1269,1270,1271,-955,-1107,-1079]]]},{"type":"Polygon","id":33003,"arcs":[[-865,1272,1273,1274,-1240,-873]]},{"type":"Polygon","id":27013,"arcs":[[-1185,1275,1276,1277,1278,-1227,-1232]]},{"type":"Polygon","id":55057,"arcs":[[1279,1280,1281,1282,-1166,-1128]]},{"type":"Polygon","id":55001,"arcs":[[-1125,1283,1284,1285,-1280,-1127]]},{"type":"Polygon","id":55137,"arcs":[[-1140,1286,1287,1288,-1284,-1124]]},{"type":"Polygon","id":55139,"arcs":[[1289,1290,1291,-1287,-1139,-1170]]},{"type":"Polygon","id":55015,"arcs":[[-1134,-1265,1292,1293,-1290,-1169]]},{"type":"Polygon","id":16023,"arcs":[[-1172,1294,1295,1296,-1044,-788]]},{"type":"Polygon","id":50017,"arcs":[[-1245,1297,-1266,-1224,-1090]]},{"type":"Polygon","id":36049,"arcs":[[1298,1299,-1251,-993,1300]]},{"type":"Polygon","id":46085,"arcs":[[1301,1302,1303,1304,1305,-1082,-1178,-1035,1306]]},{"type":"Polygon","id":27117,"arcs":[[-1157,1307,1308,1309,-1194,-1154]]},{"type":"Polygon","id":27101,"arcs":[[-1121,1310,1311,1312,-1308,-1156]]},{"type":"Polygon","id":46073,"arcs":[[-1152,1313,1314,1315,1316,-1038]]},{"type":"Polygon","id":27147,"arcs":[[1317,1318,1319,1320,-1188]]},{"type":"Polygon","id":27039,"arcs":[[1321,1322,-1318,-1187,-1117]]},{"type":"Polygon","id":46101,"arcs":[[-1310,1323,1324,-1195]]},{"type":"Polygon","id":27161,"arcs":[[-1321,1325,1326,-1276,-1184,-1189]]},{"type":"Polygon","id":46017,"arcs":[[-1317,1327,-1307,-1034,-1039]]},{"type":"Polygon","id":46111,"arcs":[[-1193,1328,1329,1330,1331,-1314,-1151]]},{"type":"Polygon","id":27109,"arcs":[[-1234,1332,1333,1334,-1322,-1116]]},{"type":"Polygon","id":27033,"arcs":[[-1229,1335,1336,1337,-1311,-1120]]},{"type":"Polygon","id":46079,"arcs":[[-1196,-1325,1338,1339,1340,-1191]]},{"type":"Polygon","id":46097,"arcs":[[-1341,1341,1342,-1329,-1192]]},{"type":"Polygon","id":27169,"arcs":[[-1161,-1165,1343,1344,1345,-1333,-1233]]},{"type":"Polygon","id":56045,"arcs":[[-1159,-1223,1346,1347,1348,-1000,-1009]]},{"type":"Polygon","id":26105,"arcs":[[1349,1350,1351,-1199]]},{"type":"MultiPolygon","id":23005,"arcs":[[[-1231,1352,1353,1354,1355,1356,-863]]]},{"type":"Polygon","id":46075,"arcs":[[-1306,1357,1358,-1101,-1083]]},{"type":"Polygon","id":56043,"arcs":[[-1175,1359,1360,1361,-986,-998]]},{"type":"MultiPolygon","id":23023,"arcs":[[[-1355,1362]],[[1363,1364,-1261,1365]],[[1366,-1353,-1230,-1110]]]},{"type":"Polygon","id":26085,"arcs":[[-1198,-1203,1367,1368,-1350]]},{"type":"Polygon","id":26133,"arcs":[[-1210,1369,1370,-1368,-1202]]},{"type":"Polygon","id":26035,"arcs":[[-1207,1371,1372,-1370,-1209]]},{"type":"Polygon","id":26051,"arcs":[[1373,1374,1375,-1372,-1206,-1215]]},{"type":"Polygon","id":55081,"arcs":[[-1283,1376,1377,-1167]]},{"type":"Polygon","id":16075,"arcs":[[-1219,1378,-1235,-1065]]},{"type":"Polygon","id":36041,"arcs":[[1379,1380,1381,1382,-991,-1006,-1182]]},{"type":"Polygon","id":27165,"arcs":[[-1279,1383,1384,-1336,-1228]]},{"type":"Polygon","id":16039,"arcs":[[-1046,1385,1386,1387,1388,1389,1390,-1258]]},{"type":"Polygon","id":36043,"arcs":[[-1383,1391,1392,1393,1394,-1301,-992]]},{"type":"Polygon","id":55063,"arcs":[[-1168,-1378,1395,1396,-1344,-1164]]},{"type":"Polygon","id":56017,"arcs":[[-1362,1397,-987]]},{"type":"Polygon","id":16051,"arcs":[[-1099,1398,1399,1400,-1295,-1171]]},{"type":"Polygon","id":41025,"arcs":[[-1239,1401,1402,1403,-1246,-1176,-1011]]},{"type":"Polygon","id":56013,"arcs":[[-1398,-1361,1404,1405,1406,1407,-1145,-988]]},{"type":"Polygon","id":26017,"arcs":[[1408,1409,1410,1411,-1375,1412]]},{"type":"Polygon","id":46071,"arcs":[[-1359,1413,1414,1415,-1220,-1102]]},{"type":"Polygon","id":16013,"arcs":[[-1297,1416,1417,1418,1419,1420,1421,-1386,-1045]]},{"type":"Polygon","id":16081,"arcs":[[-1149,1422,1423,-1097]]},{"type":"Polygon","id":55047,"arcs":[[-1292,1424,1425,1426,1427,-1288]]},{"type":"Polygon","id":55077,"arcs":[[-1428,1428,-1285,-1289]]},{"type":"Polygon","id":50027,"arcs":[[-1244,1429,1430,1431,1432,-1267,-1298]]},{"type":"MultiPolygon","id":41019,"arcs":[[[1433,1434,1435,1436,1437,1438,-1271]]]},{"type":"Polygon","id":55039,"arcs":[[1439,1440,1441,-1425,-1291,-1294]]},{"type":"Polygon","id":46003,"arcs":[[-1332,1442,1443,1444,1445,-1315]]},{"type":"Polygon","id":46015,"arcs":[[-1446,1446,-1302,-1328,-1316]]},{"type":"Polygon","id":16065,"arcs":[[-1424,1447,-1399,-1098]]},{"type":"Polygon","id":55117,"arcs":[[1448,1449,1450,-1440,-1293,-1264]]},{"type":"Polygon","id":16027,"arcs":[[-1218,1451,1452,-1236,-1379]]},{"type":"Polygon","id":46095,"arcs":[[-1305,1453,1454,-1414,-1358]]},{"type":"Polygon","id":16025,"arcs":[[1455,1456,-1387,-1422]]},{"type":"Polygon","id":50021,"arcs":[[1457,1458,-1268,-1433]]},{"type":"Polygon","id":46033,"arcs":[[1459,1460,1461,-1347,-1222]]},{"type":"Polygon","id":27133,"arcs":[[-1313,1462,1463,1464,-1309]]},{"type":"Polygon","id":27105,"arcs":[[-1338,1465,1466,1467,-1463,-1312]]},{"type":"Polygon","id":27047,"arcs":[[1468,1469,1470,1471,-1326,-1320]]},{"type":"Polygon","id":27099,"arcs":[[-1335,1472,1473,1474,1475,-1469,-1319,-1323]]},{"type":"Polygon","id":27055,"arcs":[[-1397,1476,1477,1478,1479,-1345]]},{"type":"Polygon","id":46035,"arcs":[[1480,1481,1482,-1443,-1331]]},{"type":"Polygon","id":46061,"arcs":[[-1343,1483,1484,-1481,-1330]]},{"type":"Polygon","id":27045,"arcs":[[-1346,-1480,1485,1486,-1473,-1334]]},{"type":"Polygon","id":27063,"arcs":[[-1385,1487,1488,1489,1490,-1466,-1337]]},{"type":"Polygon","id":27043,"arcs":[[-1327,-1472,1491,1492,1493,-1277]]},{"type":"Polygon","id":46099,"arcs":[[1494,1495,1496,1497,-1339,-1324,-1465]]},{"type":"Polygon","id":27091,"arcs":[[-1278,-1494,1498,1499,-1488,-1384]]},{"type":"Polygon","id":46087,"arcs":[[-1340,-1498,1500,1501,-1484,-1342]]},{"type":"Polygon","id":26111,"arcs":[[-1412,1502,1503,1504,-1376]]},{"type":"Polygon","id":26127,"arcs":[[1505,1506,1507,-1351]]},{"type":"Polygon","id":26073,"arcs":[[-1505,1508,1509,1510,-1373]]},{"type":"MultiPolygon","id":23031,"arcs":[[[1511,1512,-1273,-864,-1357]]]},{"type":"Polygon","id":26107,"arcs":[[-1511,1513,1514,-1371]]},{"type":"Polygon","id":26123,"arcs":[[-1515,1515,1516,1517,-1506,-1369]]},{"type":"Polygon","id":36115,"arcs":[[-1459,1518,1519,1520,1521,-1180,-1269]]},{"type":"Polygon","id":16001,"arcs":[[-1259,-1391,1522,-1452,-1217]]},{"type":"Polygon","id":36113,"arcs":[[1523,-1380,-1181,-1522]]},{"type":"Polygon","id":46123,"arcs":[[1524,1525,1526,-1454,-1304]]},{"type":"Polygon","id":33001,"arcs":[[1527,1528,-1241,-1275]]},{"type":"Polygon","id":26157,"arcs":[[1529,1530,1531,1532,1533,-1410,1534]]},{"type":"Polygon","id":55123,"arcs":[[1535,1536,1537,1538,-1477,-1396,-1377,-1282]]},{"type":"MultiPolygon","id":36075,"arcs":[[[-1254,1539]],[[-1300,1540,1541,1542,1543,1544,-1252]]]},{"type":"Polygon","id":46113,"arcs":[[1545,1546,1547,1548,-1460,-1221,-1416]]},{"type":"Polygon","id":26151,"arcs":[[1549,1550,-1531,1551,1552]]},{"type":"Polygon","id":16073,"arcs":[[-1523,-1390,1553,1554,1555,-1237,-1453]]},{"type":"Polygon","id":55021,"arcs":[[1556,1557,1558,-1286,-1429,-1427]]},{"type":"Polygon","id":55111,"arcs":[[-1559,1559,1560,1561,-1536,-1281]]},{"type":"Polygon","id":55027,"arcs":[[1562,1563,1564,1565,-1557,-1426,-1442]]},{"type":"Polygon","id":16019,"arcs":[[-1448,-1423,-1148,1566,1567,1568,-1400]]},{"type":"Polygon","id":16011,"arcs":[[1569,1570,1571,-1417,-1296,-1401,-1569]]},{"type":"Polygon","id":41035,"arcs":[[1572,1573,1574,1575,-1434,-1270,-1248]]},{"type":"Polygon","id":41037,"arcs":[[1576,1577,-1573,-1247,-1404]]},{"type":"Polygon","id":36065,"arcs":[[1578,1579,-1541,-1299,-1395]]},{"type":"Polygon","id":33013,"arcs":[[-1529,1580,1581,1582,1583,-1242]]},{"type":"MultiPolygon","id":41011,"arcs":[[[1584,1585,-1438]]]},{"type":"Polygon","id":33019,"arcs":[[-1584,1586,1587,1588,-1430,-1243]]},{"type":"Polygon","id":33017,"arcs":[[-1513,1589,1590,-1581,-1528,-1274]]},{"type":"Polygon","id":26145,"arcs":[[-1534,1591,1592,1593,-1503,-1411]]},{"type":"Polygon","id":55103,"arcs":[[1594,1595,1596,-1537,-1562]]},{"type":"Polygon","id":55089,"arcs":[[1597,1598,1599,-1450]]},{"type":"Polygon","id":55131,"arcs":[[-1451,-1600,1600,-1563,-1441]]},{"type":"Polygon","id":19189,"arcs":[[1601,1602,1603,-1492,-1471]]},{"type":"Polygon","id":19109,"arcs":[[1604,1605,1606,-1499,-1493,-1604,1607]]},{"type":"Polygon","id":19059,"arcs":[[1608,1609,-1490,1610]]},{"type":"Polygon","id":19063,"arcs":[[1611,-1611,-1489,-1500,-1607]]},{"type":"Polygon","id":19195,"arcs":[[1612,-1602,-1470,-1476,1613]]},{"type":"Polygon","id":19143,"arcs":[[1614,1615,-1467,-1491,-1610]]},{"type":"Polygon","id":56027,"arcs":[[-1462,1616,1617,1618,1619,1620,-1348]]},{"type":"Polygon","id":19131,"arcs":[[1621,1622,-1614,-1475,1623]]},{"type":"Polygon","id":19119,"arcs":[[1624,1625,-1495,-1464,-1468,-1616]]},{"type":"Polygon","id":19089,"arcs":[[1626,1627,-1624,-1474,-1487]]},{"type":"Polygon","id":19005,"arcs":[[-1539,1628,1629,1630,-1478]]},{"type":"Polygon","id":19191,"arcs":[[1631,1632,-1627,-1486,-1479,-1631]]},{"type":"Polygon","id":46083,"arcs":[[-1626,1633,1634,1635,1636,-1496]]},{"type":"Polygon","id":56009,"arcs":[[-1621,1637,1638,1639,1640,-1173,-1001,-1349]]},{"type":"Polygon","id":46023,"arcs":[[-1445,1641,1642,1643,1644,1645,1646,-1447]]},{"type":"Polygon","id":46125,"arcs":[[-1497,-1637,1647,1648,1649,-1501]]},{"type":"Polygon","id":46067,"arcs":[[-1502,-1650,1650,1651,-1643,1652,-1482,-1485]]},{"type":"Polygon","id":56025,"arcs":[[-1174,-1641,1653,-1405,-1360]]},{"type":"Polygon","id":46043,"arcs":[[-1483,-1653,-1642,-1444]]},{"type":"Polygon","id":46053,"arcs":[[-1647,1654,1655,-1525,-1303]]},{"type":"Polygon","id":46047,"arcs":[[-1549,1656,1657,-1617,-1461]]},{"type":"Polygon","id":26121,"arcs":[[-1518,1658,1659,1660,-1507]]},{"type":"Polygon","id":26117,"arcs":[[-1510,1661,1662,1663,-1516,-1514]]},{"type":"Polygon","id":56035,"arcs":[[1664,1665,-1146,-1408]]},{"type":"Polygon","id":26057,"arcs":[[-1504,-1594,1666,1667,-1662,-1509]]},{"type":"Polygon","id":55023,"arcs":[[-1597,1668,1669,-1629,-1538]]},{"type":"Polygon","id":36011,"arcs":[[1670,1671,1672,1673,1674,1675,-1544]]},{"type":"Polygon","id":36091,"arcs":[[-1521,1676,1677,1678,1679,1680,-1381,-1524]]},{"type":"Polygon","id":46007,"arcs":[[1681,1682,-1546,-1415]]},{"type":"Polygon","id":46121,"arcs":[[-1527,1683,-1682,-1455]]},{"type":"Polygon","id":36073,"arcs":[[1684,1685,1686,1687]]},{"type":"Polygon","id":36063,"arcs":[[-1687,1688,1689,1690]]},{"type":"Polygon","id":36055,"arcs":[[1691,1692,1693,1694,-1685,1695]]},{"type":"MultiPolygon","id":36117,"arcs":[[[1696,1697,-1692,1698,-1675]]]},{"type":"Polygon","id":26087,"arcs":[[1699,1700,1701,1702,-1532,-1551]]},{"type":"Polygon","id":56023,"arcs":[[-1666,1703,1704,1705,1706,1707,-1567,-1147]]},{"type":"Polygon","id":50003,"arcs":[[-1432,1708,1709,1710,1711,-1519,-1458]]},{"type":"Polygon","id":55025,"arcs":[[-1566,1712,1713,1714,1715,-1560,-1558]]},{"type":"Polygon","id":26081,"arcs":[[1716,1717,1718,1719,-1659,-1517,-1664]]},{"type":"Polygon","id":36035,"arcs":[[-1681,1720,-1392,-1382]]},{"type":"MultiPolygon","id":33015,"arcs":[[[1721,1722,1723,-1582,-1591]]]},{"type":"Polygon","id":36067,"arcs":[[1724,1725,-1671,-1543]]},{"type":"Polygon","id":50025,"arcs":[[-1589,1726,1727,-1709,-1431]]},{"type":"Polygon","id":19167,"arcs":[[1728,1729,1730,-1634,-1625]]},{"type":"Polygon","id":19141,"arcs":[[1731,1732,-1729,-1615]]},{"type":"Polygon","id":19033,"arcs":[[-1623,1733,1734,1735,-1613]]},{"type":"Polygon","id":19081,"arcs":[[-1736,1736,-1608,-1603]]},{"type":"Polygon","id":19147,"arcs":[[-1606,1737,1738,-1612]]},{"type":"Polygon","id":19041,"arcs":[[-1739,1739,-1732,-1609]]},{"type":"Polygon","id":26049,"arcs":[[-1703,1740,1741,1742,-1592,-1533]]},{"type":"Polygon","id":19037,"arcs":[[-1633,1743,1744,1745,-1628]]},{"type":"Polygon","id":19067,"arcs":[[-1746,1746,-1734,-1622]]},{"type":"Polygon","id":55049,"arcs":[[-1561,-1716,1747,1748,1749,-1595]]},{"type":"Polygon","id":55043,"arcs":[[-1750,1750,1751,1752,1753,-1669,-1596]]},{"type":"Polygon","id":33011,"arcs":[[-1724,1754,1755,1756,1757,-1587,-1583]]},{"type":"Polygon","id":26139,"arcs":[[-1720,1758,1759,-1660]]},{"type":"Polygon","id":16063,"arcs":[[1760,1761,1762,-1456,-1421]]},{"type":"Polygon","id":16047,"arcs":[[-1763,1763,1764,-1388,-1457]]},{"type":"Polygon","id":55055,"arcs":[[1765,1766,1767,-1713,-1565]]},{"type":"Polygon","id":16067,"arcs":[[1768,1769,-1761,-1420]]},{"type":"Polygon","id":55133,"arcs":[[-1601,1770,1771,1772,-1766,-1564]]},{"type":"Polygon","id":55079,"arcs":[[1773,1774,-1771,-1599]]},{"type":"Polygon","id":36053,"arcs":[[-1580,1775,1776,1777,-1725,-1542]]},{"type":"Polygon","id":33005,"arcs":[[-1758,1778,1779,-1727,-1588]]},{"type":"Polygon","id":46135,"arcs":[[1780,1781,1782,1783,-1651,-1649]]},{"type":"Polygon","id":46009,"arcs":[[-1784,1784,-1644,-1652]]},{"type":"Polygon","id":26155,"arcs":[[-1743,1785,1786,1787,-1667,-1593]]},{"type":"Polygon","id":36037,"arcs":[[1788,1789,1790,-1689,-1686,-1695]]},{"type":"Polygon","id":26067,"arcs":[[1791,1792,1793,-1717,-1663]]},{"type":"Polygon","id":26037,"arcs":[[-1788,1794,1795,-1792,-1668]]},{"type":"Polygon","id":16077,"arcs":[[-1572,1796,1797,1798,-1418]]},{"type":"MultiPolygon","id":36029,"arcs":[[[1799]],[[-1791,1800,1801,1802,1803,-1690]]]},{"type":"Polygon","id":46127,"arcs":[[-1731,1804,1805,1806,1807,1808,-1635]]},{"type":"Polygon","id":46027,"arcs":[[-1809,1809,1810,-1781,-1648,-1636]]},{"type":"Polygon","id":19065,"arcs":[[1811,1812,1813,-1744,-1632]]},{"type":"Polygon","id":19043,"arcs":[[-1670,-1754,1814,1815,-1812,-1630]]},{"type":"Polygon","id":36057,"arcs":[[-1680,1816,1817,1818,-1393,-1721]]},{"type":"Polygon","id":36069,"arcs":[[1819,1820,1821,1822,-1693,-1698]]},{"type":"Polygon","id":16005,"arcs":[[1823,1824,1825,-1797,-1571]]},{"type":"Polygon","id":16029,"arcs":[[-1568,-1708,1826,1827,-1824,-1570]]},{"type":"Polygon","id":36099,"arcs":[[1828,1829,1830,-1820,-1697,-1674]]},{"type":"Polygon","id":31165,"arcs":[[1831,1832,1833,-1618,-1658,1834]]},{"type":"Polygon","id":31161,"arcs":[[1835,1836,1837,1838,1839,-1547,1840]]},{"type":"Polygon","id":31045,"arcs":[[1841,-1835,-1657,-1548,-1840]]},{"type":"Polygon","id":31015,"arcs":[[1842,1843,1844,-1655,-1646]]},{"type":"Polygon","id":31103,"arcs":[[-1845,1845,1846,1847,-1526,-1656]]},{"type":"Polygon","id":31031,"arcs":[[-1684,-1848,1848,1849,1850,1851,1852,-1841,-1683]]},{"type":"Polygon","id":41029,"arcs":[[-1576,1853,1854,-1435]]},{"type":"Polygon","id":36051,"arcs":[[-1823,1855,1856,1857,-1789,-1694]]},{"type":"Polygon","id":36083,"arcs":[[-1712,1858,1859,1860,-1677,-1520]]},{"type":"Polygon","id":36093,"arcs":[[1861,1862,-1817,-1679]]},{"type":"Polygon","id":41015,"arcs":[[-1437,1863,1864,1865,-1585]]},{"type":"Polygon","id":16083,"arcs":[[1866,1867,1868,-1554,-1389,-1765]]},{"type":"Polygon","id":19149,"arcs":[[1869,1870,-1805,-1730]]},{"type":"Polygon","id":19021,"arcs":[[1871,1872,1873,-1740]]},{"type":"Polygon","id":19035,"arcs":[[-1874,1874,1875,-1870,-1733]]},{"type":"Polygon","id":19151,"arcs":[[1876,1877,1878,-1872,-1738]]},{"type":"Polygon","id":19197,"arcs":[[1879,1880,1881,1882,-1737]]},{"type":"Polygon","id":19091,"arcs":[[-1883,1883,-1877,-1605]]},{"type":"Polygon","id":19069,"arcs":[[1884,1885,-1880,-1735]]},{"type":"Polygon","id":19023,"arcs":[[1886,1887,1888,-1885,-1747]]},{"type":"Polygon","id":19017,"arcs":[[-1814,1889,-1887,-1745]]},{"type":"Polygon","id":36077,"arcs":[[-1819,1890,1891,1892,-1776,-1579,-1394]]},{"type":"Polygon","id":31089,"arcs":[[1893,1894,1895,1896,1897,1898,-1844]]},{"type":"Polygon","id":26099,"arcs":[[1899,1900,1901,-1701,1902]]},{"type":"Polygon","id":26125,"arcs":[[1903,1904,1905,-1741,-1702,-1902]]},{"type":"MultiPolygon","id":25009,"arcs":[[[1906,1907,1908,-1755,-1723]]]},{"type":"Polygon","id":31107,"arcs":[[-1785,-1783,1909,1910,1911,-1894,-1843,-1645]]},{"type":"Polygon","id":31027,"arcs":[[-1811,1912,1913,1914,-1910,-1782]]},{"type":"Polygon","id":36121,"arcs":[[-1858,1915,1916,-1801,-1790]]},{"type":"Polygon","id":55045,"arcs":[[1917,1918,1919,1920,-1748,-1715]]},{"type":"Polygon","id":16053,"arcs":[[-1770,1921,-1867,-1764,-1762]]},{"type":"Polygon","id":55105,"arcs":[[-1768,1922,1923,1924,-1918,-1714]]},{"type":"Polygon","id":31017,"arcs":[[1925,1926,1927,-1849,-1847]]},{"type":"Polygon","id":55127,"arcs":[[-1773,1928,1929,1930,1931,-1923,-1767]]},{"type":"Polygon","id":55101,"arcs":[[1932,1933,-1929,-1772,-1775]]},{"type":"Polygon","id":36095,"arcs":[[-1863,1934,1935,1936,-1891,-1818]]},{"type":"Polygon","id":36001,"arcs":[[-1861,1937,-1935,-1862,-1678]]},{"type":"Polygon","id":55065,"arcs":[[-1921,1938,1939,-1751,-1749]]},{"type":"Polygon","id":31149,"arcs":[[1940,-1926,-1846,-1899]]},{"type":"Polygon","id":36023,"arcs":[[1941,1942,-1672,-1726,-1778,1943,1944]]},{"type":"Polygon","id":41033,"arcs":[[-1436,-1855,1945,1946,-1864]]},{"type":"Polygon","id":26093,"arcs":[[-1906,1947,1948,1949,-1786,-1742]]},{"type":"Polygon","id":26065,"arcs":[[-1950,1950,1951,-1795,-1787]]},{"type":"Polygon","id":26045,"arcs":[[-1796,-1952,1952,1953,1954,-1793]]},{"type":"Polygon","id":26015,"arcs":[[-1955,1955,1956,1957,-1718,-1794]]},{"type":"Polygon","id":26005,"arcs":[[-1719,-1958,1958,1959,1960,-1759]]},{"type":"Polygon","id":36123,"arcs":[[1961,1962,-1821,-1831]]},{"type":"Polygon","id":31051,"arcs":[[-1808,1963,1964,1965,-1913,-1810]]},{"type":"Polygon","id":25003,"arcs":[[1966,1967,1968,1969,1970,1971,-1859,-1711]]},{"type":"Polygon","id":36017,"arcs":[[-1893,1972,1973,-1944,-1777]]},{"type":"Polygon","id":25011,"arcs":[[1974,-1967,-1710,-1728,-1780,1975]]},{"type":"Polygon","id":25017,"arcs":[[1976,1977,1978,1979,1980,1981,1982,1983,1984,-1756,-1909]]},{"type":"Polygon","id":25027,"arcs":[[-1757,-1985,1985,1986,1987,1988,1989,1990,-1976,-1779]]},{"type":"Polygon","id":16031,"arcs":[[-1799,1991,1992,1993,-1868,-1922,-1769,-1419]]},{"type":"Polygon","id":19061,"arcs":[[1994,1995,1996,1997,-1815,-1753]]},{"type":"Polygon","id":55059,"arcs":[[1998,1999,2000,-1930,-1934]]},{"type":"Polygon","id":19055,"arcs":[[-1998,2001,2002,2003,-1816]]},{"type":"Polygon","id":19187,"arcs":[[-1882,2004,2005,2006,2007,-1878,-1884]]},{"type":"Polygon","id":19019,"arcs":[[-2004,2008,2009,2010,-1813]]},{"type":"Polygon","id":19013,"arcs":[[-2011,2011,2012,2013,-1888,-1890]]},{"type":"Polygon","id":36109,"arcs":[[-1943,2014,2015,2016,-1829,-1673]]},{"type":"Polygon","id":56015,"arcs":[[-1834,2017,2018,2019,2020,-1619]]},{"type":"Polygon","id":56031,"arcs":[[-2021,2021,2022,-1638,-1620]]},{"type":"Polygon","id":16007,"arcs":[[-1707,2023,2024,-1827]]},{"type":"Polygon","id":36101,"arcs":[[-1822,-1963,2025,2026,2027,2028,2029,-1856]]},{"type":"Polygon","id":36013,"arcs":[[2030,2031,2032,2033,-1803]]},{"type":"Polygon","id":19193,"arcs":[[-1876,2034,2035,2036,2037,-1806,-1871]]},{"type":"Polygon","id":19161,"arcs":[[2038,2039,2040,2041,-1873]]},{"type":"Polygon","id":19093,"arcs":[[-2042,2042,-2035,-1875]]},{"type":"Polygon","id":19025,"arcs":[[-2008,2043,2044,-2039,-1879]]},{"type":"Polygon","id":19079,"arcs":[[2045,2046,2047,-2005,-1881]]},{"type":"Polygon","id":19083,"arcs":[[-1886,2048,2049,2050,-2046]]},{"type":"Polygon","id":19075,"arcs":[[-2014,2051,2052,-2049,-1889]]},{"type":"Polygon","id":25015,"arcs":[[-1991,2053,-1968,-1975]]},{"type":"Polygon","id":36097,"arcs":[[-2017,2054,-2026,-1962,-1830]]},{"type":"Polygon","id":36009,"arcs":[[-1917,2055,2056,2057,-2031,-1802]]},{"type":"Polygon","id":31043,"arcs":[[-1807,-2038,2058,-1964]]},{"type":"Polygon","id":36003,"arcs":[[-1857,-2030,2059,2060,-2056,-1916]]},{"type":"Polygon","id":36025,"arcs":[[-1937,2061,2062,2063,2064,2065,-1973,-1892]]},{"type":"Polygon","id":17085,"arcs":[[-1940,2066,2067,2068,-1995,-1752]]},{"type":"Polygon","id":36021,"arcs":[[2069,2070,2071,-1860,-1972]]},{"type":"Polygon","id":17177,"arcs":[[2072,-2067,-1939,-1920,2073,2074]]},{"type":"Polygon","id":17201,"arcs":[[2075,2076,-2074,-1919,-1925]]},{"type":"Polygon","id":16071,"arcs":[[-1826,2077,2078,2079,-1992,-1798]]},{"type":"Polygon","id":17111,"arcs":[[2080,2081,2082,2083,2084,-1931,-2001]]},{"type":"Polygon","id":17007,"arcs":[[-1932,-2085,2085,-2076,-1924]]},{"type":"Polygon","id":17097,"arcs":[[2086,-2081,-2000,2087]]},{"type":"Polygon","id":36039,"arcs":[[2088,-2062,-1936,-1938,-2072]]},{"type":"MultiPolygon","id":25025,"arcs":[[[2089,2090,-1983,2091,-1981]],[[-1979,2092]],[[2093,-1977,-1908]]]},{"type":"Polygon","id":31139,"arcs":[[2094,2095,2096,-1911,-1915]]},{"type":"Polygon","id":31013,"arcs":[[2097,2098,-1832,-1842,-1839]]},{"type":"Polygon","id":31003,"arcs":[[-2097,2099,2100,2101,-1895,-1912]]},{"type":"Polygon","id":26161,"arcs":[[2102,2103,2104,-1948,-1905,2105]]},{"type":"Polygon","id":56007,"arcs":[[-1640,2106,2107,2108,2109,2110,-1406,-1654]]},{"type":"Polygon","id":56001,"arcs":[[-2023,2111,2112,2113,-2107,-1639]]},{"type":"Polygon","id":16041,"arcs":[[-2025,2114,-2078,-1825,-1828]]},{"type":"Polygon","id":26075,"arcs":[[-1949,-2105,2115,2116,2117,-1953,-1951]]},{"type":"Polygon","id":26025,"arcs":[[-2118,2118,2119,2120,-1956,-1954]]},{"type":"Polygon","id":26159,"arcs":[[2121,2122,2123,2124,-1960]]},{"type":"Polygon","id":26077,"arcs":[[-2121,2125,-2122,-1959,-1957]]},{"type":"Polygon","id":36007,"arcs":[[-2066,2126,2127,2128,-1945,-1974]]},{"type":"Polygon","id":36107,"arcs":[[-2129,2129,2130,2131,-2015,-1942]]},{"type":"Polygon","id":19097,"arcs":[[-2069,2132,2133,2134,-1996]]},{"type":"Polygon","id":31179,"arcs":[[-1966,2135,2136,2137,-2095,-1914]]},{"type":"MultiPolygon","id":25021,"arcs":[[[2138,2139]],[[-2091,2140,2141,2142,2143,-1986,-1984]],[[-1982,-2092]]]},{"type":"Polygon","id":25013,"arcs":[[-1990,2144,2145,2146,-1969,-2054]]},{"type":"MultiPolygon","id":25023,"arcs":[[[-2140,2147,2148,2149,2150,-2142,2151]]]},{"type":"Polygon","id":19011,"arcs":[[2152,2153,2154,-2012,-2010]]},{"type":"Polygon","id":19113,"arcs":[[-2003,2155,2156,2157,-2153,-2009]]},{"type":"Polygon","id":19171,"arcs":[[-2155,2158,2159,-2052,-2013]]},{"type":"Polygon","id":19105,"arcs":[[-1997,-2135,2160,2161,-2156,-2002]]},{"type":"Polygon","id":36015,"arcs":[[-2016,-2132,2162,2163,-2027,-2055]]},{"type":"Polygon","id":31173,"arcs":[[-2037,2164,2165,2166,-2136,-1965,-2059]]},{"type":"Polygon","id":56037,"arcs":[[-1407,-2111,2167,2168,2169,2170,-1704,-1665]]},{"type":"Polygon","id":42049,"arcs":[[2171,2172,2173,2174,-2033]]},{"type":"Polygon","id":26021,"arcs":[[2175,2176,2177,2178,-2124]]},{"type":"Polygon","id":19133,"arcs":[[2179,2180,2181,-2165,-2036]]},{"type":"Polygon","id":19127,"arcs":[[-2160,2182,2183,-2050,-2053]]},{"type":"Polygon","id":19027,"arcs":[[-2045,2184,2185,2186,2187,-2040]]},{"type":"Polygon","id":19047,"arcs":[[-2043,-2041,-2188,2188,2189,-2180]]},{"type":"Polygon","id":19015,"arcs":[[-2048,2190,2191,2192,2193,-2006]]},{"type":"Polygon","id":19073,"arcs":[[-2194,2194,2195,-2185,-2044,-2007]]},{"type":"Polygon","id":19169,"arcs":[[-2051,-2184,2196,2197,-2191,-2047]]},{"type":"Polygon","id":17141,"arcs":[[2198,2199,2200,2201,-2075,-2077]]},{"type":"Polygon","id":17015,"arcs":[[-2202,2202,2203,-2133,-2068,-2073]]},{"type":"Polygon","id":36111,"arcs":[[-2071,2204,2205,2206,-2063,-2089]]},{"type":"Polygon","id":17031,"arcs":[[-2087,2207,2208,2209,2210,2211,-2082]]},{"type":"Polygon","id":17037,"arcs":[[2212,2213,2214,2215,-2199,-2086,-2084]]},{"type":"Polygon","id":17089,"arcs":[[-2212,2216,2217,-2213,-2083]]},{"type":"Polygon","id":31075,"arcs":[[2218,2219,2220,-1836,-1853]]},{"type":"MultiPolygon","id":25005,"arcs":[[[2221,2222,2223,2224,2225,-2143,-2151]]]},{"type":"Polygon","id":31091,"arcs":[[2226,2227,2228,-2219,-1852]]},{"type":"Polygon","id":31039,"arcs":[[-2167,2229,2230,2231,2232,-2137]]},{"type":"Polygon","id":31119,"arcs":[[2233,2234,2235,-2100,-2096]]},{"type":"Polygon","id":31167,"arcs":[[-2233,2236,2237,-2234,-2138]]},{"type":"Polygon","id":31171,"arcs":[[2238,2239,2240,-2227,-1851]]},{"type":"Polygon","id":31183,"arcs":[[2241,2242,2243,2244,-1896,-2102]]},{"type":"Polygon","id":31009,"arcs":[[-1850,-1928,2245,2246,2247,-2239]]},{"type":"Polygon","id":31115,"arcs":[[2248,2249,-2246,-1927,-1941,-1898]]},{"type":"Polygon","id":31071,"arcs":[[-2245,2250,2251,-2249,-1897]]},{"type":"Polygon","id":26091,"arcs":[[2252,2253,2254,2255,-2116,-2104]]},{"type":"Polygon","id":36027,"arcs":[[-1971,2256,2257,2258,2259,-2205,-2070]]},{"type":"MultiPolygon","id":25001,"arcs":[[[2260,-2149,2261]]]},{"type":"Polygon","id":26023,"arcs":[[2262,2263,2264,2265,-2120]]},{"type":"Polygon","id":26059,"arcs":[[-2256,2266,2267,2268,-2263,-2119,-2117]]},{"type":"Polygon","id":26149,"arcs":[[-2266,2269,2270,2271,-2126]]},{"type":"Polygon","id":26027,"arcs":[[-2272,2272,2273,-2176,-2123]]},{"type":"Polygon","id":9005,"arcs":[[-2147,2274,2275,2276,-2257,-1970]]},{"type":"Polygon","id":31021,"arcs":[[2277,2278,2279,-2230,-2166,-2182]]},{"type":"Polygon","id":9003,"arcs":[[2280,2281,2282,2283,-2275,-2146]]},{"type":"Polygon","id":9013,"arcs":[[-1989,2284,2285,-2281,-2145]]},{"type":"Polygon","id":19045,"arcs":[[-2204,2286,2287,2288,2289,-2161,-2134]]},{"type":"Polygon","id":9015,"arcs":[[2290,2291,2292,-2285,-1988]]},{"type":"Polygon","id":44007,"arcs":[[-2226,2293,2294,2295,-2291,-1987,-2144]]},{"type":"Polygon","id":36105,"arcs":[[2296,2297,2298,-2064,-2207]]},{"type":"Polygon","id":6093,"arcs":[[-1575,2299,2300,2301,2302,2303,-1946,-1854]]},{"type":"Polygon","id":31069,"arcs":[[-2221,2304,2305,2306,2307,2308,-1837]]},{"type":"Polygon","id":31123,"arcs":[[-2309,2309,2310,2311,-2098,-1838]]},{"type":"Polygon","id":49005,"arcs":[[2312,2313,2314,-2079,-2115]]},{"type":"Polygon","id":31157,"arcs":[[-2312,2315,-2018,-1833,-2099]]},{"type":"Polygon","id":49033,"arcs":[[-1706,2316,2317,2318,2319,-2313,-2024]]},{"type":"Polygon","id":42015,"arcs":[[-2163,-2131,2320,2321,2322,2323,2324]]},{"type":"Polygon","id":42117,"arcs":[[-2164,-2325,2325,2326,-2028]]},{"type":"Polygon","id":49003,"arcs":[[2327,2328,2329,-1993,-2080,-2315]]},{"type":"Polygon","id":32013,"arcs":[[2330,2331,2332,-1402,-1238,-1556,2333]]},{"type":"Polygon","id":32007,"arcs":[[-1994,-2330,2334,2335,2336,2337,-2334,-1555,-1869]]},{"type":"Polygon","id":42083,"arcs":[[-2061,2338,2339,2340,2341,-2057]]},{"type":"Polygon","id":42105,"arcs":[[-2029,-2327,2342,2343,2344,-2339,-2060]]},{"type":"Polygon","id":6015,"arcs":[[-2304,2345,2346,-1865,-1947]]},{"type":"Polygon","id":42127,"arcs":[[-2065,-2299,2347,2348,2349,2350,-2127]]},{"type":"Polygon","id":42115,"arcs":[[2351,2352,-2321,-2130,-2128,-2351]]},{"type":"Polygon","id":42123,"arcs":[[-2342,2353,2354,2355,-2172,-2032,-2058]]},{"type":"Polygon","id":6049,"arcs":[[-1578,2356,2357,2358,-2300,-1574]]},{"type":"Polygon","id":32031,"arcs":[[2359,2360,2361,2362,2363,2364,2365,2366,2367,-2357,-1577,-1403,-2333]]},{"type":"Polygon","id":17043,"arcs":[[2368,2369,-2217,-2211]]},{"type":"Polygon","id":39007,"arcs":[[2370,2371,2372,2373,2374,-2174]]},{"type":"Polygon","id":19031,"arcs":[[-2162,-2290,2375,2376,2377,-2157]]},{"type":"Polygon","id":17195,"arcs":[[-2201,2378,2379,2380,2381,-2287,-2203]]},{"type":"Polygon","id":31011,"arcs":[[-2236,2382,2383,2384,-2242,-2101]]},{"type":"Polygon","id":17103,"arcs":[[-2216,2385,2386,-2379,-2200]]},{"type":"Polygon","id":19085,"arcs":[[-2190,2387,2388,2389,-2278,-2181]]},{"type":"Polygon","id":19095,"arcs":[[2390,2391,2392,2393,-2154]]},{"type":"Polygon","id":19049,"arcs":[[2394,2395,2396,-2195,-2193]]},{"type":"Polygon","id":19165,"arcs":[[2397,2398,2399,-2388,-2189]]},{"type":"Polygon","id":19009,"arcs":[[2400,2401,-2398,-2187]]},{"type":"Polygon","id":19157,"arcs":[[-2394,2402,2403,2404,-2159]]},{"type":"Polygon","id":19153,"arcs":[[-2198,2405,2406,2407,-2395,-2192]]},{"type":"Polygon","id":19099,"arcs":[[-2183,-2405,2408,2409,-2406,-2197]]},{"type":"Polygon","id":19077,"arcs":[[-2397,2410,-2401,-2186,-2196]]},{"type":"Polygon","id":19103,"arcs":[[-2378,2411,2412,2413,-2391,-2158]]},{"type":"Polygon","id":39085,"arcs":[[2414,2415,2416,-2374]]},{"type":"Polygon","id":42039,"arcs":[[-2356,2417,2418,2419,-2371,-2173]]},{"type":"Polygon","id":17161,"arcs":[[2420,2421,2422,2423,2424,-2288,-2382]]},{"type":"Polygon","id":44001,"arcs":[[2425,-2294,-2225]]},{"type":"Polygon","id":19163,"arcs":[[-2425,2426,-2376,-2289]]},{"type":"Polygon","id":44003,"arcs":[[2427,2428,2429,-2292,-2296]]},{"type":"Polygon","id":18039,"arcs":[[2430,2431,2432,2433,2434,-2273,-2271]]},{"type":"Polygon","id":18141,"arcs":[[2435,2436,2437,-2177,-2274,-2435]]},{"type":"Polygon","id":18091,"arcs":[[2438,2439,2440,-2178,-2438]]},{"type":"Polygon","id":18151,"arcs":[[2441,2442,-2264,-2269,2443]]},{"type":"Polygon","id":18087,"arcs":[[2444,-2431,-2270,-2265,-2443]]},{"type":"Polygon","id":31037,"arcs":[[-2232,2445,2446,2447,-2237]]},{"type":"Polygon","id":31141,"arcs":[[-2448,2448,2449,2450,2451,-2383,-2235,-2238]]},{"type":"Polygon","id":31053,"arcs":[[-2280,2452,2453,2454,2455,-2446,-2231]]},{"type":"Polygon","id":31117,"arcs":[[-2241,2456,2457,2458,2459,-2228]]},{"type":"Polygon","id":31005,"arcs":[[-2460,2460,-2305,-2220,-2229]]},{"type":"Polygon","id":31077,"arcs":[[2461,2462,2463,2464,-2243,-2385]]},{"type":"Polygon","id":31041,"arcs":[[-2252,2465,2466,2467,2468,2469,2470,-2247,-2250]]},{"type":"Polygon","id":31113,"arcs":[[-2248,-2471,2471,-2457,-2240]]},{"type":"Polygon","id":31175,"arcs":[[-2244,-2465,2472,-2466,-2251]]},{"type":"MultiPolygon","id":39095,"arcs":[[[2473,2474,2475,2476,-2254,2477,2478,2479,2480,2481,2482]]]},{"type":"Polygon","id":17197,"arcs":[[-2210,2483,2484,2485,2486,-2369]]},{"type":"MultiPolygon","id":39123,"arcs":[[[2487,2488,2489,2490,2491,-2474]]]},{"type":"Polygon","id":17093,"arcs":[[-2487,2492,2493,-2214,-2218,-2370]]},{"type":"Polygon","id":39051,"arcs":[[2494,2495,-2267,-2255,-2477]]},{"type":"Polygon","id":39055,"arcs":[[2496,2497,2498,-2415,-2373]]},{"type":"MultiPolygon","id":9011,"arcs":[[[-2430,2499,2500,2501,-2282,-2286,-2293]]]},{"type":"Polygon","id":18089,"arcs":[[2502,2503,2504,2505,-2484,-2209,2506]]},{"type":"Polygon","id":18127,"arcs":[[2507,-2503,2508,-2440]]},{"type":"Polygon","id":39171,"arcs":[[2509,2510,2511,-2444,-2268,-2496]]},{"type":"Polygon","id":31007,"arcs":[[-2311,2512,2513,2514,-2019,-2316]]},{"type":"Polygon","id":31177,"arcs":[[-2390,2515,2516,-2453,-2279]]},{"type":"MultiPolygon","id":44005,"arcs":[[[2517]],[[2518,-2223]]]},{"type":"MultiPolygon","id":9001,"arcs":[[[2519,2520,2521,2522,-2258,-2277]]]},{"type":"Polygon","id":56021,"arcs":[[-2020,-2515,2523,2524,2525,-2112,-2022]]},{"type":"MultiPolygon","id":44009,"arcs":[[[-2500,-2429,2526]]]},{"type":"Polygon","id":42131,"arcs":[[-2353,2527,2528,2529,-2322]]},{"type":"MultiPolygon","id":9007,"arcs":[[[2530,2531]],[[-2502,2532,2533,-2283]]]},{"type":"Polygon","id":9009,"arcs":[[-2534,2534,-2531,2535,-2520,-2276,-2284]]},{"type":"Polygon","id":42069,"arcs":[[-2350,2536,2537,-2528,-2352]]},{"type":"Polygon","id":36071,"arcs":[[-2260,2538,2539,2540,2541,2542,-2297,-2206]]},{"type":"Polygon","id":17099,"arcs":[[-2494,2543,2544,2545,2546,2547,2548,-2386,-2215]]},{"type":"Polygon","id":39035,"arcs":[[-2499,2549,2550,2551,2552,2553,-2416]]},{"type":"Polygon","id":42047,"arcs":[[2554,2555,2556,2557,-2341]]},{"type":"Polygon","id":42053,"arcs":[[-2558,2558,2559,2560,-2354]]},{"type":"Polygon","id":42121,"arcs":[[2561,2562,2563,-2418,-2355,-2561]]},{"type":"MultiPolygon","id":39043,"arcs":[[[2564,2565,2566,2567]]]},{"type":"Polygon","id":39173,"arcs":[[-2492,2568,2569,2570,2571,-2475]]},{"type":"Polygon","id":42023,"arcs":[[-2345,2572,2573,-2555,-2340]]},{"type":"Polygon","id":42103,"arcs":[[-2543,2574,2575,-2348,-2298]]},{"type":"Polygon","id":19139,"arcs":[[-2377,-2427,-2424,2576,-2412]]},{"type":"Polygon","id":42081,"arcs":[[2577,2578,2579,2580,2581,2582,-2343,-2326,-2324]]},{"type":"Polygon","id":42113,"arcs":[[-2578,-2323,-2530,2583,2584]]},{"type":"Polygon","id":17011,"arcs":[[-2549,2585,2586,2587,2588,-2380,-2387]]},{"type":"Polygon","id":17073,"arcs":[[-2589,2589,2590,2591,-2421,-2381]]},{"type":"Polygon","id":56041,"arcs":[[-2171,2592,-2317,-1705]]},{"type":"Polygon","id":18033,"arcs":[[2593,2594,2595,-2442,-2512]]},{"type":"Polygon","id":18113,"arcs":[[-2596,2596,2597,2598,-2432,-2445]]},{"type":"Polygon","id":31125,"arcs":[[2599,2600,-2462,-2384,-2452]]},{"type":"Polygon","id":36079,"arcs":[[2601,2602,-2539,-2259,-2523,2603]]},{"type":"MultiPolygon","id":25007,"arcs":[[[2604]]]},{"type":"Polygon","id":39093,"arcs":[[-2553,2605,2606,2607,-2565,2608]]},{"type":"Polygon","id":19183,"arcs":[[2609,2610,2611,2612,-2392,-2414]]},{"type":"Polygon","id":19181,"arcs":[[2613,2614,2615,2616,-2408]]},{"type":"Polygon","id":19107,"arcs":[[-2613,2617,2618,2619,-2403,-2393]]},{"type":"Polygon","id":19121,"arcs":[[2620,2621,2622,-2396,-2617]]},{"type":"Polygon","id":19123,"arcs":[[-2620,2623,2624,2625,-2409,-2404]]},{"type":"Polygon","id":19125,"arcs":[[-2626,2626,2627,-2614,-2407,-2410]]},{"type":"Polygon","id":19155,"arcs":[[-2400,2628,2629,2630,2631,2632,-2516,-2389]]},{"type":"Polygon","id":19029,"arcs":[[-2402,2633,2634,2635,-2629,-2399]]},{"type":"Polygon","id":19001,"arcs":[[-2411,-2623,2636,2637,-2634]]},{"type":"Polygon","id":39155,"arcs":[[-2420,2638,2639,2640,-2497,-2372]]},{"type":"MultiPolygon","id":39143,"arcs":[[[-2489,2641]],[[2642,-2567,2643,2644,-2569,-2491]]]},{"type":"Polygon","id":42085,"arcs":[[-2564,2645,2646,2647,-2639,-2419]]},{"type":"Polygon","id":39069,"arcs":[[-2476,-2572,2648,2649,-2510,-2495]]},{"type":"Polygon","id":18099,"arcs":[[-2434,2650,2651,2652,-2436]]},{"type":"Polygon","id":42035,"arcs":[[-2583,2653,2654,2655,-2573,-2344]]},{"type":"MultiPolygon","id":6023,"arcs":[[[-2303,2656,2657,2658,-2346]]]},{"type":"Polygon","id":17063,"arcs":[[2659,2660,-2544,-2493,-2486]]},{"type":"Polygon","id":31023,"arcs":[[-2456,2661,2662,2663,2664,-2449,-2447]]},{"type":"Polygon","id":31155,"arcs":[[2665,2666,2667,2668,-2662,-2455]]},{"type":"Polygon","id":18085,"arcs":[[-2599,2669,2670,2671,-2651,-2433]]},{"type":"Polygon","id":31033,"arcs":[[-2308,2672,2673,2674,2675,-2513,-2310]]},{"type":"Polygon","id":42031,"arcs":[[2676,2677,2678,-2562,-2560]]},{"type":"Polygon","id":49057,"arcs":[[-2314,-2320,2679,2680,-2328]]},{"type":"Polygon","id":18149,"arcs":[[-2653,2681,2682,-2439,-2437]]},{"type":"Polygon","id":42079,"arcs":[[2683,2684,2685,2686,-2584,-2529,-2538]]},{"type":"Polygon","id":39039,"arcs":[[2687,2688,2689,-2594,-2511,-2650]]},{"type":"Polygon","id":19115,"arcs":[[-2577,-2423,2690,2691,2692,-2610,-2413]]},{"type":"Polygon","id":31101,"arcs":[[2693,2694,2695,-2306,-2461,-2459]]},{"type":"Polygon","id":31111,"arcs":[[-2472,-2470,2696,2697,2698,2699,-2694,-2458]]},{"type":"MultiPolygon","id":25019,"arcs":[[[2700]]]},{"type":"Polygon","id":31143,"arcs":[[2701,2702,2703,-2450,-2665]]},{"type":"Polygon","id":31105,"arcs":[[-2676,2704,2705,-2524,-2514]]},{"type":"Polygon","id":31121,"arcs":[[-2704,2706,2707,2708,-2600,-2451]]},{"type":"Polygon","id":31093,"arcs":[[-2601,-2709,2709,2710,2711,-2463]]},{"type":"Polygon","id":31163,"arcs":[[-2464,-2712,2712,-2467,-2473]]},{"type":"Polygon","id":31055,"arcs":[[-2517,-2633,2713,-2666,-2454]]},{"type":"Polygon","id":49029,"arcs":[[2714,2715,2716,-2680,-2319]]},{"type":"Polygon","id":42065,"arcs":[[2717,2718,2719,-2677,-2559,-2557]]},{"type":"Polygon","id":6105,"arcs":[[2720,2721,2722,-2657,-2302]]},{"type":"MultiPolygon","id":36119,"arcs":[[[2723,2724,2725,-2604,-2522,2726]]]},{"type":"Polygon","id":34037,"arcs":[[2727,2728,2729,2730,-2575,-2542]]},{"type":"Polygon","id":39153,"arcs":[[2731,2732,2733,2734,-2551]]},{"type":"Polygon","id":39133,"arcs":[[-2641,2735,2736,-2732,-2550,-2498]]},{"type":"Polygon","id":17131,"arcs":[[-2592,2737,2738,2739,2740,-2691,-2422]]},{"type":"Polygon","id":17155,"arcs":[[-2548,2741,-2586]]},{"type":"Polygon","id":36087,"arcs":[[2742,2743,-2540,-2603,2744]]},{"type":"Polygon","id":42037,"arcs":[[2745,2746,2747,-2579,-2585,-2687]]},{"type":"Polygon","id":17091,"arcs":[[-2506,2748,2749,2750,2751,-2660,-2485]]},{"type":"Polygon","id":18183,"arcs":[[2752,2753,2754,-2670,-2598]]},{"type":"MultiPolygon","id":36103,"arcs":[[[2755,2756]],[[2757,2758]]]},{"type":"Polygon","id":39077,"arcs":[[-2608,2759,2760,2761,2762,-2644,-2566]]},{"type":"Polygon","id":18073,"arcs":[[-2683,2763,2764,2765,2766,-2504,-2508]]},{"type":"Polygon","id":39103,"arcs":[[-2735,2767,2768,-2606,-2552]]},{"type":"Polygon","id":18003,"arcs":[[2769,2770,2771,2772,2773,-2753,-2597,-2595,-2690]]},{"type":"Polygon","id":39147,"arcs":[[-2763,2774,2775,2776,-2570,-2645]]},{"type":"Polygon","id":42033,"arcs":[[2777,2778,2779,2780,-2718,-2556,-2574,-2656]]},{"type":"Polygon","id":49043,"arcs":[[-2593,-2170,2781,2782,2783,2784,-2715,-2318]]},{"type":"Polygon","id":42027,"arcs":[[2785,2786,2787,2788,-2778,-2655]]},{"type":"Polygon","id":42089,"arcs":[[-2731,2789,2790,2791,-2684,-2537,-2349,-2576]]},{"type":"Polygon","id":39125,"arcs":[[2792,2793,-2770,-2689]]},{"type":"Polygon","id":17175,"arcs":[[-2588,2794,2795,2796,-2590]]},{"type":"Polygon","id":31049,"arcs":[[-2696,2797,2798,-2673,-2307]]},{"type":"Polygon","id":18111,"arcs":[[2799,2800,-2749,-2505,-2767]]},{"type":"Polygon","id":34031,"arcs":[[2801,2802,-2728,-2541,-2744,2803]]},{"type":"Polygon","id":31153,"arcs":[[-2632,2804,2805,-2667,-2714]]},{"type":"Polygon","id":6035,"arcs":[[-2368,2806,2807,2808,-2358]]},{"type":"Polygon","id":6089,"arcs":[[-2359,-2809,2809,2810,-2721,-2301]]},{"type":"Polygon","id":42097,"arcs":[[2811,-2747,2812,2813,2814,2815,2816,-2581]]},{"type":"Polygon","id":18049,"arcs":[[2817,2818,2819,2820,-2652,-2672]]},{"type":"Polygon","id":31081,"arcs":[[2821,2822,2823,2824,-2707,-2703]]},{"type":"Polygon","id":18131,"arcs":[[-2821,2825,2826,-2764,-2682]]},{"type":"Polygon","id":42093,"arcs":[[-2748,-2812,-2580]]},{"type":"Polygon","id":42019,"arcs":[[-2679,2827,2828,2829,2830,-2646,-2563]]},{"type":"Polygon","id":42005,"arcs":[[-2720,2831,2832,2833,-2828,-2678]]},{"type":"Polygon","id":39063,"arcs":[[-2777,2834,2835,2836,2837,-2571]]},{"type":"Polygon","id":39137,"arcs":[[-2838,2838,2839,-2793,-2688,-2649]]},{"type":"Polygon","id":19101,"arcs":[[-2612,2840,2841,2842,-2618]]},{"type":"Polygon","id":19087,"arcs":[[-2693,2843,2844,2845,-2841,-2611]]},{"type":"Polygon","id":19179,"arcs":[[-2843,2846,2847,-2624,-2619]]},{"type":"Polygon","id":19039,"arcs":[[2848,2849,2850,-2621,-2616]]},{"type":"Polygon","id":19117,"arcs":[[-2628,2851,2852,-2849,-2615]]},{"type":"Polygon","id":19135,"arcs":[[-2848,2853,-2852,-2627,-2625]]},{"type":"Polygon","id":19129,"arcs":[[2854,2855,2856,-2805,-2631]]},{"type":"Polygon","id":19137,"arcs":[[-2636,2857,2858,-2855,-2630]]},{"type":"Polygon","id":19003,"arcs":[[-2638,2859,2860,-2858,-2635]]},{"type":"Polygon","id":19175,"arcs":[[-2622,-2851,2861,-2860,-2637]]},{"type":"Polygon","id":49011,"arcs":[[-2717,2862,2863,-2681]]},{"type":"Polygon","id":17095,"arcs":[[-2797,2864,2865,2866,-2738,-2591]]},{"type":"Polygon","id":42119,"arcs":[[2867,2868,-2786,-2654,-2582,-2817]]},{"type":"Polygon","id":17123,"arcs":[[-2742,-2547,2869,2870,-2795,-2587]]},{"type":"Polygon","id":34003,"arcs":[[2871,2872,-2804,-2743,2873]]},{"type":"Polygon","id":39099,"arcs":[[-2648,2874,2875,2876,-2736,-2640]]},{"type":"Polygon","id":42025,"arcs":[[-2792,2877,2878,2879,-2685]]},{"type":"Polygon","id":42073,"arcs":[[-2831,2880,2881,-2875,-2647]]},{"type":"Polygon","id":17105,"arcs":[[-2752,2882,2883,2884,-2545,-2661]]},{"type":"Polygon","id":34041,"arcs":[[2885,2886,2887,2888,-2790,-2730]]},{"type":"Polygon","id":34027,"arcs":[[2889,2890,2891,2892,-2886,-2729,-2803]]},{"type":"Polygon","id":49045,"arcs":[[2893,2894,2895,2896,-2335,-2329,-2864]]},{"type":"Polygon","id":19057,"arcs":[[-2741,2897,2898,-2844,-2692]]},{"type":"Polygon","id":17071,"arcs":[[2899,2900,2901,2902,-2898,-2740]]},{"type":"Polygon","id":17187,"arcs":[[-2867,2903,2904,-2900,-2739]]},{"type":"Polygon","id":39005,"arcs":[[-2769,2905,2906,2907,2908,-2760,-2607]]},{"type":"Polygon","id":31025,"arcs":[[-2857,2909,2910,2911,-2668,-2806]]},{"type":"Polygon","id":31185,"arcs":[[2912,2913,2914,-2822,-2702]]},{"type":"Polygon","id":31079,"arcs":[[-2708,-2825,2915,2916,-2710]]},{"type":"Polygon","id":31047,"arcs":[[2917,2918,2919,2920,-2697,-2469]]},{"type":"Polygon","id":31019,"arcs":[[-2711,-2917,2921,2922,2923,-2918,-2468,-2713]]},{"type":"Polygon","id":31159,"arcs":[[2924,2925,-2913,-2664]]},{"type":"Polygon","id":31109,"arcs":[[-2912,2926,2927,2928,-2925,-2663,-2669]]},{"type":"Polygon","id":18169,"arcs":[[-2755,2929,2930,2931,-2818,-2671]]},{"type":"Polygon","id":17075,"arcs":[[-2801,2932,2933,2934,-2750]]},{"type":"Polygon","id":18069,"arcs":[[-2774,2935,2936,-2930,-2754]]},{"type":"MultiPolygon","id":8123,"arcs":[[[2937]],[[2938,2939,2940,2941,2942,-2525,-2706,2943]]]},{"type":"Polygon","id":31135,"arcs":[[-2700,2944,2945,2946,2947,-2798,-2695]]},{"type":"Polygon","id":8107,"arcs":[[2948,2949,2950,2951,2952,-2109,2953]]},{"type":"Polygon","id":8057,"arcs":[[2954,-2954,-2108,-2114,2955]]},{"type":"Polygon","id":8081,"arcs":[[-2953,2956,2957,2958,-2168,-2110]]},{"type":"Polygon","id":8075,"arcs":[[2959,2960,2961,-2944,-2705,-2675,2962,2963]]},{"type":"Polygon","id":8115,"arcs":[[2964,-2963,-2674,-2799,-2948]]},{"type":"Polygon","id":49009,"arcs":[[2965,2966,-2782,-2169,-2959]]},{"type":"Polygon","id":32011,"arcs":[[2967,2968,2969,-2337]]},{"type":"Polygon","id":32015,"arcs":[[-2970,2970,2971,2972,-2331,-2338]]},{"type":"Polygon","id":8069,"arcs":[[2973,2974,-2956,-2113,-2526,-2943]]},{"type":"Polygon","id":18103,"arcs":[[-2932,2975,2976,2977,-2819]]},{"type":"Polygon","id":39033,"arcs":[[2978,2979,2980,2981,-2775,-2762]]},{"type":"Polygon","id":17053,"arcs":[[2982,2983,2984,-2883,-2751,-2935]]},{"type":"Polygon","id":39139,"arcs":[[-2909,2985,2986,-2979,-2761]]},{"type":"Polygon","id":39175,"arcs":[[-2982,2987,2988,-2835,-2776]]},{"type":"Polygon","id":39169,"arcs":[[-2734,2989,2990,-2906,-2768]]},{"type":"Polygon","id":39161,"arcs":[[-2840,2991,2992,2993,2994,-2771,-2794]]},{"type":"Polygon","id":39151,"arcs":[[-2877,2995,2996,2997,2998,-2990,-2733,-2737]]},{"type":"Polygon","id":17143,"arcs":[[-2871,2999,3000,3001,-2865,-2796]]},{"type":"Polygon","id":42095,"arcs":[[3002,3003,-2878,-2791,-2889]]},{"type":"Polygon","id":32027,"arcs":[[-2973,3004,-2360,-2332]]},{"type":"Polygon","id":42107,"arcs":[[-2880,3005,3006,3007,3008,-2813,-2746,-2686]]},{"type":"Polygon","id":39029,"arcs":[[-2882,3009,3010,3011,3012,-2996,-2876]]},{"type":"Polygon","id":17203,"arcs":[[3013,3014,-3000,-2870,-2546,-2885]]},{"type":"Polygon","id":18001,"arcs":[[3015,3016,3017,-2772,-2995]]},{"type":"Polygon","id":39003,"arcs":[[-2837,3018,3019,-2992,-2839]]},{"type":"Polygon","id":49035,"arcs":[[-2716,-2785,3020,3021,-2894,-2863]]},{"type":"MultiPolygon","id":36005,"arcs":[[[3022,3023,3024,-2725]]]},{"type":"Polygon","id":18179,"arcs":[[-3018,3025,3026,3027,-2936,-2773]]},{"type":"MultiPolygon","id":36059,"arcs":[[[-2756,3028]],[[-2758,3029,3030,3031,3032,3033]]]},{"type":"Polygon","id":18181,"arcs":[[3034,3035,3036,3037,-2765,-2827]]},{"type":"Polygon","id":18017,"arcs":[[-2820,-2978,3038,3039,-3035,-2826]]},{"type":"Polygon","id":42063,"arcs":[[-2781,3040,3041,-2832,-2719]]},{"type":"Polygon","id":34013,"arcs":[[-2802,-2873,3042,3043,3044,-2890]]},{"type":"Polygon","id":19071,"arcs":[[3045,3046,3047,-2910,-2856]]},{"type":"Polygon","id":19145,"arcs":[[3048,3049,3050,-3046,-2859]]},{"type":"Polygon","id":19173,"arcs":[[3051,3052,3053,-3049,-2861]]},{"type":"Polygon","id":19177,"arcs":[[-2846,3054,3055,3056,3057,-2842]]},{"type":"Polygon","id":19051,"arcs":[[-3058,3058,3059,3060,-2847]]},{"type":"Polygon","id":19159,"arcs":[[3061,3062,3063,-3052,-2862]]},{"type":"Polygon","id":19053,"arcs":[[3064,3065,3066,-3062,-2850]]},{"type":"Polygon","id":19185,"arcs":[[3067,3068,3069,-3065,-2853]]},{"type":"Polygon","id":19007,"arcs":[[-3061,3070,3071,-3068,-2854]]},{"type":"Polygon","id":42109,"arcs":[[-2816,3072,3073,-2868]]},{"type":"Polygon","id":36061,"arcs":[[3074,-3024]]},{"type":"Polygon","id":49047,"arcs":[[-2958,3075,3076,3077,3078,3079,3080,-2966]]},{"type":"Polygon","id":42007,"arcs":[[-2830,3081,3082,3083,-3010,-2881]]},{"type":"Polygon","id":42087,"arcs":[[3084,-2787,-2869,-3074,3085]]},{"type":"Polygon","id":49013,"arcs":[[-2967,-3081,3086,3087,3088,-2783]]},{"type":"Polygon","id":39065,"arcs":[[-2989,3089,3090,3091,3092,-3019,-2836]]},{"type":"Polygon","id":34017,"arcs":[[3093,-3043,-2872]]},{"type":"Polygon","id":19111,"arcs":[[-2899,-2903,3094,3095,-3055,-2845]]},{"type":"MultiPolygon","id":36081,"arcs":[[[3096,-3031]],[[3097,3098]],[[-3033,3099,3100,3101]]]},{"type":"Polygon","id":34019,"arcs":[[3102,3103,3104,-2887,-2893]]},{"type":"Polygon","id":42077,"arcs":[[3105,3106,3107,-3006,-2879,-3004]]},{"type":"Polygon","id":31131,"arcs":[[-3048,3108,3109,3110,-2927,-2911]]},{"type":"Polygon","id":34035,"arcs":[[3111,3112,3113,-3103,-2892]]},{"type":"Polygon","id":17113,"arcs":[[-2985,3114,3115,3116,3117,3118,-3014,-2884]]},{"type":"Polygon","id":8095,"arcs":[[-2947,3119,3120,-2964,-2965]]},{"type":"Polygon","id":17179,"arcs":[[-3119,3121,3122,3123,-3001,-3015]]},{"type":"Polygon","id":42061,"arcs":[[-3085,3124,3125,3126,3127,3128,-2788]]},{"type":"Polygon","id":42013,"arcs":[[3129,3130,-2779,-2789,-3129]]},{"type":"MultiPolygon","id":36047,"arcs":[[[3131,-3098]],[[3132,-3101]]]},{"type":"Polygon","id":18007,"arcs":[[-2766,-3038,3133,3134,3135,-2933,-2800]]},{"type":"Polygon","id":34039,"arcs":[[3136,3137,-3112,-2891,-3045]]},{"type":"Polygon","id":18015,"arcs":[[-3040,3138,3139,3140,-3036]]},{"type":"Polygon","id":39019,"arcs":[[-3013,3141,3142,3143,-2997]]},{"type":"Polygon","id":39107,"arcs":[[3144,3145,3146,3147,-3016,-2994]]},{"type":"Polygon","id":42021,"arcs":[[-3131,3148,3149,3150,-3041,-2780]]},{"type":"Polygon","id":17057,"arcs":[[-3002,-3124,3151,3152,3153,-2904,-2866]]},{"type":"Polygon","id":39117,"arcs":[[-2987,3154,3155,3156,-2980]]},{"type":"Polygon","id":39101,"arcs":[[-3157,3157,3158,-3090,-2988,-2981]]},{"type":"Polygon","id":31063,"arcs":[[-2921,3159,3160,3161,3162,3163,-2698]]},{"type":"Polygon","id":31001,"arcs":[[-2824,3164,3165,3166,-2922,-2916]]},{"type":"Polygon","id":31073,"arcs":[[3167,3168,-3160,-2920]]},{"type":"Polygon","id":31085,"arcs":[[-3164,3169,3170,3171,-2945,-2699]]},{"type":"Polygon","id":31029,"arcs":[[-3172,3172,3173,-3120,-2946]]},{"type":"Polygon","id":31059,"arcs":[[3174,3175,3176,3177,-2914]]},{"type":"Polygon","id":31151,"arcs":[[-2926,-2929,3178,3179,-3175]]},{"type":"Polygon","id":31035,"arcs":[[-3178,3180,3181,-3165,-2823,-2915]]},{"type":"Polygon","id":42067,"arcs":[[-2815,3182,3183,-3125,-3086,-3073]]},{"type":"Polygon","id":49051,"arcs":[[-3089,3184,-3021,-2784]]},{"type":"Polygon","id":31099,"arcs":[[3185,3186,3187,-2923,-3167]]},{"type":"Polygon","id":39011,"arcs":[[-3093,3188,3189,-3145,-2993,-3020]]},{"type":"Polygon","id":31137,"arcs":[[-2924,-3188,3190,3191,-3168,-2919]]},{"type":"Polygon","id":42129,"arcs":[[-3042,-3151,3192,3193,3194,3195,-2833]]},{"type":"Polygon","id":42011,"arcs":[[3196,3197,3198,3199,-3007,-3108]]},{"type":"Polygon","id":42003,"arcs":[[-2834,-3196,3200,-3082,-2829]]},{"type":"Polygon","id":39075,"arcs":[[-2999,3201,3202,3203,-2907,-2991]]},{"type":"Polygon","id":39157,"arcs":[[-3144,3204,3205,3206,-3202,-2998]]},{"type":"Polygon","id":42043,"arcs":[[-3009,3207,3208,3209,3210,3211,-2814]]},{"type":"Polygon","id":18053,"arcs":[[-3028,3212,3213,3214,3215,3216,-2976,-2931,-2937]]},{"type":"Polygon","id":36085,"arcs":[[3217]]},{"type":"Polygon","id":17067,"arcs":[[3218,3219,3220,3221,3222,-3095,-2902]]},{"type":"Polygon","id":54029,"arcs":[[3223,3224,3225,-3011,-3084]]},{"type":"Polygon","id":17109,"arcs":[[-3154,3226,-3219,-2901,-2905]]},{"type":"Polygon","id":42099,"arcs":[[3227,3228,-3183,-3212]]},{"type":"Polygon","id":29045,"arcs":[[-3223,3229,3230,3231,-3056,-3096]]},{"type":"Polygon","id":42017,"arcs":[[3232,3233,3234,3235,-3106,-3003,-2888,-3105]]},{"type":"Polygon","id":34023,"arcs":[[3236,3237,3238,-3113,-3138]]},{"type":"Polygon","id":29199,"arcs":[[-3232,3239,3240,3241,-3059,-3057]]},{"type":"Polygon","id":39081,"arcs":[[-3226,3242,3243,3244,3245,-3142,-3012]]},{"type":"Polygon","id":29197,"arcs":[[3246,3247,-3071,-3060,-3242]]},{"type":"Polygon","id":29171,"arcs":[[-3248,3248,3249,3250,-3069,-3072]]},{"type":"Polygon","id":29005,"arcs":[[-3051,3251,3252,3253,-3109,-3047]]},{"type":"Polygon","id":29129,"arcs":[[-3251,3254,3255,3256,-3066,-3070]]},{"type":"Polygon","id":29147,"arcs":[[-3054,3257,3258,3259,3260,-3252,-3050]]},{"type":"Polygon","id":49049,"arcs":[[-3088,3261,3262,3263,-2895,-3022,-3185]]},{"type":"Polygon","id":29081,"arcs":[[-3257,3264,3265,3266,3267,-3063,-3067]]},{"type":"Polygon","id":39083,"arcs":[[3268,3269,3270,-3155,-2986,-2908,-3204]]},{"type":"Polygon","id":18075,"arcs":[[3271,3272,3273,3274,-3026,-3017,-3148]]},{"type":"Polygon","id":29227,"arcs":[[-3268,3275,-3258,-3053,-3064]]},{"type":"Polygon","id":18009,"arcs":[[-3275,3276,-3213,-3027]]},{"type":"Polygon","id":18067,"arcs":[[3277,3278,-3139,-3039,-2977,-3217]]},{"type":"Polygon","id":18157,"arcs":[[-3141,3279,3280,3281,3282,-3134,-3037]]},{"type":"Polygon","id":31127,"arcs":[[3283,3284,3285,3286,-3110,-3254]]},{"type":"Polygon","id":42075,"arcs":[[-3208,-3008,-3200,3287]]},{"type":"Polygon","id":39091,"arcs":[[-3092,3288,3289,3290,-3189]]},{"type":"Polygon","id":8087,"arcs":[[-2962,3291,3292,-2939]]},{"type":"Polygon","id":31067,"arcs":[[3293,3294,3295,3296,3297,-3179,-2928]]},{"type":"Polygon","id":31097,"arcs":[[-3287,3298,-3294,-3111]]},{"type":"Polygon","id":39159,"arcs":[[3299,3300,3301,-3289,-3091,-3159,3302]]},{"type":"Polygon","id":17183,"arcs":[[-3136,3303,3304,3305,3306,-2983,-2934]]},{"type":"Polygon","id":8049,"arcs":[[3307,3308,3309,3310,3311,-2949,-2955,-2975]]},{"type":"Polygon","id":39149,"arcs":[[-3291,3312,3313,3314,-3146,-3190]]},{"type":"Polygon","id":42125,"arcs":[[-3201,-3195,3315,3316,3317,3318,3319,-3224,-3083]]},{"type":"Polygon","id":18171,"arcs":[[-3283,3320,3321,-3304,-3135]]},{"type":"Polygon","id":34025,"arcs":[[3322,3323,3324,-3238,3325]]},{"type":"Polygon","id":39031,"arcs":[[-3207,3326,3327,3328,-3269,-3203]]},{"type":"Polygon","id":42091,"arcs":[[3329,3330,3331,-3197,-3107,-3236]]},{"type":"Polygon","id":6103,"arcs":[[3332,3333,3334,3335,-2722,-2811]]},{"type":"Polygon","id":6063,"arcs":[[-2808,3336,3337,3338,-3333,-2810]]},{"type":"Polygon","id":39041,"arcs":[[-3271,3339,3340,-3303,-3158,-3156]]},{"type":"Polygon","id":8125,"arcs":[[-3174,3341,3342,3343,3344,-2960,-3121]]},{"type":"Polygon","id":8121,"arcs":[[-3345,3345,3346,3347,3348,-3292,-2961]]},{"type":"Polygon","id":17125,"arcs":[[3349,3350,3351,3352,-3152,-3123]]},{"type":"Polygon","id":39067,"arcs":[[-3246,3353,3354,-3205,-3143]]},{"type":"Polygon","id":18023,"arcs":[[-3279,3355,3356,3357,3358,-3280,-3140]]},{"type":"Polygon","id":34021,"arcs":[[-3239,-3325,3359,-3233,-3104,-3114]]},{"type":"Polygon","id":18159,"arcs":[[-3216,3360,3361,-3356,-3278]]},{"type":"Polygon","id":54009,"arcs":[[-3320,3362,-3243,-3225]]},{"type":"Polygon","id":17019,"arcs":[[-3307,3363,3364,-3115,-2984]]},{"type":"Polygon","id":29211,"arcs":[[3365,3366,3367,-3255,-3250]]},{"type":"Polygon","id":29075,"arcs":[[-3267,3368,3369,3370,-3259,-3276]]},{"type":"Polygon","id":18095,"arcs":[[3371,3372,3373,3374,-3361,-3215]]},{"type":"Polygon","id":18035,"arcs":[[-3277,-3274,3375,3376,-3372,-3214]]},{"type":"Polygon","id":18045,"arcs":[[-3282,3377,3378,3379,-3321]]},{"type":"Polygon","id":39037,"arcs":[[-3315,3380,3381,3382,3383,3384,-3272,-3147]]},{"type":"Polygon","id":31181,"arcs":[[-3182,3385,3386,3387,3388,-3186,-3166]]},{"type":"Polygon","id":31061,"arcs":[[-3389,3389,3390,3391,-3187]]},{"type":"Polygon","id":31129,"arcs":[[-3177,3392,3393,3394,-3386,-3181]]},{"type":"Polygon","id":31057,"arcs":[[3395,3396,3397,-3342,-3173,-3171]]},{"type":"Polygon","id":31065,"arcs":[[-3192,3398,3399,3400,3401,-3161,-3169]]},{"type":"Polygon","id":31169,"arcs":[[3402,3403,-3393,-3176]]},{"type":"Polygon","id":31095,"arcs":[[-3180,-3298,3404,-3403]]},{"type":"Polygon","id":31145,"arcs":[[-3402,3405,3406,3407,-3162]]},{"type":"Polygon","id":31087,"arcs":[[-3163,-3408,3408,-3396,-3170]]},{"type":"Polygon","id":31083,"arcs":[[-3392,3409,3410,-3399,-3191]]},{"type":"Polygon","id":29001,"arcs":[[-3241,3411,3412,3413,-3366,-3249,-3247]]},{"type":"Polygon","id":42041,"arcs":[[3414,3415,3416,-3228,-3211]]},{"type":"Polygon","id":17107,"arcs":[[3417,3418,3419,3420,-3350,-3122,-3118]]},{"type":"Polygon","id":42009,"arcs":[[-3128,3421,3422,3423,-3149,-3130]]},{"type":"Polygon","id":42071,"arcs":[[3424,3425,3426,3427,-3209,-3288,-3199]]},{"type":"Polygon","id":18135,"arcs":[[-3385,3428,3429,-3376,-3273]]},{"type":"Polygon","id":29103,"arcs":[[-3231,3430,3431,3432,-3412,-3240]]},{"type":"Polygon","id":42055,"arcs":[[-3417,3433,3434,3435,3436,-3126,-3184,-3229]]},{"type":"Polygon","id":42111,"arcs":[[-3424,3437,3438,3439,-3193,-3150]]},{"type":"Polygon","id":17039,"arcs":[[3440,3441,-3418,-3117]]},{"type":"Polygon","id":17169,"arcs":[[-3153,-3353,3442,3443,3444,-3220,-3227]]},{"type":"Polygon","id":17147,"arcs":[[-3365,3445,3446,3447,-3441,-3116]]},{"type":"Polygon","id":39089,"arcs":[[-3329,3448,3449,3450,3451,-3340,-3270]]},{"type":"Polygon","id":39021,"arcs":[[-3302,3452,3453,3454,-3313,-3290]]},{"type":"Polygon","id":29079,"arcs":[[-3368,3455,3456,3457,-3265,-3256]]},{"type":"Polygon","id":8013,"arcs":[[-2942,3458,3459,3460,-3308,-2974]]},{"type":"Polygon","id":31133,"arcs":[[-3286,3461,3462,3463,-3295,-3299]]},{"type":"Polygon","id":31147,"arcs":[[3464,3465,3466,3467,-3462,-3285]]},{"type":"Polygon","id":29087,"arcs":[[-3261,3468,3469,-3465,-3284,-3253]]},{"type":"Polygon","id":29111,"arcs":[[-3222,3470,3471,3472,-3431,-3230]]},{"type":"MultiPolygon","id":42029,"arcs":[[[3473,3474]],[[3475,3476,3477,-3425,-3198,-3332]]]},{"type":"Polygon","id":42133,"arcs":[[-3428,3478,3479,3480,3481,-3415,-3210]]},{"type":"Polygon","id":8103,"arcs":[[3482,-3076,-2957,-2952]]},{"type":"Polygon","id":39059,"arcs":[[3483,3484,-3327,-3206,-3355,3485]]},{"type":"Polygon","id":18057,"arcs":[[3486,3487,3488,-3357,-3362,-3375]]},{"type":"Polygon","id":18107,"arcs":[[-3359,3489,3490,3491,3492,-3378,-3281]]},{"type":"Polygon","id":17001,"arcs":[[-3445,3493,3494,3495,-3471,-3221]]},{"type":"Polygon","id":39109,"arcs":[[-3455,3496,3497,-3381,-3314]]},{"type":"Polygon","id":54069,"arcs":[[-3319,3498,3499,-3244,-3363]]},{"type":"Polygon","id":34005,"arcs":[[-3324,3500,3501,3502,3503,3504,3505,-3234,-3360]]},{"type":"Polygon","id":18011,"arcs":[[-3489,3506,3507,-3490,-3358]]},{"type":"Polygon","id":39013,"arcs":[[-3245,-3500,3508,3509,3510,-3486,-3354]]},{"type":"MultiPolygon","id":34029,"arcs":[[[3511,-3501,-3323]]]},{"type":"Polygon","id":39119,"arcs":[[-3485,3512,3513,3514,-3449,-3328]]},{"type":"Polygon","id":42057,"arcs":[[-3437,3515,3516,-3422,-3127]]},{"type":"Polygon","id":17129,"arcs":[[-3421,3517,3518,-3351]]},{"type":"Polygon","id":6007,"arcs":[[3519,3520,3521,3522,-3334,-3339]]},{"type":"Polygon","id":18165,"arcs":[[-3380,3523,3524,3525,-3305,-3322]]},{"type":"Polygon","id":42051,"arcs":[[-3440,3526,3527,3528,3529,-3316,-3194]]},{"type":"Polygon","id":39049,"arcs":[[-3452,3530,3531,3532,-3300,-3341]]},{"type":"Polygon","id":42101,"arcs":[[-3506,3533,3534,-3330,-3235]]},{"type":"Polygon","id":29061,"arcs":[[-3266,-3458,3535,3536,3537,-3369]]},{"type":"Polygon","id":29003,"arcs":[[-3371,3538,3539,3540,-3469,-3260]]},{"type":"Polygon","id":32033,"arcs":[[-2897,3541,3542,3543,3544,-2968,-2336]]},{"type":"Polygon","id":17017,"arcs":[[-3352,-3519,3545,3546,3547,-3443]]},{"type":"Polygon","id":39097,"arcs":[[3548,3549,3550,3551,-3453,-3301,-3533]]},{"type":"Polygon","id":17009,"arcs":[[-3548,3552,3553,-3494,-3444]]},{"type":"Polygon","id":8045,"arcs":[[-2951,3554,3555,3556,3557,-3077,-3483]]},{"type":"Polygon","id":18065,"arcs":[[-3430,3558,3559,3560,3561,-3373,-3377]]},{"type":"Polygon","id":42001,"arcs":[[3562,3563,-3434,-3416,-3482]]},{"type":"Polygon","id":42045,"arcs":[[-3535,3564,3565,-3474,3566,-3476,-3331]]},{"type":"Polygon","id":17115,"arcs":[[-3448,3567,3568,3569,3570,-3419,-3442]]},{"type":"Polygon","id":8014,"arcs":[[3571,3572,-3459,-2941],[-2938]]},{"type":"Polygon","id":29121,"arcs":[[-3433,3573,3574,3575,3576,3577,-3413]]},{"type":"Polygon","id":39023,"arcs":[[-3552,3578,3579,-3497,-3454]]},{"type":"Polygon","id":29115,"arcs":[[-3578,3580,3581,-3456,-3367,-3414]]},{"type":"Polygon","id":29063,"arcs":[[-3538,3582,3583,3584,-3539,-3370]]},{"type":"Polygon","id":54051,"arcs":[[3585,3586,-3509,-3499,-3318,3587]]},{"type":"Polygon","id":42059,"arcs":[[-3530,3588,3589,-3588,-3317]]},{"type":"Polygon","id":49023,"arcs":[[-3264,3590,3591,-3542,-2896]]},{"type":"Polygon","id":18177,"arcs":[[-3384,3592,3593,3594,-3559,-3429]]},{"type":"Polygon","id":20023,"arcs":[[3595,3596,3597,-3343,-3398]]},{"type":"Polygon","id":20153,"arcs":[[-3407,3598,3599,3600,-3596,-3397,-3409]]},{"type":"Polygon","id":6045,"arcs":[[-2723,-3336,3601,3602,3603,3604,-2658]]},{"type":"Polygon","id":20089,"arcs":[[3605,3606,3607,3608,3609,-3387,-3395]]},{"type":"Polygon","id":20183,"arcs":[[-3610,3610,3611,3612,-3390,-3388]]},{"type":"Polygon","id":20157,"arcs":[[3613,3614,-3606,-3394,-3404]]},{"type":"Polygon","id":20201,"arcs":[[-3297,3615,3616,3617,3618,-3614,-3405]]},{"type":"Polygon","id":20039,"arcs":[[-3401,3619,3620,3621,-3599,-3406]]},{"type":"Polygon","id":32001,"arcs":[[-2972,3622,3623,3624,-2361,-3005]]},{"type":"Polygon","id":20137,"arcs":[[3625,3626,3627,-3620,-3400,-3411]]},{"type":"Polygon","id":20147,"arcs":[[-3613,3628,3629,-3626,-3410,-3391]]},{"type":"Polygon","id":20117,"arcs":[[-3464,3630,3631,3632,-3616,-3296]]},{"type":"Polygon","id":8001,"arcs":[[-3349,3633,3634,3635,-3572,-2940,-3293]]},{"type":"Polygon","id":20013,"arcs":[[3636,3637,3638,3639,-3467]]},{"type":"Polygon","id":20131,"arcs":[[-3468,-3640,3640,3641,-3631,-3463]]},{"type":"Polygon","id":20043,"arcs":[[-3470,-3541,3642,3643,-3637,-3466]]},{"type":"MultiPolygon","id":34007,"arcs":[[[3644,3645,3646,-3504]]]},{"type":"Polygon","id":17167,"arcs":[[-3571,3647,3648,3649,3650,-3546,-3518,-3420]]},{"type":"Polygon","id":29117,"arcs":[[3651,3652,3653,-3536,-3457,-3582]]},{"type":"Polygon","id":29205,"arcs":[[-3473,3654,3655,-3574,-3432]]},{"type":"Polygon","id":18121,"arcs":[[-3493,3656,3657,3658,-3524,-3379]]},{"type":"Polygon","id":39121,"arcs":[[-3511,3659,3660,3661,-3513,-3484]]},{"type":"Polygon","id":29127,"arcs":[[-3496,3662,3663,3664,-3655,-3472]]},{"type":"Polygon","id":18059,"arcs":[[-3562,3665,3666,3667,-3487,-3374]]},{"type":"Polygon","id":39045,"arcs":[[-3451,3668,3669,3670,-3531]]},{"type":"Polygon","id":8047,"arcs":[[3671,3672,-3309,-3461]]},{"type":"Polygon","id":39127,"arcs":[[3673,3674,3675,-3669,-3450,-3515]]},{"type":"Polygon","id":18097,"arcs":[[-3668,3676,3677,3678,3679,-3507,-3488]]},{"type":"Polygon","id":8037,"arcs":[[3680,3681,3682,-3555,-2950,-3312]]},{"type":"Polygon","id":8117,"arcs":[[3683,3684,3685,-3681,-3311]]},{"type":"Polygon","id":18063,"arcs":[[-3680,3686,3687,-3491,-3508]]},{"type":"Polygon","id":39113,"arcs":[[-3580,3688,3689,3690,3691,-3382,-3498]]},{"type":"Polygon","id":39135,"arcs":[[-3692,3692,3693,-3593,-3383]]},{"type":"Polygon","id":8059,"arcs":[[-3636,3694,3695,3696,3697,3698,3699,-3672,-3460,-3573]]},{"type":"Polygon","id":8031,"arcs":[[3700,-3695,-3635]]},{"type":"MultiPolygon","id":34015,"arcs":[[[3701,3702,3703,3704,-3646]]]},{"type":"Polygon","id":17045,"arcs":[[3705,3706,3707,3708,-3306,-3526]]},{"type":"Polygon","id":17041,"arcs":[[-3709,3709,3710,-3446,-3364]]},{"type":"Polygon","id":17137,"arcs":[[-3651,3711,3712,3713,3714,-3553,-3547]]},{"type":"Polygon","id":39111,"arcs":[[-3587,3715,3716,3717,-3660,-3510]]},{"type":"Polygon","id":18133,"arcs":[[-3688,3718,3719,3720,-3657,-3492]]},{"type":"Polygon","id":8019,"arcs":[[-3700,3721,-3684,-3310,-3673]]},{"type":"Polygon","id":39057,"arcs":[[-3551,3722,3723,3724,-3689,-3579]]},{"type":"Polygon","id":17149,"arcs":[[-3715,3725,3726,3727,3728,3729,-3663,-3495,-3554]]},{"type":"MultiPolygon","id":10003,"arcs":[[[3730,3731]],[[3732,3733]],[[3734,3735,3736,3737,-3477,-3567,-3475,-3566]]]},{"type":"Polygon","id":17021,"arcs":[[-3570,3738,3739,-3648]]},{"type":"Polygon","id":29021,"arcs":[[-3585,3740,3741,3742,-3643,-3540]]},{"type":"Polygon","id":49007,"arcs":[[-3087,-3080,3743,3744,-3262]]},{"type":"Polygon","id":49039,"arcs":[[-3745,3745,3746,3747,-3591,-3263]]},{"type":"Polygon","id":39129,"arcs":[[-3671,3748,3749,3750,-3549,-3532]]},{"type":"Polygon","id":6021,"arcs":[[-3523,3751,3752,-3602,-3335]]},{"type":"Polygon","id":17139,"arcs":[[-3711,3753,3754,-3568,-3447]]},{"type":"Polygon","id":18041,"arcs":[[3755,3756,3757,-3560,-3595]]},{"type":"Polygon","id":18139,"arcs":[[-3758,3758,3759,3760,-3666,-3561]]},{"type":"Polygon","id":17171,"arcs":[[3761,-3726,-3714]]},{"type":"Polygon","id":29025,"arcs":[[-3654,3762,3763,3764,-3583,-3537]]},{"type":"MultiPolygon","id":34033,"arcs":[[[3765,3766]],[[3767,3768,-3732,3769,-3734,3770,-3704]]]},{"type":"Polygon","id":6091,"arcs":[[-2807,-2367,3771,3772,-3337]]},{"type":"Polygon","id":39115,"arcs":[[-3662,3773,3774,-3674,-3514]]},{"type":"Polygon","id":29049,"arcs":[[-3765,3775,3776,3777,-3741,-3584]]},{"type":"Polygon","id":8005,"arcs":[[-3348,3778,3779,3780,-3696,-3701,-3634]]},{"type":"Polygon","id":32019,"arcs":[[3781,3782,3783,3784,3785,-2362,-3625]]},{"type":"MultiPolygon","id":34001,"arcs":[[[3786,3787,3788,-3702,-3645,-3503]]]},{"type":"Polygon","id":18161,"arcs":[[-3694,3789,3790,-3756,-3594]]},{"type":"Polygon","id":24043,"arcs":[[-3436,3791,3792,3793,3794,3795,3796,-3516]]},{"type":"Polygon","id":24001,"arcs":[[-3517,-3797,3797,3798,3799,3800,-3438,-3423]]},{"type":"Polygon","id":24023,"arcs":[[-3801,3801,3802,3803,-3527,-3439]]},{"type":"MultiPolygon","id":24015,"arcs":[[[-3738,3804,3805,3806,-3426,-3478]]]},{"type":"Polygon","id":54061,"arcs":[[-3529,3807,3808,3809,3810,-3589]]},{"type":"Polygon","id":54077,"arcs":[[-3804,3811,3812,3813,3814,-3808,-3528]]},{"type":"MultiPolygon","id":24025,"arcs":[[[-3427,-3807,3815,3816,-3479]]]},{"type":"Polygon","id":54103,"arcs":[[-3590,-3811,3817,3818,3819,3820,-3716,-3586]]},{"type":"MultiPolygon","id":24005,"arcs":[[[-3817,3821,3822,3823,3824,3825,-3480]]]},{"type":"Polygon","id":24013,"arcs":[[-3826,3826,3827,-3563,-3481]]},{"type":"Polygon","id":24021,"arcs":[[-3828,3828,3829,3830,-3792,-3435,-3564]]},{"type":"Polygon","id":39047,"arcs":[[-3751,3831,3832,3833,-3723,-3550]]},{"type":"Polygon","id":49015,"arcs":[[-3079,3834,3835,3836,-3746,-3744]]},{"type":"Polygon","id":29041,"arcs":[[-3577,3837,3838,3839,3840,-3652,-3581]]},{"type":"Polygon","id":18145,"arcs":[[-3761,3841,3842,3843,-3677,-3667]]},{"type":"Polygon","id":54065,"arcs":[[3844,3845,3846,-3798,-3796]]},{"type":"Polygon","id":29173,"arcs":[[-3730,3847,3848,3849,-3664]]},{"type":"Polygon","id":17029,"arcs":[[-3708,3850,3851,3852,-3754,-3710]]},{"type":"Polygon","id":29137,"arcs":[[-3665,-3850,3853,3854,-3575,-3656]]},{"type":"Polygon","id":39073,"arcs":[[3855,3856,3857,-3749,-3670,-3676]]},{"type":"Polygon","id":20029,"arcs":[[-3619,3858,3859,3860,-3607,-3615]]},{"type":"Polygon","id":20085,"arcs":[[3861,3862,3863,3864,-3641,-3639]]},{"type":"Polygon","id":20005,"arcs":[[-3743,3865,3866,3867,-3862,-3638,-3644]]},{"type":"Polygon","id":17173,"arcs":[[-3755,-3853,3868,3869,3870,3871,-3739,-3569]]},{"type":"Polygon","id":54057,"arcs":[[3872,3873,-3802,-3800]]},{"type":"Polygon","id":39167,"arcs":[[-3718,3874,3875,3876,3877,-3774,-3661]]},{"type":"Polygon","id":54049,"arcs":[[-3810,3878,3879,-3818]]},{"type":"Polygon","id":18081,"arcs":[[-3844,3880,3881,3882,-3678]]},{"type":"Polygon","id":6115,"arcs":[[-3773,3883,3884,3885,-3520,-3338]]},{"type":"Polygon","id":18109,"arcs":[[-3883,3886,3887,3888,-3719,-3687,-3679]]},{"type":"Polygon","id":32029,"arcs":[[-2363,-3786]]},{"type":"Polygon","id":54003,"arcs":[[-3795,3889,3890,-3845]]},{"type":"Polygon","id":29033,"arcs":[[-3841,3891,3892,3893,-3763,-3653]]},{"type":"Polygon","id":29175,"arcs":[[-3855,3894,3895,3896,-3838,-3576]]},{"type":"Polygon","id":18167,"arcs":[[3897,3898,3899,-3706,-3525,-3659]]},{"type":"Polygon","id":18021,"arcs":[[-3721,3900,3901,3902,-3898,-3658]]},{"type":"Polygon","id":54095,"arcs":[[3903,3904,3905,-3875,-3717,-3821]]},{"type":"Polygon","id":29163,"arcs":[[3906,3907,3908,-3848,-3729,3909]]},{"type":"Polygon","id":39017,"arcs":[[3910,3911,3912,-3790,-3693,-3691]]},{"type":"Polygon","id":39165,"arcs":[[-3725,3913,3914,3915,-3911,-3690]]},{"type":"Polygon","id":6033,"arcs":[[3916,3917,3918,3919,-3603,-3753]]},{"type":"Polygon","id":8063,"arcs":[[-3598,3920,3921,3922,3923,-3346,-3344]]},{"type":"MultiPolygon","id":34011,"arcs":[[[3924,-3767,3925,-3768,-3703,-3789,3926,3927]]]},{"type":"Polygon","id":20163,"arcs":[[3928,3929,3930,3931,-3629,-3612]]},{"type":"Polygon","id":39027,"arcs":[[3932,3933,-3914,-3724,-3834,3934]]},{"type":"Polygon","id":20181,"arcs":[[-3601,3935,3936,3937,-3921,-3597]]},{"type":"Polygon","id":20193,"arcs":[[-3622,3938,3939,3940,-3936,-3600]]},{"type":"Polygon","id":20141,"arcs":[[-3609,3941,3942,3943,3944,-3929,-3611]]},{"type":"Polygon","id":20179,"arcs":[[-3628,3945,3946,-3939,-3621]]},{"type":"Polygon","id":20027,"arcs":[[3947,3948,3949,3950,-3859,-3618]]},{"type":"Polygon","id":20123,"arcs":[[-3861,3951,3952,-3942,-3608]]},{"type":"Polygon","id":20065,"arcs":[[-3932,3953,3954,-3946,-3627,-3630]]},{"type":"Polygon","id":20161,"arcs":[[3955,3956,3957,-3948,-3617,-3633]]},{"type":"Polygon","id":8035,"arcs":[[3958,3959,3960,-3697,-3781]]},{"type":"Polygon","id":8039,"arcs":[[3961,3962,-3959,-3780]]},{"type":"Polygon","id":20149,"arcs":[[-3642,-3865,3963,3964,-3956,-3632]]},{"type":"Polygon","id":8073,"arcs":[[-3924,3965,3966,3967,3968,-3962,-3779,-3347]]},{"type":"Polygon","id":8093,"arcs":[[-3699,3969,3970,3971,3972,-3685,-3722]]},{"type":"Polygon","id":39009,"arcs":[[-3878,3973,3974,3975,-3856,-3675,-3775]]},{"type":"Polygon","id":49027,"arcs":[[-3748,3976,3977,3978,-3543,-3592]]},{"type":"Polygon","id":54027,"arcs":[[-3847,3979,3980,-3873,-3799]]},{"type":"Polygon","id":29165,"arcs":[[-3742,-3778,3981,3982,3983,-3866]]},{"type":"Polygon","id":18047,"arcs":[[-3791,-3913,3984,3985,3986,-3759,-3757]]},{"type":"Polygon","id":29177,"arcs":[[-3894,3987,3988,3989,-3776,-3764]]},{"type":"Polygon","id":17135,"arcs":[[-3740,-3872,3990,3991,3992,3993,-3649]]},{"type":"Polygon","id":17117,"arcs":[[-3994,3994,3995,3996,-3712,-3650]]},{"type":"Polygon","id":6057,"arcs":[[-2366,3997,-3884,-3772]]},{"type":"Polygon","id":17061,"arcs":[[-3713,-3997,3998,3999,-3727,-3762]]},{"type":"Polygon","id":39141,"arcs":[[-3858,4000,4001,4002,4003,-3832,-3750]]},{"type":"Polygon","id":54037,"arcs":[[4004,4005,-3890,-3794]]},{"type":"Polygon","id":49019,"arcs":[[-3558,4006,4007,-3835,-3078]]},{"type":"Polygon","id":17023,"arcs":[[-3900,4008,4009,4010,4011,-3851,-3707]]},{"type":"Polygon","id":54073,"arcs":[[4012,4013,-3876,-3906]]},{"type":"Polygon","id":18119,"arcs":[[-3889,4014,4015,-3901,-3720]]},{"type":"Polygon","id":54033,"arcs":[[4016,4017,4018,4019,4020,-3819,-3880]]},{"type":"Polygon","id":51069,"arcs":[[4021,4022,4023,4024,-3980,-3846,-3891],[4025]]},{"type":"Polygon","id":29047,"arcs":[[-3777,-3990,4026,4027,-3982]]},{"type":"Polygon","id":18031,"arcs":[[-3987,4028,4029,4030,-3842,-3760]]},{"type":"Polygon","id":54091,"arcs":[[4031,-4017,-3879,-3809,-3815]]},{"type":"Polygon","id":54017,"arcs":[[-4021,4032,4033,4034,-3904,-3820]]},{"type":"Polygon","id":20087,"arcs":[[4035,4036,4037,-3863,-3868]]},{"type":"Polygon","id":20103,"arcs":[[-3984,4038,4039,4040,-4036,-3867]]},{"type":"Polygon","id":6011,"arcs":[[-3522,4041,4042,-3917,-3752]]},{"type":"Polygon","id":29195,"arcs":[[-3840,4043,4044,4045,4046,-3892]]},{"type":"Polygon","id":54107,"arcs":[[-4014,4047,4048,4049,4050,-3974,-3877]]},{"type":"Polygon","id":17013,"arcs":[[-4000,4051,4052,4053,-3910,-3728]]},{"type":"Polygon","id":39163,"arcs":[[-3976,4054,4055,4056,-4001,-3857]]},{"type":"Polygon","id":54085,"arcs":[[-4035,4057,4058,4059,-4048,-4013,-3905]]},{"type":"MultiPolygon","id":24029,"arcs":[[[-3737,4060,4061,4062,-3805]]]},{"type":"Polygon","id":8065,"arcs":[[-3686,-3973,4063,4064,-3682]]},{"type":"Polygon","id":17035,"arcs":[[-4012,4065,4066,-3869,-3852]]},{"type":"Polygon","id":39071,"arcs":[[-4004,4067,4068,4069,-3935,-3833]]},{"type":"MultiPolygon","id":24510,"arcs":[[[4070,4071]],[[4072,4073,-3823]]]},{"type":"Polygon","id":24027,"arcs":[[-3825,4074,4075,4076,-3829,-3827]]},{"type":"Polygon","id":8077,"arcs":[[4077,4078,4079,4080,-4007,-3557]]},{"type":"Polygon","id":8097,"arcs":[[-4065,4081,4082,-4078,-3556,-3683]]},{"type":"Polygon","id":10001,"arcs":[[4083,4084,4085,-4061,-3736,4086]]},{"type":"Polygon","id":18105,"arcs":[[4087,4088,4089,4090,-4015,-3888]]},{"type":"Polygon","id":18005,"arcs":[[-4031,4091,4092,4093,-3881,-3843]]},{"type":"Polygon","id":29007,"arcs":[[-3849,-3909,4094,4095,4096,-3895,-3854]]},{"type":"Polygon","id":24031,"arcs":[[4097,4098,4099,4100,-3830,-4077]]},{"type":"Polygon","id":18013,"arcs":[[-4094,4101,-4088,-3887,-3882]]},{"type":"Polygon","id":29089,"arcs":[[4102,-4044,-3839,-3897,4103]]},{"type":"Polygon","id":54023,"arcs":[[4104,4105,4106,4107,-3812,-3803,-3874]]},{"type":"MultiPolygon","id":34009,"arcs":[[[4108,-3927,-3788]]]},{"type":"Polygon","id":51107,"arcs":[[-4101,4109,4110,4111,4112,-4005,-3793,-3831]]},{"type":"Polygon","id":6061,"arcs":[[-2365,4113,4114,4115,4116,4117,-3885,-3998]]},{"type":"Polygon","id":39061,"arcs":[[-3916,4118,4119,4120,4121,4122,-3912]]},{"type":"Polygon","id":18137,"arcs":[[4123,4124,4125,4126,4127,-4029,-3986]]},{"type":"Polygon","id":18029,"arcs":[[-3985,-4123,4128,4129,-4124]]},{"type":"Polygon","id":20143,"arcs":[[-3951,4130,4131,4132,-3952,-3860]]},{"type":"Polygon","id":6101,"arcs":[[-3886,-4118,4133,4134,-4042,-3521]]},{"type":"Polygon","id":54001,"arcs":[[4135,4136,4137,-4018,-4032,-3814]]},{"type":"Polygon","id":29107,"arcs":[[4138,4139,4140,-3988,-3893,-4047]]},{"type":"Polygon","id":54093,"arcs":[[4141,-4136,-3813,-4108]]},{"type":"Polygon","id":39025,"arcs":[[4142,4143,4144,-4119,-3915,-3934,4145]]},{"type":"Polygon","id":51043,"arcs":[[-4113,4146,4147,-4022,-4006]]},{"type":"Polygon","id":17083,"arcs":[[-3996,4148,4149,-4052,-3999]]},{"type":"MultiPolygon","id":24035,"arcs":[[[4150]],[[-4086,4151,4152,4153,-4062]]]},{"type":"Polygon","id":18153,"arcs":[[-3903,4154,4155,4156,-4009,-3899]]},{"type":"Polygon","id":8051,"arcs":[[-4083,4157,4158,4159,4160,4161,4162,-4079]]},{"type":"Polygon","id":39015,"arcs":[[4163,4164,4165,-4146,-3933,-4070]]},{"type":"Polygon","id":29019,"arcs":[[-4097,4166,4167,4168,4169,-4104,-3896]]},{"type":"Polygon","id":32510,"arcs":[[4170,-4114,-2364,-3785]]},{"type":"Polygon","id":54031,"arcs":[[-4025,4171,4172,4173,-4105,-3981]]},{"type":"MultiPolygon","id":24003,"arcs":[[[4174,4175,4176,-4075,-3824,-4074,4177,-4071,4178]]]},{"type":"Polygon","id":29095,"arcs":[[-3989,-4141,4179,4180,4181,4182,-4027]]},{"type":"Polygon","id":29113,"arcs":[[-4054,4183,4184,4185,-3907]]},{"type":"Polygon","id":20061,"arcs":[[4186,4187,4188,-3949,-3958]]},{"type":"Polygon","id":8029,"arcs":[[-4163,4189,-4080]]},{"type":"Polygon","id":20105,"arcs":[[-4133,4190,4191,4192,-3943,-3953]]},{"type":"Polygon","id":20177,"arcs":[[-4038,4193,4194,4195,-3964,-3864]]},{"type":"Polygon","id":17051,"arcs":[[-3871,4196,4197,4198,4199,4200,-3991]]},{"type":"Polygon","id":17049,"arcs":[[-4067,4201,4202,-4197,-3870]]},{"type":"Polygon","id":20197,"arcs":[[-4196,4203,4204,4205,-4187,-3957,-3965]]},{"type":"Polygon","id":39079,"arcs":[[4206,4207,4208,4209,-4002,-4057]]},{"type":"Polygon","id":39105,"arcs":[[4210,4211,-4055,-3975,-4051,4212]]},{"type":"Polygon","id":51840,"arcs":[[-4026]]},{"type":"Polygon","id":20209,"arcs":[[-3983,-4028,-4183,4213,-4039]]},{"type":"Polygon","id":39131,"arcs":[[4214,4215,-4068,-4003,-4210]]},{"type":"Polygon","id":18079,"arcs":[[4216,4217,4218,-4092,-4030,-4128]]},{"type":"Polygon","id":54105,"arcs":[[4219,4220,4221,-4049,-4060]]},{"type":"Polygon","id":17033,"arcs":[[-4157,4222,4223,4224,4225,-4010]]},{"type":"Polygon","id":17079,"arcs":[[-4226,4226,4227,-4202,-4066,-4011]]},{"type":"Polygon","id":18055,"arcs":[[-4016,-4091,4228,4229,4230,4231,-4155,-3902]]},{"type":"Polygon","id":54041,"arcs":[[4232,4233,4234,4235,-4033,-4020]]},{"type":"Polygon","id":32023,"arcs":[[-2969,-3545,4236,4237,4238,4239,4240,-3623,-2971]]},{"type":"Polygon","id":29139,"arcs":[[-3908,-4186,4241,4242,4243,4244,-4095]]},{"type":"Polygon","id":21015,"arcs":[[4245,4246,4247,4248,4249,-4129,-4122]]},{"type":"Polygon","id":24011,"arcs":[[4250,4251,4252,4253,-4152,-4085]]},{"type":"Polygon","id":20109,"arcs":[[4254,4255,4256,4257,-3937,-3941]]},{"type":"Polygon","id":20199,"arcs":[[-4258,4258,4259,4260,-3922,-3938]]},{"type":"Polygon","id":20051,"arcs":[[-3945,4261,4262,4263,4264,-3930]]},{"type":"Polygon","id":20063,"arcs":[[-3947,-3955,4265,4266,4267,4268,-4255,-3940]]},{"type":"Polygon","id":20041,"arcs":[[-4189,4269,4270,4271,-4131,-3950]]},{"type":"Polygon","id":20167,"arcs":[[-4193,4272,4273,4274,-4262,-3944]]},{"type":"Polygon","id":20195,"arcs":[[-3931,-4265,4275,-4266,-3954]]},{"type":"Polygon","id":24033,"arcs":[[-4177,4276,4277,4278,4279,4280,-4098,-4076]]},{"type":"Polygon","id":8119,"arcs":[[4281,4282,-3970,-3698,-3961]]},{"type":"Polygon","id":8041,"arcs":[[-3963,-3969,4283,4284,-4282,-3960]]},{"type":"Polygon","id":21037,"arcs":[[-4145,4285,4286,-4120]]},{"type":"Polygon","id":54083,"arcs":[[-4107,4287,4288,4289,4290,-4137,-4142]]},{"type":"Polygon","id":32005,"arcs":[[-3784,4291,4292,4293,-4115,-4171]]},{"type":"Polygon","id":54097,"arcs":[[-4138,-4291,4294,-4233,-4019]]},{"type":"Polygon","id":54021,"arcs":[[-4236,4295,4296,-4058,-4034]]},{"type":"Polygon","id":51171,"arcs":[[4297,4298,4299,-4172,-4024]]},{"type":"Polygon","id":21117,"arcs":[[-4287,4300,4301,-4246,-4121]]},{"type":"Polygon","id":54035,"arcs":[[-4222,4302,4303,4304,4305,-4213,-4050]]},{"type":"Polygon","id":32021,"arcs":[[-4241,4306,4307,-3782,-3624]]},{"type":"Polygon","id":18071,"arcs":[[-4219,4308,4309,4310,-4089,-4102,-4093]]},{"type":"Polygon","id":20045,"arcs":[[-4041,4311,4312,4313,-4194,-4037]]},{"type":"Polygon","id":6017,"arcs":[[-4294,4314,4315,4316,-4116]]},{"type":"Polygon","id":29027,"arcs":[[-4096,-4245,4317,4318,-4167]]},{"type":"Polygon","id":29053,"arcs":[[-4170,4319,4320,4321,-4045,-4103]]},{"type":"MultiPolygon","id":51059,"arcs":[[[4322,4323,4324,4325,4326,4327,-4110,-4100],[4328]],[[4329]]]},{"type":"Polygon","id":8015,"arcs":[[-3972,4330,4331,-4158,-4082,-4064]]},{"type":"Polygon","id":20091,"arcs":[[-4182,4332,4333,-4312,-4040,-4214]]},{"type":"Polygon","id":39001,"arcs":[[4334,4335,4336,-4164,-4069,-4216]]},{"type":"Polygon","id":49041,"arcs":[[-3837,4337,4338,4339,-3977,-3747]]},{"type":"Polygon","id":8017,"arcs":[[-4261,4340,4341,-3966,-3923]]},{"type":"Polygon","id":54013,"arcs":[[-4297,4342,4343,4344,-4220,-4059]]},{"type":"Polygon","id":51187,"arcs":[[4345,4346,4347,-4298,-4023,-4148]]},{"type":"Polygon","id":39053,"arcs":[[4348,4349,4350,-4207,-4056,-4212]]},{"type":"Polygon","id":18115,"arcs":[[4351,-4125,-4130,-4250]]},{"type":"Polygon","id":54053,"arcs":[[-4306,4352,4353,-4349,-4211]]},{"type":"Polygon","id":17005,"arcs":[[-4201,4354,4355,-3992]]},{"type":"Polygon","id":39145,"arcs":[[-4209,4356,4357,4358,-4335,-4215]]},{"type":"Polygon","id":51061,"arcs":[[4359,4360,4361,4362,-4346,-4147,-4112]]},{"type":"Polygon","id":17119,"arcs":[[-3995,-3993,-4356,4363,4364,4365,4366,4367,-4149]]},{"type":"Polygon","id":29219,"arcs":[[4368,4369,4370,-4242,-4185]]},{"type":"MultiPolygon","id":11001,"arcs":[[[4371,4372,-4099,-4281]]]},{"type":"Polygon","id":18093,"arcs":[[-4311,4373,4374,4375,-4229,-4090]]},{"type":"Polygon","id":29183,"arcs":[[-4368,4376,4377,-4369,-4184,-4053,-4150]]},{"type":"Polygon","id":10005,"arcs":[[4378,4379,4380,4381,4382,4383,-4251,-4084]]},{"type":"Polygon","id":20169,"arcs":[[-4272,4384,4385,-4191,-4132]]},{"type":"Polygon","id":54071,"arcs":[[-4174,4386,4387,4388,4389,-4288,-4106]]},{"type":"Polygon","id":29159,"arcs":[[-4322,4390,4391,4392,4393,-4139,-4046]]},{"type":"MultiPolygon","id":24041,"arcs":[[[-4254,4394,-4153]]]},{"type":"Polygon","id":51153,"arcs":[[-4328,4395,4396,-4360,-4111],[4397,4398]]},{"type":"Polygon","id":54087,"arcs":[[-4345,4399,4400,-4303,-4221]]},{"type":"Polygon","id":29101,"arcs":[[-4394,4401,4402,-4180,-4140]]},{"type":"Polygon","id":51013,"arcs":[[4403,-4325,4404,-4323,-4373,4405]]},{"type":"Polygon","id":6003,"arcs":[[4406,4407,4408,4409,-4315,-4293]]},{"type":"Polygon","id":18155,"arcs":[[-4249,4410,4411,4412,-4126,-4352]]},{"type":"Polygon","id":29135,"arcs":[[4413,4414,4415,-4320,-4169]]},{"type":"Polygon","id":6113,"arcs":[[-4135,4416,4417,4418,-3918,-4043]]},{"type":"Polygon","id":18077,"arcs":[[-4413,4419,4420,4421,4422,-4217,-4127]]},{"type":"Polygon","id":17025,"arcs":[[-4228,4423,4424,4425,-4198,-4203]]},{"type":"Polygon","id":18083,"arcs":[[4426,4427,4428,4429,4430,-4223,-4156,-4232]]},{"type":"Polygon","id":18101,"arcs":[[-4376,4431,4432,4433,-4230]]},{"type":"Polygon","id":18027,"arcs":[[4434,4435,-4427,-4231,-4434]]},{"type":"Polygon","id":54007,"arcs":[[-4235,4436,4437,4438,-4343,-4296]]},{"type":"Polygon","id":51610,"arcs":[[-4324,-4405]]},{"type":"Polygon","id":29189,"arcs":[[-4367,4439,4440,4441,4442,4443,-4377]]},{"type":"Polygon","id":21191,"arcs":[[4444,4445,-4301,-4286,-4144,4446]]},{"type":"Polygon","id":20127,"arcs":[[-4206,4447,4448,4449,-4270,-4188]]},{"type":"Polygon","id":20053,"arcs":[[-4386,4450,4451,4452,-4273,-4192]]},{"type":"Polygon","id":20139,"arcs":[[-4314,4453,4454,4455,-4204,-4195]]},{"type":"Polygon","id":51600,"arcs":[[-4329],[-4330]]},{"type":"Polygon","id":6055,"arcs":[[4456,4457,4458,4459,4460,-3919,-4419]]},{"type":"Polygon","id":51157,"arcs":[[4461,4462,4463,-4347,-4363]]},{"type":"Polygon","id":21077,"arcs":[[4464,4465,4466,-4411,-4248]]},{"type":"Polygon","id":6097,"arcs":[[-3920,-4461,4467,4468,4469,-3604]]},{"type":"Polygon","id":17101,"arcs":[[4470,4471,-4224,-4431]]},{"type":"Polygon","id":51165,"arcs":[[-4300,4472,4473,4474,4475,-4387,-4173],[4476]]},{"type":"Polygon","id":17159,"arcs":[[-4225,-4472,4477,4478,4479,-4424,-4227]]},{"type":"Polygon","id":39087,"arcs":[[4480,4481,4482,4483,-4357,-4208,-4351]]},{"type":"Polygon","id":29037,"arcs":[[-4403,4484,4485,4486,-4333,-4181]]},{"type":"Polygon","id":51510,"arcs":[[4487,-4326,-4404]]},{"type":"Polygon","id":51139,"arcs":[[-4464,4488,4489,-4473,-4299,-4348]]},{"type":"Polygon","id":18143,"arcs":[[-4423,4490,4491,-4309,-4218]]},{"type":"Polygon","id":21023,"arcs":[[-4166,4492,4493,4494,-4447,-4143]]},{"type":"Polygon","id":17121,"arcs":[[-4426,4495,4496,4497,4498,-4199]]},{"type":"Polygon","id":21081,"arcs":[[-4302,-4446,4499,4500,4501,-4465,-4247]]},{"type":"Polygon","id":51685,"arcs":[[4502,-4398]]},{"type":"Polygon","id":18175,"arcs":[[-4492,4503,4504,4505,4506,4507,-4374,-4310]]},{"type":"Polygon","id":51683,"arcs":[[-4503,-4399]]},{"type":"Polygon","id":29510,"arcs":[[-4440,-4366,4508]]},{"type":"Polygon","id":24009,"arcs":[[4509,-4277,-4176]]},{"type":"Polygon","id":21161,"arcs":[[-4337,4510,4511,4512,-4493,-4165]]},{"type":"Polygon","id":21041,"arcs":[[4513,4514,4515,-4420,-4412,-4467]]},{"type":"Polygon","id":21089,"arcs":[[-4484,4516,4517,4518,-4358]]},{"type":"Polygon","id":17027,"arcs":[[-4200,-4499,4519,4520,-4364,-4355]]},{"type":"Polygon","id":54075,"arcs":[[-4390,4521,4522,4523,4524,-4289]]},{"type":"Polygon","id":29051,"arcs":[[-4319,4525,4526,-4414,-4168]]},{"type":"Polygon","id":20111,"arcs":[[-4456,4527,4528,4529,-4448,-4205]]},{"type":"Polygon","id":20059,"arcs":[[4530,4531,4532,-4454,-4313]]},{"type":"Polygon","id":20121,"arcs":[[-4487,4533,4534,-4531,-4334]]},{"type":"Polygon","id":54101,"arcs":[[-4295,-4290,-4525,4535,4536,-4437,-4234]]},{"type":"MultiPolygon","id":6067,"arcs":[[[4537,4538,4539,4540,-4417,-4134,-4117,-4317]]]},{"type":"Polygon","id":21223,"arcs":[[-4516,4541,4542,4543,-4421]]},{"type":"Polygon","id":21187,"arcs":[[-4502,4544,4545,4546,-4514,-4466]]},{"type":"Polygon","id":21135,"arcs":[[4547,4548,4549,-4511,-4336,-4359,-4519]]},{"type":"Polygon","id":29073,"arcs":[[-4371,4550,4551,4552,4553,4554,-4243]]},{"type":"Polygon","id":6051,"arcs":[[-3783,-4308,4555,4556,4557,4558,4559,-4407,-4292]]},{"type":"Polygon","id":29071,"arcs":[[-4378,-4444,4560,4561,4562,-4551,-4370]]},{"type":"Polygon","id":29151,"arcs":[[-4244,-4555,4563,4564,-4526,-4318]]},{"type":"Polygon","id":6005,"arcs":[[-4410,4565,4566,-4538,-4316]]},{"type":"MultiPolygon","id":24019,"arcs":[[[4567,-4252,-4384,4568,4569]]]},{"type":"Polygon","id":20171,"arcs":[[-4269,4570,4571,4572,4573,-4256]]},{"type":"Polygon","id":20101,"arcs":[[4574,4575,-4571,-4268]]},{"type":"Polygon","id":20071,"arcs":[[4576,4577,4578,4579,-4341,-4260]]},{"type":"Polygon","id":20203,"arcs":[[-4574,4580,4581,-4577,-4259,-4257]]},{"type":"Polygon","id":51047,"arcs":[[4582,4583,4584,4585,-4462,-4362]]},{"type":"Polygon","id":20135,"arcs":[[-4276,-4264,4586,4587,4588,4589,-4575,-4267]]},{"type":"Polygon","id":29141,"arcs":[[-4416,4590,4591,4592,-4391,-4321]]},{"type":"Polygon","id":20009,"arcs":[[-4453,4593,4594,4595,4596,-4274]]},{"type":"Polygon","id":8043,"arcs":[[-4285,4597,4598,4599,-4331,-3971,-4283]]},{"type":"Polygon","id":20165,"arcs":[[-4275,-4597,4600,-4587,-4263]]},{"type":"MultiPolygon","id":24017,"arcs":[[[-4279,4601,4602,4603]]]},{"type":"Polygon","id":18117,"arcs":[[-4508,4604,4605,-4432,-4375]]},{"type":"Polygon","id":54079,"arcs":[[4606,4607,-4353,-4305,4608]]},{"type":"Polygon","id":32017,"arcs":[[-3979,4609,4610,4611,4612,4613,-4237,-3544]]},{"type":"Polygon","id":8085,"arcs":[[-4162,4614,4615,4616,-4081,-4190]]},{"type":"Polygon","id":54015,"arcs":[[4617,4618,-4400,-4344,-4439]]},{"type":"Polygon","id":17163,"arcs":[[-4521,4619,4620,4621,-4441,-4509,-4365]]},{"type":"Polygon","id":51113,"arcs":[[-4586,4622,4623,-4489,-4463]]},{"type":"Polygon","id":54039,"arcs":[[-4401,-4619,4624,4625,4626,4627,4628,-4609,-4304]]},{"type":"Polygon","id":8061,"arcs":[[-4580,4629,4630,4631,4632,-3967,-4342]]},{"type":"Polygon","id":20113,"arcs":[[4633,4634,4635,4636,-4451,-4385]]},{"type":"Polygon","id":20115,"arcs":[[-4450,4637,4638,4639,-4634,-4271]]},{"type":"Polygon","id":21201,"arcs":[[-4513,4640,4641,4642,-4494]]},{"type":"Polygon","id":17191,"arcs":[[-4480,4643,4644,4645,4646,-4496,-4425]]},{"type":"Polygon","id":18019,"arcs":[[-4544,4647,4648,4649,-4504,-4491,-4422]]},{"type":"Polygon","id":54011,"arcs":[[-4354,-4608,4650,4651,-4481,-4350]]},{"type":"Polygon","id":21103,"arcs":[[4652,4653,4654,-4542,-4515,-4547]]},{"type":"Polygon","id":51091,"arcs":[[4655,4656,-4522,-4389]]},{"type":"Polygon","id":51179,"arcs":[[4657,4658,4659,4660,4661,4662,-4583,-4361,-4397]]},{"type":"Polygon","id":21097,"arcs":[[-4643,4663,4664,4665,-4500,-4445,-4495]]},{"type":"Polygon","id":49001,"arcs":[[-4340,4666,4667,4668,-4610,-3978]]},{"type":"Polygon","id":17047,"arcs":[[4669,4670,-4644,-4479]]},{"type":"Polygon","id":17185,"arcs":[[-4478,-4471,-4430,4671,4672,-4670]]},{"type":"Polygon","id":29083,"arcs":[[-4393,4673,4674,4675,-4485,-4402]]},{"type":"Polygon","id":24045,"arcs":[[4676,4677,4678,-4569,-4383]]},{"type":"Polygon","id":54067,"arcs":[[-4537,4679,4680,-4625,-4618,-4438]]},{"type":"Polygon","id":18125,"arcs":[[4681,4682,4683,-4428,-4436]]},{"type":"MultiPolygon","id":6095,"arcs":[[[4684,-4459]],[[-4541,4685,-4457,-4418]]]},{"type":"Polygon","id":29015,"arcs":[[-4593,4686,4687,4688,-4674,-4392]]},{"type":"Polygon","id":18051,"arcs":[[4689,4690,4691,4692,-4672,-4429,-4684]]},{"type":"Polygon","id":18037,"arcs":[[-4606,4693,4694,4695,4696,-4682,-4435,-4433]]},{"type":"Polygon","id":21069,"arcs":[[4697,4698,4699,-4641,-4512,-4550]]},{"type":"Polygon","id":21185,"arcs":[[-4655,4700,4701,-4648,-4543]]},{"type":"Polygon","id":20017,"arcs":[[-4530,4702,4703,-4638,-4449]]},{"type":"Polygon","id":8101,"arcs":[[4704,4705,4706,4707,4708,-4598,-4284]]},{"type":"Polygon","id":8025,"arcs":[[-4633,4709,-4705,-3968]]},{"type":"Polygon","id":20159,"arcs":[[-4637,4710,4711,-4594,-4452]]},{"type":"Polygon","id":17133,"arcs":[[4712,4713,4714,-4442,-4622]]},{"type":"Polygon","id":17189,"arcs":[[-4498,4715,4716,4717,-4620,-4520]]},{"type":"MultiPolygon","id":24037,"arcs":[[[4718,-4603,4719]]]},{"type":"Polygon","id":6009,"arcs":[[4720,4721,4722,-4566,-4409]]},{"type":"Polygon","id":49031,"arcs":[[4723,4724,-4667,-4339]]},{"type":"Polygon","id":49055,"arcs":[[-3836,4725,4726,-4724,-4338]]},{"type":"Polygon","id":29099,"arcs":[[-4715,4727,4728,4729,-4561,-4443]]},{"type":"Polygon","id":21019,"arcs":[[4730,4731,-4517,-4483,4732]]},{"type":"Polygon","id":21043,"arcs":[[-4732,4733,4734,4735,-4548,-4518]]},{"type":"Polygon","id":49037,"arcs":[[-4617,4736,4737,4738,4739,4740,4741,4742,4743,-4726,-4008]]},{"type":"Polygon","id":21209,"arcs":[[4744,4745,4746,4747,-4545,-4501,-4666]]},{"type":"Polygon","id":51660,"arcs":[[-4477]]},{"type":"Polygon","id":51079,"arcs":[[4748,4749,-4474,-4490,-4624]]},{"type":"Polygon","id":51015,"arcs":[[4750,4751,4752,4753,-4656,-4388,-4476],[4754],[4755]]},{"type":"Polygon","id":17081,"arcs":[[-4647,4756,4757,4758,-4716,-4497]]},{"type":"Polygon","id":29013,"arcs":[[-4676,4759,4760,4761,-4534,-4486]]},{"type":"Polygon","id":32009,"arcs":[[4762,-4556,-4307,-4240]]},{"type":"Polygon","id":21181,"arcs":[[-4700,4763,4764,-4664,-4642]]},{"type":"Polygon","id":8109,"arcs":[[-4600,4765,4766,4767,4768,4769,4770,-4159,-4332]]},{"type":"MultiPolygon","id":24047,"arcs":[[[4771,4772]],[[-4380,4773]],[[4774,4775,4776,4777,-4677,-4382,4778]]]},{"type":"Polygon","id":6109,"arcs":[[-4560,4779,4780,4781,-4721,-4408]]},{"type":"Polygon","id":20031,"arcs":[[-4533,4782,4783,4784,-4528,-4455]]},{"type":"Polygon","id":29131,"arcs":[[-4527,-4565,4785,4786,4787,-4591,-4415]]},{"type":"Polygon","id":18025,"arcs":[[4788,4789,4790,-4694,-4605,-4507]]},{"type":"Polygon","id":18061,"arcs":[[4791,4792,4793,4794,-4789,-4506]]},{"type":"Polygon","id":54099,"arcs":[[-4652,4795,4796,4797,4798,-4733,-4482]]},{"type":"Polygon","id":18043,"arcs":[[-4650,4799,-4792,-4505]]},{"type":"Polygon","id":51099,"arcs":[[4800,4801,4802,4803,-4659,4804]]},{"type":"Polygon","id":21205,"arcs":[[-4736,4805,4806,4807,4808,-4698,-4549]]},{"type":"Polygon","id":51137,"arcs":[[4809,4810,4811,-4749,-4623,-4585]]},{"type":"Polygon","id":20003,"arcs":[[4812,4813,-4783,-4532]]},{"type":"Polygon","id":20107,"arcs":[[-4762,4814,4815,-4813,-4535]]},{"type":"Polygon","id":21111,"arcs":[[4816,4817,4818,4819,-4793,-4800,-4649,-4702]]},{"type":"Polygon","id":51177,"arcs":[[-4663,4820,-4661,4821,4822,4823,-4810,-4584]]},{"type":"Polygon","id":54043,"arcs":[[-4607,-4629,4824,4825,4826,-4796,-4651]]},{"type":"Polygon","id":21073,"arcs":[[-4546,-4748,4827,4828,4829,-4653]]},{"type":"Polygon","id":21017,"arcs":[[4830,4831,4832,-4745,-4665,-4765]]},{"type":"Polygon","id":21211,"arcs":[[4833,4834,-4817,-4701,-4654,-4830]]},{"type":"Polygon","id":20145,"arcs":[[-4596,4835,4836,4837,-4588,-4601]]},{"type":"Polygon","id":8091,"arcs":[[-4161,4838,4839,4840,-4615]]},{"type":"Polygon","id":51630,"arcs":[[-4821,-4662]]},{"type":"MultiPolygon","id":6041,"arcs":[[[4841,-4469]]]},{"type":"Polygon","id":21011,"arcs":[[-4809,4842,4843,-4764,-4699]]},{"type":"Polygon","id":6077,"arcs":[[-4567,-4723,4844,4845,4846,4847,-4539]]},{"type":"Polygon","id":29125,"arcs":[[-4554,4848,4849,-4786,-4564]]},{"type":"MultiPolygon","id":24039,"arcs":[[[4850,4851]],[[4852,4853,4854,4855]],[[4856,4857,4858]],[[4859,-4678,-4778,4860]]]},{"type":"Polygon","id":51003,"arcs":[[-4812,4861,4862,4863,4864,-4751,-4475,-4750],[4865]]},{"type":"MultiPolygon","id":51193,"arcs":[[[4866,4867,4868,-4801,4869]]]},{"type":"Polygon","id":29029,"arcs":[[-4788,4870,4871,4872,4873,-4687,-4592]]},{"type":"Polygon","id":21127,"arcs":[[-4799,4874,4875,4876,4877,-4734,-4731]]},{"type":"Polygon","id":51017,"arcs":[[-4657,-4754,4878,4879,4880,-4523]]},{"type":"Polygon","id":8099,"arcs":[[-4579,4881,4882,4883,4884,-4630]]},{"type":"Polygon","id":54025,"arcs":[[-4881,4885,4886,4887,4888,-4680,-4536,-4524]]},{"type":"Polygon","id":21063,"arcs":[[-4878,4889,-4806,-4735]]},{"type":"Polygon","id":8027,"arcs":[[-4709,4890,-4766,-4599]]},{"type":"Polygon","id":8011,"arcs":[[-4885,4891,4892,4893,-4631]]},{"type":"Polygon","id":8089,"arcs":[[-4894,4894,-4706,-4710,-4632]]},{"type":"Polygon","id":18123,"arcs":[[4895,4896,4897,4898,-4695,-4791]]},{"type":"Polygon","id":54019,"arcs":[[-4681,-4889,4899,4900,-4626]]},{"type":"Polygon","id":20093,"arcs":[[4901,4902,4903,-4581,-4573]]},{"type":"Polygon","id":20055,"arcs":[[-4576,-4590,4904,4905,4906,-4902,-4572]]},{"type":"Polygon","id":20075,"arcs":[[-4904,4907,-4882,-4578,-4582]]},{"type":"Polygon","id":17193,"arcs":[[-4673,-4693,4908,4909,4910,-4645,-4671]]},{"type":"Polygon","id":20185,"arcs":[[-4712,4911,4912,4913,-4836,-4595]]},{"type":"Polygon","id":20083,"arcs":[[-4838,4914,4915,4916,-4905,-4589]]},{"type":"Polygon","id":17065,"arcs":[[-4911,4917,4918,4919,-4757,-4646]]},{"type":"Polygon","id":51033,"arcs":[[-4804,4920,4921,4922,4923,-4822,-4660]]},{"type":"Polygon","id":18173,"arcs":[[-4697,4924,4925,4926,4927,-4690,-4683]]},{"type":"Polygon","id":18129,"arcs":[[4928,4929,4930,4931,-4909,-4692]]},{"type":"Polygon","id":54005,"arcs":[[4932,4933,4934,-4825,-4628]]},{"type":"Polygon","id":17157,"arcs":[[-4621,-4718,4935,4936,4937,4938,-4713]]},{"type":"Polygon","id":29185,"arcs":[[-4689,4939,4940,4941,4942,-4760,-4675]]},{"type":"Polygon","id":17145,"arcs":[[-4717,-4759,4943,4944,-4936]]},{"type":"Polygon","id":29055,"arcs":[[-4563,4945,4946,4947,4948,-4552]]},{"type":"Polygon","id":21067,"arcs":[[4949,4950,4951,4952,-4746,-4833]]},{"type":"Polygon","id":29221,"arcs":[[-4730,4953,4954,-4946,-4562]]},{"type":"Polygon","id":18147,"arcs":[[-4899,4955,4956,-4925,-4696]]},{"type":"Polygon","id":21163,"arcs":[[4957,4958,-4896,-4790,-4795]]},{"type":"Polygon","id":51790,"arcs":[[-4755]]},{"type":"Polygon","id":21239,"arcs":[[-4953,4959,4960,4961,-4828,-4747]]},{"type":"Polygon","id":21173,"arcs":[[4962,4963,4964,-4831,-4844]]},{"type":"Polygon","id":20079,"arcs":[[4965,4966,4967,-4635,-4640]]},{"type":"Polygon","id":20073,"arcs":[[-4529,-4785,4968,4969,4970,4971,-4703]]},{"type":"Polygon","id":20155,"arcs":[[-4636,-4968,4972,4973,4974,-4912,-4711]]},{"type":"Polygon","id":18163,"arcs":[[-4928,4975,-4929,-4691]]},{"type":"Polygon","id":29161,"arcs":[[-4949,4976,4977,4978,-4849,-4553]]},{"type":"Polygon","id":49017,"arcs":[[-4744,4979,4980,-4668,-4725,-4727]]},{"type":"Polygon","id":51057,"arcs":[[4981,4982,-4921,-4803,4983]]},{"type":"Polygon","id":8113,"arcs":[[-4841,4984,4985,-4737,-4616]]},{"type":"Polygon","id":51109,"arcs":[[-4824,4986,4987,4988,-4862,-4811]]},{"type":"Polygon","id":49021,"arcs":[[-4981,4989,4990,-4611,-4669]]},{"type":"Polygon","id":21215,"arcs":[[4991,4992,4993,-4818,-4835]]},{"type":"Polygon","id":8053,"arcs":[[-4771,4994,4995,4996,4997,-4839,-4160]]},{"type":"Polygon","id":21005,"arcs":[[-4962,4998,4999,5000,-4992,-4834,-4829]]},{"type":"Polygon","id":17055,"arcs":[[-4920,5001,5002,5003,-4944,-4758]]},{"type":"Polygon","id":51159,"arcs":[[5004,5005,5006,-4868]]},{"type":"Polygon","id":29186,"arcs":[[-4939,5007,5008,-4728,-4714]]},{"type":"Polygon","id":21029,"arcs":[[5009,5010,-4819,-4994]]},{"type":"Polygon","id":21175,"arcs":[[-4877,5011,5012,5013,5014,-4807,-4890]]},{"type":"Polygon","id":21049,"arcs":[[5015,5016,5017,-4950,-4832,-4965]]},{"type":"MultiPolygon","id":6013,"arcs":[[[-4847,5018,5019]]]},{"type":"Polygon","id":51820,"arcs":[[-4756]]},{"type":"Polygon","id":20015,"arcs":[[-4704,-4972,5020,5021,5022,-4966,-4639]]},{"type":"Polygon","id":20047,"arcs":[[-4914,5023,5024,5025,-4915,-4837]]},{"type":"Polygon","id":51163,"arcs":[[5026,5027,5028,5029,-4879,-4753,5030],[5031],[5032]]},{"type":"Polygon","id":29187,"arcs":[[-5009,5033,5034,5035,-4954,-4729]]},{"type":"Polygon","id":6099,"arcs":[[5036,5037,5038,-4845,-4722,-4782]]},{"type":"Polygon","id":29085,"arcs":[[-4874,5039,5040,-4940,-4688]]},{"type":"Polygon","id":51540,"arcs":[[-4866]]},{"type":"Polygon","id":29217,"arcs":[[-4943,5041,5042,5043,5044,-4815,-4761]]},{"type":"Polygon","id":21165,"arcs":[[-4808,-5015,5045,5046,-4963,-4843]]},{"type":"Polygon","id":51125,"arcs":[[5047,5048,5049,-5031,-4752,-4865]]},{"type":"Polygon","id":21027,"arcs":[[5050,5051,5052,5053,-4897,-4959]]},{"type":"Polygon","id":20207,"arcs":[[5054,5055,-4969,-4784]]},{"type":"Polygon","id":20001,"arcs":[[5056,5057,-5055,-4814]]},{"type":"Polygon","id":20011,"arcs":[[-5045,5058,5059,-5057,-4816]]},{"type":"MultiPolygon","id":51001,"arcs":[[[5060,-4854]],[[5061,-4858]],[[-4851,5062]],[[5063,5064,5065,5066,-4776,5067]],[[-4772,5068]]]},{"type":"Polygon","id":54045,"arcs":[[-4935,5069,5070,-4826]]},{"type":"Polygon","id":51133,"arcs":[[5071,-5005,-4867,5072]]},{"type":"Polygon","id":29169,"arcs":[[-4850,-4979,5073,5074,-4871,-4787]]},{"type":"Polygon","id":8055,"arcs":[[-4708,5075,5076,5077,-4767,-4891]]},{"type":"Polygon","id":51085,"arcs":[[-4924,5078,5079,5080,5081,-4987,-4823]]},{"type":"Polygon","id":51065,"arcs":[[5082,5083,-4863,-4989,5084]]},{"type":"Polygon","id":21093,"arcs":[[5085,5086,5087,5088,-5051,-4958,-4794,-4820,-5011]]},{"type":"Polygon","id":21113,"arcs":[[5089,5090,5091,-4960,-4952]]},{"type":"Polygon","id":21115,"arcs":[[5092,5093,5094,-5012,-4876]]},{"type":"Polygon","id":20069,"arcs":[[-4917,5095,5096,5097,-4906]]},{"type":"Polygon","id":21091,"arcs":[[-5054,5098,5099,-4956,-4898]]},{"type":"Polygon","id":54081,"arcs":[[-4627,-4901,5100,5101,5102,-4933]]},{"type":"Polygon","id":21179,"arcs":[[-5001,5103,5104,5105,-5086,-5010,-4993]]},{"type":"Polygon","id":54059,"arcs":[[5106,5107,5108,5109,5110,-4797,-4827,-5071]]},{"type":"Polygon","id":21101,"arcs":[[-4927,5111,5112,5113,5114,-4930,-4976]]},{"type":"Polygon","id":51097,"arcs":[[-4983,5115,5116,5117,5118,-4922]]},{"type":"Polygon","id":8111,"arcs":[[-4998,5119,5120,-4985,-4840]]},{"type":"Polygon","id":21167,"arcs":[[-5092,5121,5122,5123,-4999,-4961]]},{"type":"Polygon","id":8079,"arcs":[[5124,5125,-4995,-4770]]},{"type":"Polygon","id":21159,"arcs":[[-4798,-5111,5126,5127,-5093,-4875]]},{"type":"Polygon","id":51005,"arcs":[[-5030,5128,5129,5130,-4886,-4880],[5131]]},{"type":"Polygon","id":17077,"arcs":[[-5004,5132,5133,5134,-4937,-4945]]},{"type":"Polygon","id":21197,"arcs":[[-5047,5135,5136,5137,-5016,-4964]]},{"type":"Polygon","id":21059,"arcs":[[5138,5139,-5112,-4926,-4957,-5100]]},{"type":"Polygon","id":21151,"arcs":[[5140,5141,5142,5143,-5090,-4951,-5018]]},{"type":"Polygon","id":17059,"arcs":[[-4932,5144,5145,5146,-4918,-4910]]},{"type":"Polygon","id":21229,"arcs":[[-5124,5147,5148,-5104,-5000]]},{"type":"Polygon","id":20057,"arcs":[[-5026,5149,5150,5151,-5096,-4916]]},{"type":"Polygon","id":20173,"arcs":[[-5023,5152,5153,-4973,-4967]]},{"type":"Polygon","id":17165,"arcs":[[-5147,5154,5155,5156,-5002,-4919]]},{"type":"Polygon","id":51101,"arcs":[[-5119,5157,5158,-5079,-4923]]},{"type":"Polygon","id":29059,"arcs":[[-4873,5159,5160,5161,5162,-5040]]},{"type":"MultiPolygon","id":6001,"arcs":[[[-4846,-5039,5163,5164,-5019]]]},{"type":"Polygon","id":51075,"arcs":[[5165,5166,5167,-5085,-4988,-5082]]},{"type":"Polygon","id":29157,"arcs":[[-5135,5168,5169,5170,5171,-5034,-5008,-4938]]},{"type":"Polygon","id":29039,"arcs":[[5172,5173,5174,-5042,-4942]]},{"type":"Polygon","id":6043,"arcs":[[5175,5176,-4781]]},{"type":"Polygon","id":21225,"arcs":[[5177,5178,5179,-5145,-4931,-5115]]},{"type":"Polygon","id":29105,"arcs":[[-5075,5180,5181,5182,-5160,-4872]]},{"type":"Polygon","id":8033,"arcs":[[-5121,5183,-4738,-4986]]},{"type":"Polygon","id":21153,"arcs":[[-5095,5184,5185,5186,5187,-5013]]},{"type":"Polygon","id":54089,"arcs":[[5188,5189,-5101,-4900,-4888,5190]]},{"type":"Polygon","id":17199,"arcs":[[-5157,5191,5192,-5133,-5003]]},{"type":"Polygon","id":21237,"arcs":[[-5188,5193,5194,-5136,-5046,-5014]]},{"type":"Polygon","id":21065,"arcs":[[-5138,5195,5196,-5141,-5017]]},{"type":"MultiPolygon","id":51103,"arcs":[[[5197,-5006,-5072,5198]]]},{"type":"Polygon","id":8105,"arcs":[[-4769,5199,5200,5201,-5125]]},{"type":"MultiPolygon","id":6075,"arcs":[[[5202,5203]]]},{"type":"Polygon","id":29167,"arcs":[[-5041,-5163,5204,5205,-5173,-4941]]},{"type":"Polygon","id":21079,"arcs":[[-5144,5206,5207,5208,-5122,-5091]]},{"type":"Polygon","id":20151,"arcs":[[-4975,5209,5210,5211,-5024,-4913]]},{"type":"Polygon","id":51580,"arcs":[[-5132]]},{"type":"Polygon","id":51009,"arcs":[[5212,5213,5214,5215,-5027,-5050]]},{"type":"Polygon","id":8071,"arcs":[[-4893,5216,5217,5218,5219,-5076,-4707,-4895]]},{"type":"Polygon","id":51023,"arcs":[[5220,5221,5222,-5129,-5029]]},{"type":"Polygon","id":51678,"arcs":[[-5032]]},{"type":"Polygon","id":29065,"arcs":[[-4948,5223,5224,5225,5226,-4977]]},{"type":"Polygon","id":54109,"arcs":[[5227,5228,-5107,-5070,-4934,-5103]]},{"type":"Polygon","id":51029,"arcs":[[5229,5230,5231,-5048,-4864,-5084]]},{"type":"Polygon","id":6039,"arcs":[[-4559,5232,5233,-5176,-4780]]},{"type":"MultiPolygon","id":51119,"arcs":[[[5234,-5116,-4982,5235]]]},{"type":"Polygon","id":51530,"arcs":[[-5033]]},{"type":"Polygon","id":21071,"arcs":[[-5128,5236,5237,-5185,-5094]]},{"type":"Polygon","id":8003,"arcs":[[-5078,5238,5239,-5200,-4768]]},{"type":"Polygon","id":51049,"arcs":[[-5168,5240,5241,5242,-5230,-5083]]},{"type":"Polygon","id":21195,"arcs":[[5243,5244,5245,5246,5247,-5237,-5127,-5110]]},{"type":"Polygon","id":29093,"arcs":[[-5036,5248,5249,5250,-5224,-4947,-4955]]},{"type":"Polygon","id":20187,"arcs":[[5251,5252,5253,5254,-4883,-4908]]},{"type":"Polygon","id":21183,"arcs":[[-5053,5255,5256,5257,5258,-5139,-5099]]},{"type":"Polygon","id":20067,"arcs":[[5259,5260,-5252,-4903]]},{"type":"Polygon","id":20081,"arcs":[[-5098,5261,5262,5263,-5260,-4907]]},{"type":"Polygon","id":20097,"arcs":[[-5212,5264,5265,5266,-5150,-5025]]},{"type":"Polygon","id":20095,"arcs":[[-5154,5267,5268,5269,-5210,-4974]]},{"type":"Polygon","id":20205,"arcs":[[5270,5271,5272,-4970,-5056]]},{"type":"Polygon","id":20133,"arcs":[[-5060,5273,5274,5275,-5271,-5058]]},{"type":"Polygon","id":21123,"arcs":[[5276,5277,5278,5279,-5087,-5106]]},{"type":"Polygon","id":21155,"arcs":[[-5149,5280,5281,5282,-5277,-5105]]},{"type":"Polygon","id":54063,"arcs":[[-5131,5283,5284,-5191,-4887]]},{"type":"Polygon","id":21129,"arcs":[[-5195,5285,5286,5287,-5196,-5137]]},{"type":"Polygon","id":21021,"arcs":[[-5209,5288,5289,-5281,-5148,-5123]]},{"type":"Polygon","id":51087,"arcs":[[5290,5291,5292,5293,5294,5295,-5166,-5081]]},{"type":"Polygon","id":6081,"arcs":[[5296,5297,5298,5299,-5203]]},{"type":"Polygon","id":21025,"arcs":[[-5187,5300,5301,5302,-5286,-5194]]},{"type":"Polygon","id":51145,"arcs":[[5303,5304,-5241,-5167]]},{"type":"Polygon","id":21149,"arcs":[[-5259,5305,5306,5307,-5113,-5140]]},{"type":"Polygon","id":20037,"arcs":[[-5044,5308,5309,5310,5311,-5274,-5059]]},{"type":"Polygon","id":51045,"arcs":[[5312,5313,5314,-5284,-5130,-5223]]},{"type":"Polygon","id":8023,"arcs":[[-5220,5315,5316,5317,-5239,-5077]]},{"type":"Polygon","id":29011,"arcs":[[-5175,5318,5319,-5309,-5043]]},{"type":"Polygon","id":21233,"arcs":[[5320,5321,5322,-5178,-5114,-5308]]},{"type":"Polygon","id":29123,"arcs":[[-5172,5323,5324,-5249,-5035]]},{"type":"Polygon","id":8009,"arcs":[[-5255,5325,5326,5327,-5217,-4892,-4884]]},{"type":"Polygon","id":8083,"arcs":[[5328,5329,-4739,-5184]]},{"type":"Polygon","id":8067,"arcs":[[-4997,5330,5331,-5329,-5120]]},{"type":"Polygon","id":6047,"arcs":[[-5234,5332,5333,5334,-5037,-5177]]},{"type":"Polygon","id":21137,"arcs":[[5335,5336,5337,-5289,-5208]]},{"type":"Polygon","id":51127,"arcs":[[5338,5339,5340,-5291,-5080,-5159]]},{"type":"Polygon","id":21085,"arcs":[[-5089,5341,5342,5343,-5256,-5052]]},{"type":"Polygon","id":51019,"arcs":[[5344,5345,5346,5347,5348,-5221,-5028,-5216],[5349]]},{"type":"Polygon","id":49053,"arcs":[[5350,5351,-4612,-4991]]},{"type":"Polygon","id":20049,"arcs":[[-5273,5352,5353,5354,-5021,-4971]]},{"type":"Polygon","id":29179,"arcs":[[5355,5356,5357,-5225,-5251]]},{"type":"Polygon","id":29031,"arcs":[[5358,5359,5360,5361,5362,-5170]]},{"type":"Polygon","id":29215,"arcs":[[-4978,-5227,5363,5364,5365,5366,-5181,-5074]]},{"type":"Polygon","id":17069,"arcs":[[-5146,-5180,5367,5368,5369,-5155]]},{"type":"Polygon","id":17151,"arcs":[[-5370,5370,5371,5372,-5156]]},{"type":"Polygon","id":51760,"arcs":[[5373,-5295]]},{"type":"Polygon","id":17087,"arcs":[[-5373,5374,5375,5376,-5192]]},{"type":"Polygon","id":17181,"arcs":[[-5193,-5377,5377,5378,-5359,-5169,-5134]]},{"type":"MultiPolygon","id":51073,"arcs":[[[5379,5380,5381,-5117,-5235]]]},{"type":"Polygon","id":29017,"arcs":[[-5363,5382,5383,-5324,-5171]]},{"type":"Polygon","id":54055,"arcs":[[-5190,5384,5385,5386,5387,-5228,-5102]]},{"type":"Polygon","id":6019,"arcs":[[5388,5389,5390,5391,5392,-5333,-5233,-4558]]},{"type":"Polygon","id":29057,"arcs":[[-5206,5393,5394,5395,-5319,-5174]]},{"type":"Polygon","id":21109,"arcs":[[-5288,5396,5397,5398,5399,-5142,-5197]]},{"type":"Polygon","id":21107,"arcs":[[5400,5401,5402,-5321,-5307]]},{"type":"MultiPolygon","id":51041,"arcs":[[[-5374,-5294,5403,5404,5405,5406,5407,5408,5409,5410,-5304,-5296]]]},{"type":"Polygon","id":51011,"arcs":[[5411,5412,5413,-5213,-5049,-5232]]},{"type":"MultiPolygon","id":51131,"arcs":[[[5414,-5065]]]},{"type":"Polygon","id":21045,"arcs":[[-5338,5415,5416,5417,5418,-5282,-5290]]},{"type":"Polygon","id":54047,"arcs":[[-5388,5419,5420,-5108,-5229]]},{"type":"Polygon","id":21055,"arcs":[[-5323,5421,5422,5423,-5368,-5179]]},{"type":"Polygon","id":49025,"arcs":[[-4980,-4743,5424,5425,-5351,-4990]]},{"type":"Polygon","id":21189,"arcs":[[-5303,5426,5427,-5397,-5287]]},{"type":"Polygon","id":21203,"arcs":[[-5400,5428,5429,-5336,-5207,-5143]]},{"type":"Polygon","id":51027,"arcs":[[-5421,5430,5431,5432,-5244,-5109]]},{"type":"MultiPolygon","id":51115,"arcs":[[[-5381,5433]]]},{"type":"Polygon","id":21119,"arcs":[[-5186,-5238,-5248,5434,5435,-5301]]},{"type":"Polygon","id":51007,"arcs":[[-5411,5436,5437,5438,-5242,-5305]]},{"type":"Polygon","id":51036,"arcs":[[5439,5440,-5292,-5341]]},{"type":"Polygon","id":29225,"arcs":[[-5183,5441,5442,5443,5444,-5161]]},{"type":"Polygon","id":6085,"arcs":[[-5038,-5335,5445,5446,-5298,5447,-5164]]},{"type":"Polygon","id":21217,"arcs":[[-5283,-5419,5448,5449,-5278]]},{"type":"Polygon","id":29229,"arcs":[[-5367,5450,-5442,-5182]]},{"type":"Polygon","id":51071,"arcs":[[-5315,5451,5452,5453,-5385,-5189,-5285]]},{"type":"Polygon","id":20035,"arcs":[[-5355,5454,5455,5456,5457,-5022]]},{"type":"Polygon","id":20191,"arcs":[[-5458,5458,5459,5460,-5268,-5153]]},{"type":"Polygon","id":20119,"arcs":[[-5152,5461,5462,5463,-5262,-5097]]},{"type":"Polygon","id":20025,"arcs":[[-5267,5464,5465,5466,-5462,-5151]]},{"type":"Polygon","id":20007,"arcs":[[-5270,5467,5468,5469,5470,-5265,-5211]]},{"type":"Polygon","id":21087,"arcs":[[-5450,5471,5472,5473,-5279]]},{"type":"Polygon","id":51680,"arcs":[[-5215,5474,-5345]]},{"type":"Polygon","id":6027,"arcs":[[-4763,-4239,5475,5476,5477,5478,-5389,-4557]]},{"type":"MultiPolygon","id":51095,"arcs":[[[5479,5480,5481,5482,-5440,-5340,5483,5484,5485]]]},{"type":"Polygon","id":21099,"arcs":[[-5280,-5474,5486,5487,5488,-5342,-5088]]},{"type":"Polygon","id":21193,"arcs":[[-5436,5489,5490,5491,5492,-5427,-5302]]},{"type":"Polygon","id":51031,"arcs":[[-5414,5493,5494,5495,-5346,-5475,-5214]]},{"type":"Polygon","id":29077,"arcs":[[-5162,-5445,5496,5497,-5394,-5205]]},{"type":"Polygon","id":21139,"arcs":[[-5424,5498,5499,5500,5501,-5371,-5369]]},{"type":"Polygon","id":29203,"arcs":[[-5358,5502,5503,5504,-5364,-5226]]},{"type":"Polygon","id":8007,"arcs":[[-5126,-5202,5505,5506,5507,-5331,-4996]]},{"type":"Polygon","id":51161,"arcs":[[-5349,5508,5509,5510,-5313,-5222],[5511,5512]]},{"type":"Polygon","id":21177,"arcs":[[5513,5514,5515,5516,-5401,-5306,-5258]]},{"type":"Polygon","id":51147,"arcs":[[-5243,-5439,5517,5518,5519,-5412,-5231]]},{"type":"Polygon","id":8021,"arcs":[[-5201,-5240,-5318,5520,5521,-5506]]},{"type":"Polygon","id":21031,"arcs":[[5522,5523,-5514,-5257,-5344,5524]]},{"type":"Polygon","id":20129,"arcs":[[5525,5526,5527,-5326,-5254]]},{"type":"Polygon","id":20189,"arcs":[[-5264,5528,5529,-5526,-5253,-5261]]},{"type":"Polygon","id":20175,"arcs":[[-5464,5530,5531,-5529,-5263]]},{"type":"Polygon","id":20125,"arcs":[[-5272,-5276,5532,5533,5534,5535,-5353]]},{"type":"Polygon","id":20077,"arcs":[[5536,5537,-5468,-5269,-5461]]},{"type":"Polygon","id":20033,"arcs":[[5538,5539,-5465,-5266,-5471]]},{"type":"Polygon","id":20099,"arcs":[[-5312,5540,5541,5542,-5533,-5275]]},{"type":"Polygon","id":21033,"arcs":[[-5403,5543,5544,5545,-5422,-5322]]},{"type":"MultiPolygon","id":51199,"arcs":[[[5546,5547,5548,5549,-5481,5550,-5485]]]},{"type":"Polygon","id":29097,"arcs":[[-5396,5551,5552,5553,-5310,-5320]]},{"type":"Polygon","id":51121,"arcs":[[5554,5555,5556,5557,-5452,-5314,-5511]]},{"type":"Polygon","id":51515,"arcs":[[-5350]]},{"type":"Polygon","id":21051,"arcs":[[-5493,5558,5559,5560,5561,-5398,-5428]]},{"type":"Polygon","id":21199,"arcs":[[5562,5563,5564,-5416,-5337,-5430,5565]]},{"type":"Polygon","id":20021,"arcs":[[-5554,5566,5567,5568,-5541,-5311]]},{"type":"Polygon","id":21061,"arcs":[[-5489,5569,5570,-5525,-5343]]},{"type":"Polygon","id":51770,"arcs":[[5571,-5513]]},{"type":"Polygon","id":17127,"arcs":[[-5502,5572,5573,-5375,-5372]]},{"type":"Polygon","id":51185,"arcs":[[-5387,5574,5575,5576,-5431,-5420]]},{"type":"Polygon","id":17003,"arcs":[[5577,5578,5579,5580,-5360,-5379]]},{"type":"Polygon","id":21125,"arcs":[[-5562,5581,5582,5583,-5566,-5429,-5399]]},{"type":"Polygon","id":17153,"arcs":[[-5376,-5574,5584,5585,-5578,-5378]]},{"type":"Polygon","id":51775,"arcs":[[-5572,-5512]]},{"type":"Polygon","id":21131,"arcs":[[5586,5587,-5559,-5492]]},{"type":"Polygon","id":51670,"arcs":[[5588,-5405,5589]]},{"type":"Polygon","id":29223,"arcs":[[-5384,5590,5591,5592,-5356,-5250,-5325]]},{"type":"Polygon","id":51149,"arcs":[[5593,5594,5595,5596,5597,-5406,-5589]]},{"type":"Polygon","id":51830,"arcs":[[5598,-5486,-5551,-5480]]},{"type":"Polygon","id":21001,"arcs":[[-5418,5599,5600,5601,-5472,-5449]]},{"type":"Polygon","id":51051,"arcs":[[5602,5603,-5245,-5433]]},{"type":"Polygon","id":20019,"arcs":[[-5536,5604,5605,-5455,-5354]]},{"type":"Polygon","id":51021,"arcs":[[5606,5607,5608,-5575,-5386,-5454]]},{"type":"Polygon","id":51570,"arcs":[[5609,-5408]]},{"type":"Polygon","id":51135,"arcs":[[5610,5611,-5518,-5438,5612]]},{"type":"Polygon","id":29109,"arcs":[[-5498,5613,5614,5615,5616,-5552,-5395]]},{"type":"Polygon","id":6087,"arcs":[[5617,5618,5619,-5299,-5447]]},{"type":"Polygon","id":51053,"arcs":[[5620,-5597,5621,5622,5623,-5613,-5437,-5410]]},{"type":"Polygon","id":21133,"arcs":[[-5247,5624,5625,-5490,-5435]]},{"type":"Polygon","id":29201,"arcs":[[5626,5627,5628,-5361,-5581]]},{"type":"Polygon","id":51037,"arcs":[[5629,5630,5631,-5494,-5413,-5520]]},{"type":"Polygon","id":51155,"arcs":[[5632,-5556,5633,5634,5635,-5607,-5453,-5558]]},{"type":"Polygon","id":51730,"arcs":[[-5621,-5409,-5610,-5407,-5598]]},{"type":"Polygon","id":51181,"arcs":[[5636,5637,5638,-5595,5639]]},{"type":"Polygon","id":51067,"arcs":[[5640,5641,5642,5643,-5509,-5348]]},{"type":"Polygon","id":21007,"arcs":[[5644,5645,-5579,-5586,5646]]},{"type":"Polygon","id":21145,"arcs":[[-5501,5647,5648,5649,-5647,-5585,-5573]]},{"type":"Polygon","id":51700,"arcs":[[5650,5651,-5482,-5550]]},{"type":"Polygon","id":51195,"arcs":[[5652,5653,5654,5655,-5625,-5246,-5604],[5656]]},{"type":"Polygon","id":21227,"arcs":[[-5571,5657,5658,5659,5660,-5523]]},{"type":"Polygon","id":21207,"arcs":[[-5565,5661,5662,5663,-5600,-5417]]},{"type":"Polygon","id":21169,"arcs":[[-5602,5664,5665,5666,-5487,-5473]]},{"type":"Polygon","id":21143,"arcs":[[-5546,5667,5668,-5499,-5423]]},{"type":"MultiPolygon","id":51735,"arcs":[[[5669,5670,-5548,5671]]]},{"type":"Polygon","id":21009,"arcs":[[5672,5673,-5658,-5570,-5488,-5667]]},{"type":"Polygon","id":21047,"arcs":[[-5517,5674,5675,5676,5677,-5544,-5402]]},{"type":"Polygon","id":51750,"arcs":[[-5633,-5557]]},{"type":"Polygon","id":51093,"arcs":[[5678,5679,5680,5681,-5637,5682]]},{"type":"Polygon","id":51167,"arcs":[[5683,5684,5685,-5653,-5603,-5432,-5577]]},{"type":"Polygon","id":51143,"arcs":[[5686,5687,5688,5689,5690,5691,-5641,-5347,-5496]]},{"type":"Polygon","id":51063,"arcs":[[-5644,5692,5693,-5634,-5555,-5510]]},{"type":"Polygon","id":29207,"arcs":[[-5362,-5629,5694,5695,5696,-5591,-5383]]},{"type":"Polygon","id":51111,"arcs":[[-5612,5697,5698,-5630,-5519]]},{"type":"Polygon","id":51650,"arcs":[[-5651,-5549,-5671,5699]]},{"type":"Polygon","id":51183,"arcs":[[5700,5701,-5622,-5596,-5639]]},{"type":"Polygon","id":29035,"arcs":[[-5593,5702,5703,5704,-5503,-5357]]},{"type":"Polygon","id":29043,"arcs":[[-5444,5705,5706,5707,-5614,-5497]]},{"type":"Polygon","id":51197,"arcs":[[5708,5709,5710,-5608,-5636]]},{"type":"Polygon","id":21141,"arcs":[[5711,5712,5713,-5515,-5524,-5661]]},{"type":"Polygon","id":21219,"arcs":[[-5714,5714,5715,-5675,-5516]]},{"type":"Polygon","id":29067,"arcs":[[-5451,-5366,5716,5717,5718,-5706,-5443]]},{"type":"Polygon","id":21157,"arcs":[[-5669,5719,5720,5721,-5648,-5500]]},{"type":"Polygon","id":29133,"arcs":[[-5646,5722,5723,5724,5725,-5627,-5580]]},{"type":"Polygon","id":51083,"arcs":[[-5632,5726,5727,5728,5729,-5687,-5495]]},{"type":"Polygon","id":29091,"arcs":[[-5505,5730,5731,5732,-5717,-5365]]},{"type":"Polygon","id":29145,"arcs":[[-5617,5733,5734,5735,-5567,-5553]]},{"type":"Polygon","id":21121,"arcs":[[5736,5737,-5582,-5561]]},{"type":"Polygon","id":51025,"arcs":[[-5624,5738,5739,5740,5741,-5698,-5611]]},{"type":"Polygon","id":21095,"arcs":[[-5626,-5656,5742,5743,-5587,-5491]]},{"type":"Polygon","id":51173,"arcs":[[-5609,-5711,5744,5745,-5684,-5576]]},{"type":"Polygon","id":4017,"arcs":[[5746,5747,5748,5749,-4741]]},{"type":"Polygon","id":4005,"arcs":[[-5750,5750,5751,5752,-5425,-4742]]},{"type":"Polygon","id":21221,"arcs":[[5753,5754,-5720,-5668,-5545,-5678]]},{"type":"Polygon","id":40105,"arcs":[[-5543,5755,5756,5757,-5534]]},{"type":"Polygon","id":40113,"arcs":[[5758,5759,5760,5761,5762,-5456,-5606]]},{"type":"Polygon","id":40151,"arcs":[[-5470,5763,5764,5765,5766,-5539]]},{"type":"Polygon","id":40035,"arcs":[[-5569,5767,5768,5769,5770,-5756,-5542]]},{"type":"Polygon","id":40147,"arcs":[[-5758,5771,5772,-5759,-5605,-5535]]},{"type":"Polygon","id":40053,"arcs":[[5773,5774,5775,-5537,-5460]]},{"type":"Polygon","id":40003,"arcs":[[-5538,-5776,5776,5777,-5764,-5469]]},{"type":"Polygon","id":40071,"arcs":[[-5763,5778,5779,-5774,-5459,-5457]]},{"type":"Polygon","id":40115,"arcs":[[-5736,5780,5781,-5768,-5568]]},{"type":"Polygon","id":40059,"arcs":[[-5540,-5767,5782,5783,5784,-5466]]},{"type":"Polygon","id":35039,"arcs":[[-5522,5785,5786,5787,5788,5789,5790,-5507]]},{"type":"Polygon","id":35045,"arcs":[[-5791,5791,5792,5793,-5330,-5332,-5508]]},{"type":"Polygon","id":4001,"arcs":[[-5794,5794,5795,5796,5797,5798,-5747,-4740]]},{"type":"Polygon","id":35059,"arcs":[[5799,5800,5801,5802,5803,5804,-5218,-5328]]},{"type":"Polygon","id":40025,"arcs":[[-5528,5805,5806,5807,-5800,-5327]]},{"type":"Polygon","id":40139,"arcs":[[5808,5809,5810,5811,-5806,-5527,-5530,-5532]]},{"type":"Polygon","id":40007,"arcs":[[-5467,-5785,5812,5813,5814,-5809,-5531,-5463]]},{"type":"Polygon","id":51175,"arcs":[[5815,-5680,5816,5817,5818,5819,5820,-5701,-5638,-5682]]},{"type":"Polygon","id":21231,"arcs":[[5821,5822,5823,5824,-5662,-5564]]},{"type":"Polygon","id":35055,"arcs":[[5825,5826,-5786,-5521,-5317]]},{"type":"Polygon","id":35007,"arcs":[[-5805,5827,5828,-5826,-5316,-5219]]},{"type":"Polygon","id":29209,"arcs":[[5829,5830,5831,-5615,-5708]]},{"type":"Polygon","id":6069,"arcs":[[-5334,-5393,5832,-5618,-5446]]},{"type":"Polygon","id":21235,"arcs":[[-5738,5833,5834,5835,5836,-5583]]},{"type":"MultiPolygon","id":51710,"arcs":[[[5837,5838,5839]],[[5840,5841]]]},{"type":"Polygon","id":21147,"arcs":[[-5837,5842,5843,-5822,-5563,-5584]]},{"type":"Polygon","id":51720,"arcs":[[-5657]]},{"type":"Polygon","id":21013,"arcs":[[-5588,-5744,5844,5845,-5834,-5737,-5560]]},{"type":"Polygon","id":21039,"arcs":[[-5650,5846,5847,-5723,-5645]]},{"type":"Polygon","id":21083,"arcs":[[-5722,5848,5849,5850,5851,-5847,-5649]]},{"type":"Polygon","id":21057,"arcs":[[5852,5853,5854,-5665,-5601,-5664]]},{"type":"Polygon","id":21003,"arcs":[[-5674,5855,5856,5857,5858,-5659]]},{"type":"Polygon","id":29009,"arcs":[[-5832,5859,5860,5861,-5734,-5616]]},{"type":"MultiPolygon","id":51810,"arcs":[[[5862,5863,5864]],[[5865,5866,5867,5868,5869,5870,5871,-5841,5872]]]},{"type":"Polygon","id":51035,"arcs":[[5873,5874,5875,5876,5877,-5709,-5635,-5694]]},{"type":"Polygon","id":51191,"arcs":[[5878,5879,5880,5881,-5685,-5746,5882,5883,5884,5885]]},{"type":"Polygon","id":29023,"arcs":[[-5697,5886,5887,5888,-5703,-5592]]},{"type":"MultiPolygon","id":51740,"arcs":[[[-5839,5889,5890]],[[5891,5892,5893]]]},{"type":"Polygon","id":51800,"arcs":[[-5893,5894,5895,5896,-5817,-5679,5897]]},{"type":"Polygon","id":6053,"arcs":[[-5833,-5392,5898,5899,5900,-5619]]},{"type":"Polygon","id":51081,"arcs":[[-5702,-5821,5901,-5739,-5623],[5902]]},{"type":"Polygon","id":51105,"arcs":[[5903,5904,5905,-5845,-5743,-5655]]},{"type":"Polygon","id":51117,"arcs":[[-5742,5906,5907,5908,-5727,-5631,-5699]]},{"type":"Polygon","id":21053,"arcs":[[-5825,5909,5910,-5853,-5663]]},{"type":"Polygon","id":29149,"arcs":[[-5705,5911,5912,5913,5914,-5731,-5504]]},{"type":"Polygon","id":51169,"arcs":[[-5686,-5882,5915,5916,5917,-5904,-5654]]},{"type":"Polygon","id":21213,"arcs":[[5918,5919,-5712,-5660,-5859]]},{"type":"Polygon","id":51141,"arcs":[[5920,5921,5922,-5874,-5693,-5643]]},{"type":"Polygon","id":29143,"arcs":[[-5726,5923,5924,5925,5926,5927,5928,-5695,-5628]]},{"type":"Polygon","id":51550,"arcs":[[5929,-5890,-5838,5930,-5871,5931,5932,-5895,-5892]]},{"type":"Polygon","id":51089,"arcs":[[5933,5934,-5921,-5642,-5692],[5935]]},{"type":"Polygon","id":32003,"arcs":[[5936,5937,-5476,-4238,-4614]]},{"type":"Polygon","id":21171,"arcs":[[-5855,5938,5939,-5856,-5673,-5666]]},{"type":"Polygon","id":29181,"arcs":[[-5889,5940,5941,-5912,-5704]]},{"type":"Polygon","id":29213,"arcs":[[-5719,5942,5943,5944,5945,-5830,-5707]]},{"type":"Polygon","id":40153,"arcs":[[5946,5947,5948,-5783,-5766]]},{"type":"Polygon","id":51077,"arcs":[[-5878,5949,-5876,5950,5951,5952,5953,-5883,-5745,-5710]]},{"type":"Polygon","id":29153,"arcs":[[-5733,5954,5955,5956,-5943,-5718]]},{"type":"Polygon","id":21105,"arcs":[[-5852,5957,5958,5959,-5724,-5848]]},{"type":"Polygon","id":29119,"arcs":[[-5862,5960,5961,-5781,-5735]]},{"type":"Polygon","id":21035,"arcs":[[-5755,5962,5963,-5849,-5721]]},{"type":"Polygon","id":6107,"arcs":[[5964,5965,-5390,-5479]]},{"type":"Polygon","id":51595,"arcs":[[-5903]]},{"type":"Polygon","id":51690,"arcs":[[-5936]]},{"type":"Polygon","id":51620,"arcs":[[-5816,-5681]]},{"type":"Polygon","id":51640,"arcs":[[-5950,-5877]]},{"type":"Polygon","id":47161,"arcs":[[-5677,5966,5967,5968,5969,-5963,-5754]]},{"type":"Polygon","id":40041,"arcs":[[-5962,5970,5971,5972,5973,-5769,-5782]]},{"type":"Polygon","id":47147,"arcs":[[5974,5975,5976,-5715,-5713,-5920,5977]]},{"type":"Polygon","id":47165,"arcs":[[5978,5979,5980,5981,-5978,-5919,-5858]]},{"type":"MultiPolygon","id":21075,"arcs":[[[5982,-5926]],[[5983,5984,-5924,-5725,-5960]]]},{"type":"Polygon","id":47125,"arcs":[[-5977,5985,5986,5987,-5967,-5676,-5716]]},{"type":"Polygon","id":51590,"arcs":[[5988,-5689]]},{"type":"Polygon","id":47111,"arcs":[[5989,5990,5991,-5979,-5857,-5940,5992]]},{"type":"MultiPolygon","id":51520,"arcs":[[[-5879]],[[-5886,5993,-5880]]]},{"type":"Polygon","id":29069,"arcs":[[-5929,5994,5995,5996,5997,5998,-5887,-5696]]},{"type":"Polygon","id":47137,"arcs":[[5999,6000,6001,6002,-5910,-5824]]},{"type":"Polygon","id":47027,"arcs":[[6003,6004,-5993,-5939,-5854,-5911,-6003]]},{"type":"Polygon","id":47163,"arcs":[[6005,6006,6007,-5916,-5881,-5994,-5885,6008]]},{"type":"Polygon","id":47091,"arcs":[[-5954,6009,6010,6011,6012,-6009,-5884]]},{"type":"Polygon","id":47151,"arcs":[[6013,6014,6015,6016,-6000,-5823,-5844]]},{"type":"Polygon","id":47025,"arcs":[[6017,6018,6019,6020,-5835,-5846,-5906]]},{"type":"Polygon","id":40103,"arcs":[[6021,6022,6023,6024,-5779,-5762]]},{"type":"Polygon","id":47067,"arcs":[[-5918,6025,6026,-6018,-5905]]},{"type":"Polygon","id":40131,"arcs":[[6027,6028,6029,-5772,-5757,-5771]]},{"type":"Polygon","id":47013,"arcs":[[6030,6031,-6014,-5843,-5836,-6021]]},{"type":"Polygon","id":47073,"arcs":[[-6008,6032,6033,6034,6035,-6026,-5917]]},{"type":"Polygon","id":40047,"arcs":[[-5780,-6025,6036,6037,6038,-5777,-5775]]},{"type":"Polygon","id":40045,"arcs":[[-5949,6039,6040,6041,6042,-5813,-5784]]},{"type":"Polygon","id":37009,"arcs":[[6043,6044,6045,-6010,-5953]]},{"type":"Polygon","id":47049,"arcs":[[-6017,6046,6047,6048,6049,-6001]]},{"type":"Polygon","id":37005,"arcs":[[6050,6051,-6044,-5952]]},{"type":"Polygon","id":40117,"arcs":[[6052,6053,6054,-6022,-5761]]},{"type":"Polygon","id":37171,"arcs":[[-5923,6055,6056,6057,6058,-6051,-5951,-5875]]},{"type":"Polygon","id":37073,"arcs":[[6059,6060,6061,6062,6063,6064,-5818,-5897]]},{"type":"MultiPolygon","id":37053,"arcs":[[[6065,6066,-5932,-5870,6067]],[[-5866,6068,6069,6070]],[[-6072,5863,-6073,5867]]]},{"type":"Polygon","id":37169,"arcs":[[-5935,6073,6074,-6056,-5922]]},{"type":"Polygon","id":37029,"arcs":[[-5896,-5933,-6067,6075,6076,-6060]]},{"type":"Polygon","id":37185,"arcs":[[6077,6078,6079,6080,-5907,-5741]]},{"type":"Polygon","id":37131,"arcs":[[6081,6082,-6078,-5740,-5902,-5820,6083]]},{"type":"Polygon","id":37091,"arcs":[[-6065,6084,6085,-6084,-5819]]},{"type":"Polygon","id":37145,"arcs":[[6086,6087,6088,6089,-5729]]},{"type":"Polygon","id":37181,"arcs":[[-6081,6090,6091,-5908]]},{"type":"Polygon","id":37077,"arcs":[[6092,6093,6094,-6087,-5728,-5909,-6092]]},{"type":"Polygon","id":37157,"arcs":[[-5691,6095,6096,6097,-6074,-5934]]},{"type":"Polygon","id":37033,"arcs":[[-6090,6098,6099,-6096,-5690,-5989,-5688,-5730]]},{"type":"Polygon","id":47133,"arcs":[[-6050,6100,6101,-6004,-6002]]},{"type":"Polygon","id":47087,"arcs":[[-6102,6102,6103,-5990,-6005]]},{"type":"Polygon","id":37083,"arcs":[[6104,6105,6106,6107,-6079,-6083]]},{"type":"Polygon","id":40097,"arcs":[[-5974,6108,6109,-6028,-5770]]},{"type":"Polygon","id":47019,"arcs":[[6110,6111,6112,6113,-6006,-6013]]},{"type":"Polygon","id":37139,"arcs":[[6114,6115,-6061,-6077]]},{"type":"Polygon","id":47131,"arcs":[[-5959,6116,6117,6118,6119,-5984]]},{"type":"Polygon","id":40093,"arcs":[[-5778,-6039,6120,6121,6122,-5947,-5765]]},{"type":"Polygon","id":47183,"arcs":[[6123,6124,6125,-6117,-5958,-5851]]},{"type":"Polygon","id":47095,"arcs":[[6126,6127,-5927,-5983,-5925,-5985,-6120]]},{"type":"Polygon","id":48421,"arcs":[[6128,6129,-5807,-5812,6130]]},{"type":"Polygon","id":47079,"arcs":[[-5970,6131,6132,-6124,-5850,-5964]]},{"type":"Polygon","id":48195,"arcs":[[6133,6134,-6131,-5811,6135]]},{"type":"Polygon","id":48111,"arcs":[[-5808,-6130,6136,-5801]]},{"type":"Polygon","id":48357,"arcs":[[6137,6138,-6136,-5810,-5815]]},{"type":"Polygon","id":48295,"arcs":[[6139,6140,-6138,-5814,-6043]]},{"type":"Polygon","id":5007,"arcs":[[-5861,6141,6142,6143,6144,-5971,-5961]]},{"type":"Polygon","id":5049,"arcs":[[6145,6146,6147,-5955,-5732,-5915]]},{"type":"Polygon","id":5015,"arcs":[[-5831,-5946,6148,6149,6150,-6142,-5860]]},{"type":"Polygon","id":5135,"arcs":[[-5914,6151,6152,6153,6154,-6146]]},{"type":"Polygon","id":5121,"arcs":[[6155,6156,-6152,-5913,-5942,6157]]},{"type":"Polygon","id":5009,"arcs":[[6158,6159,6160,-6149,-5945]]},{"type":"Polygon","id":5089,"arcs":[[6161,6162,-6159,-5944,-5957]]},{"type":"Polygon","id":5005,"arcs":[[-6148,6163,6164,6165,-6162,-5956]]},{"type":"Polygon","id":5021,"arcs":[[-5888,-5999,6166,-6158,-5941]]},{"type":"Polygon","id":6031,"arcs":[[6167,6168,-5899,-5391,-5966]]},{"type":"Polygon","id":47169,"arcs":[[-5992,6169,6170,-5980]]},{"type":"Polygon","id":47021,"arcs":[[6171,6172,6173,-5986,-5976],[6174]]},{"type":"Polygon","id":47179,"arcs":[[-6114,6175,6176,-6033,-6007]]},{"type":"Polygon","id":37193,"arcs":[[-6059,6177,6178,6179,6180,6181,-6045,-6052]]},{"type":"Polygon","id":47173,"arcs":[[6182,6183,6184,-6031,-6020]]},{"type":"Polygon","id":47159,"arcs":[[-6104,6185,6186,6187,-6170,-5991]]},{"type":"Polygon","id":29155,"arcs":[[-6128,6188,6189,-5995,-5928]]},{"type":"Polygon","id":40143,"arcs":[[-6030,6190,6191,6192,-6053,-5760,-5773]]},{"type":"Polygon","id":47057,"arcs":[[-6027,-6036,6193,6194,6195,-6183,-6019]]},{"type":"Polygon","id":47037,"arcs":[[-5982,6196,6197,6198,-6172,-5975]]},{"type":"Polygon","id":47059,"arcs":[[6199,6200,6201,6202,-6034,-6177]]},{"type":"Polygon","id":37189,"arcs":[[-6182,6203,6204,-6011,-6046]]},{"type":"Polygon","id":37143,"arcs":[[6205,6206,-6062,-6116]]},{"type":"Polygon","id":47129,"arcs":[[-6016,6207,6208,6209,-6047]]},{"type":"Polygon","id":47083,"arcs":[[-5988,6210,6211,6212,-5968]]},{"type":"Polygon","id":47005,"arcs":[[-6213,6213,6214,6215,6216,-6132,-5969]]},{"type":"Polygon","id":47189,"arcs":[[-6171,-6188,6217,6218,6219,-6197,-5981]]},{"type":"MultiPolygon","id":37041,"arcs":[[[6220,-6063,-6207]]]},{"type":"Polygon","id":47063,"arcs":[[-6203,6221,6222,-6194,-6035]]},{"type":"MultiPolygon","id":47043,"arcs":[[[-6175]],[[-6174,6223,6224,6225,-6211,-5987]]]},{"type":"Polygon","id":5087,"arcs":[[6226,6227,6228,6229,6230,-6143,-6151]]},{"type":"Polygon","id":47141,"arcs":[[-6049,6231,6232,6233,-6186,-6103,-6101]]},{"type":"Polygon","id":47001,"arcs":[[-6185,6234,6235,-6208,-6015,-6032]]},{"type":"Polygon","id":37197,"arcs":[[6236,6237,6238,-6178,-6058]]},{"type":"Polygon","id":37011,"arcs":[[6239,6240,6241,6242,-6111,-6012,-6205]]},{"type":"Polygon","id":37069,"arcs":[[6243,6244,-6093,-6091,-6080]]},{"type":"Polygon","id":5055,"arcs":[[-5998,6245,6246,-6156,-6167]]},{"type":"Polygon","id":35033,"arcs":[[-5829,6247,6248,6249,-5787,-5827]]},{"type":"Polygon","id":37067,"arcs":[[6250,6251,6252,-6237,-6057,-6075]]},{"type":"Polygon","id":5065,"arcs":[[-6155,6253,6254,-6164,-6147]]},{"type":"Polygon","id":47171,"arcs":[[6255,6256,6257,-6200,-6176,-6113]]},{"type":"Polygon","id":5075,"arcs":[[-6247,6258,6259,6260,-6153,-6157]]},{"type":"Polygon","id":37081,"arcs":[[6261,6262,6263,-6251,-6098]]},{"type":"Polygon","id":37001,"arcs":[[6264,6265,6266,-6262,-6097,-6100]]},{"type":"Polygon","id":40119,"arcs":[[6267,6268,6269,-6023,-6055]]},{"type":"Polygon","id":47085,"arcs":[[-6226,6270,6271,-6214,-6212]]},{"type":"Polygon","id":37015,"arcs":[[6272,6273,6274,-6105,-6082,-6086]]},{"type":"Polygon","id":37135,"arcs":[[-6089,6275,6276,-6265,-6099]]},{"type":"Polygon","id":37063,"arcs":[[-6095,6277,6278,-6276,-6088]]},{"type":"Polygon","id":5143,"arcs":[[6279,6280,-6144,-6231]]},{"type":"MultiPolygon","id":37055,"arcs":[[[6281,6282,6283]],[[6284,6285]],[[-6070,6286]]]},{"type":"Polygon","id":47053,"arcs":[[6287,6288,6289,6290,-6118,-6126]]},{"type":"MultiPolygon","id":35043,"arcs":[[[6291,6292]],[[6293,6294,6295,6296,6297,-5792,-5790]]]},{"type":"Polygon","id":35021,"arcs":[[-5804,6298,6299,-6248,-5828]]},{"type":"Polygon","id":47045,"arcs":[[6300,6301,6302,-6189,-6127,-6119,-6291]]},{"type":"Polygon","id":37127,"arcs":[[-6108,6303,6304,6305,-6244]]},{"type":"Polygon","id":47089,"arcs":[[-6223,6306,6307,6308,-6195]]},{"type":"Polygon","id":47093,"arcs":[[-6309,6309,6310,6311,6312,-6235,-6184,-6196]]},{"type":"Polygon","id":47029,"arcs":[[6313,6314,6315,-6307,-6222,-6202]]},{"type":"Polygon","id":47035,"arcs":[[-6210,6316,6317,6318,6319,6320,6321,6322,-6232,-6048]]},{"type":"Polygon","id":40073,"arcs":[[6323,6324,6325,-6121,-6038]]},{"type":"Polygon","id":40083,"arcs":[[-6024,-6270,6326,6327,-6324,-6037]]},{"type":"Polygon","id":40011,"arcs":[[-6326,6328,6329,6330,6331,-6122]]},{"type":"Polygon","id":40043,"arcs":[[6332,6333,-6040,-5948,-6123,-6332]]},{"type":"Polygon","id":40037,"arcs":[[-6193,6334,6335,6336,-6268,-6054]]},{"type":"Polygon","id":40145,"arcs":[[-6110,6337,6338,6339,-6191,-6029]]},{"type":"Polygon","id":40021,"arcs":[[-5973,6340,6341,6342,-6338,-6109]]},{"type":"Polygon","id":40001,"arcs":[[-6145,-6281,6343,6344,-6341,-5972]]},{"type":"Polygon","id":37121,"arcs":[[-6243,6345,6346,-6256,-6112]]},{"type":"Polygon","id":37065,"arcs":[[6347,6348,6349,-6304,-6107]]},{"type":"Polygon","id":47017,"arcs":[[-6217,6350,6351,6352,-6288,-6125,-6133]]},{"type":"Polygon","id":47041,"arcs":[[6353,6354,6355,-6218,-6187,-6234]]},{"type":"Polygon","id":5137,"arcs":[[6356,6357,6358,6359,-6165,-6255]]},{"type":"Polygon","id":5101,"arcs":[[-6161,6360,6361,6362,-6227,-6150]]},{"type":"Polygon","id":37027,"arcs":[[-6181,6363,6364,6365,-6240,-6204]]},{"type":"Polygon","id":5129,"arcs":[[-6166,-6360,6366,6367,-6361,-6160,-6163]]},{"type":"Polygon","id":47149,"arcs":[[6368,6369,6370,6371,6372,-6198,-6220]]},{"type":"Polygon","id":37199,"arcs":[[6373,6374,6375,-6257,-6347]]},{"type":"MultiPolygon","id":47185,"arcs":[[[6376,-6321]],[[-6323,6377,6378,-6354,-6233]]]},{"type":"Polygon","id":37183,"arcs":[[-6245,6379,6380,6381,-6278,-6094]]},{"type":"Polygon","id":37117,"arcs":[[6382,6383,6384,-6348,-6106,-6275]]},{"type":"Polygon","id":37115,"arcs":[[-6258,-6376,6385,6386,-6314,-6201]]},{"type":"Polygon","id":37059,"arcs":[[6387,6388,6389,-6238,-6253]]},{"type":"Polygon","id":48393,"arcs":[[-6141,6390,6391,6392,6393,-6134,-6139]]},{"type":"Polygon","id":48211,"arcs":[[-6140,-6042,6394,6395,-6391]]},{"type":"Polygon","id":48233,"arcs":[[-6135,-6394,6396,6397]]},{"type":"Polygon","id":48205,"arcs":[[6398,6399,6400,-5802,-6137]]},{"type":"Polygon","id":37097,"arcs":[[-6239,-6390,6401,6402,6403,6404,6405,6406,-6179]]},{"type":"Polygon","id":48341,"arcs":[[-6398,6407,6408,6409,-6399,-6129]]},{"type":"Polygon","id":47187,"arcs":[[-6373,6410,6411,6412,-6224,-6173,-6199]]},{"type":"Polygon","id":37003,"arcs":[[6413,-6364,-6180,-6407]]},{"type":"Polygon","id":47145,"arcs":[[6414,6415,6416,6417,6418,-6317,-6209,-6236,-6313,6419]]},{"type":"Polygon","id":47155,"arcs":[[-6316,6420,6421,6422,-6310,-6308]]},{"type":"Polygon","id":37057,"arcs":[[-6264,6423,6424,6425,-6388,-6252]]},{"type":"Polygon","id":40129,"arcs":[[-6334,6426,6427,6428,-6395,-6041]]},{"type":"Polygon","id":35031,"arcs":[[-6298,6429,-5795,-5793]]},{"type":"Polygon","id":35049,"arcs":[[-6250,6430,6431,6432,-6295,6433,-6293,6434,-5788]]},{"type":"Polygon","id":5093,"arcs":[[6435,6436,6437,6438,6439,6440,6441,-5996,-6190,-6303]]},{"type":"Polygon","id":37023,"arcs":[[-6366,6442,6443,6444,6445,6446,-6241]]},{"type":"Polygon","id":47033,"arcs":[[6447,6448,6449,-6301,-6290]]},{"type":"Polygon","id":5031,"arcs":[[-6442,6450,6451,-6259,-6246,-5997]]},{"type":"Polygon","id":37177,"arcs":[[6452,6453,6454]]},{"type":"Polygon","id":47081,"arcs":[[-6413,6455,6456,6457,-6271,-6225]]},{"type":"Polygon","id":37187,"arcs":[[-6454,6458,6459,-6383,-6274,6460]]},{"type":"Polygon","id":35028,"arcs":[[-5789,-6435,-6292,-6434,-6294]]},{"type":"Polygon","id":47015,"arcs":[[6461,6462,-6369,-6219,-6356]]},{"type":"Polygon","id":37111,"arcs":[[6463,6464,-6374,-6346,-6242,-6447]]},{"type":"Polygon","id":47097,"arcs":[[-6450,6465,6466,-6436,-6302]]},{"type":"Polygon","id":40081,"arcs":[[-6337,6467,6468,6469,-6327,-6269]]},{"type":"Polygon","id":5063,"arcs":[[-6261,6470,6471,6472,-6357,-6254,-6154]]},{"type":"Polygon","id":37151,"arcs":[[-6267,6473,6474,6475,-6424,-6263]]},{"type":"MultiPolygon","id":47105,"arcs":[[[6476,6477]],[[-6415]],[[-6420,-6312,6478,6479,6480,-6416]]]},{"type":"Polygon","id":5067,"arcs":[[-6260,-6452,6481,6482,6483,6484,-6471]]},{"type":"Polygon","id":47009,"arcs":[[6485,6486,6487,-6479,-6311,-6423]]},{"type":"Polygon","id":37037,"arcs":[[-6279,-6382,6488,6489,6490,-6474,-6266,-6277]]},{"type":"Polygon","id":35047,"arcs":[[-6300,6491,6492,6493,-6431,-6249]]},{"type":"Polygon","id":37195,"arcs":[[6494,6495,6496,6497,-6305,-6350]]},{"type":"Polygon","id":37159,"arcs":[[-6426,6498,6499,-6402,-6389]]},{"type":"Polygon","id":40111,"arcs":[[-6192,-6340,6500,6501,6502,-6335]]},{"type":"Polygon","id":40101,"arcs":[[-6343,6503,6504,6505,-6501,-6339]]},{"type":"Polygon","id":47177,"arcs":[[-6379,6506,6507,6508,6509,-6462,-6355]]},{"type":"Polygon","id":47119,"arcs":[[6510,6511,6512,6513,-6456,-6412]]},{"type":"Polygon","id":47039,"arcs":[[6514,6515,6516,6517,-6351,-6216]]},{"type":"Polygon","id":47135,"arcs":[[-6458,6518,6519,-6515,-6215,-6272]]},{"type":"Polygon","id":37147,"arcs":[[-6385,6520,6521,6522,6523,6524,6525,-6495,-6349]]},{"type":"Polygon","id":37035,"arcs":[[-6406,6526,-6443,-6365,-6414]]},{"type":"Polygon","id":37021,"arcs":[[-6465,6527,6528,6529,-6386,-6375]]},{"type":"Polygon","id":47143,"arcs":[[6530,6531,6532,-6318,-6419]]},{"type":"Polygon","id":47175,"arcs":[[-6322,-6377,-6320,6533,6534,-6507,-6378]]},{"type":"Polygon","id":47077,"arcs":[[-6518,6535,6536,6537,-6352]]},{"type":"Polygon","id":47075,"arcs":[[6538,6539,6540,6541,-6466,-6449]]},{"type":"Polygon","id":37101,"arcs":[[-6498,6542,6543,6544,-6380,-6306]]},{"type":"Polygon","id":40039,"arcs":[[-6331,6545,6546,6547,-6427,-6333]]},{"type":"Polygon","id":6071,"arcs":[[6548,6549,6550,6551,6552,6553,-5477,-5938]]},{"type":"Polygon","id":6079,"arcs":[[-6169,6554,6555,6556,-5900]]},{"type":"Polygon","id":6029,"arcs":[[-6554,6557,6558,6559,-6555,-6168,-5965,-5478]]},{"type":"Polygon","id":47113,"arcs":[[-6353,-6538,6560,6561,-6539,-6448,-6289]]},{"type":"Polygon","id":5141,"arcs":[[-6359,6562,6563,6564,6565,-6367]]},{"type":"Polygon","id":37087,"arcs":[[-6530,6566,6567,6568,-6421,-6315,-6387]]},{"type":"Polygon","id":5047,"arcs":[[6569,6570,6571,-6229,6572]]},{"type":"Polygon","id":47007,"arcs":[[-6533,6573,6574,-6534,-6319]]},{"type":"Polygon","id":5071,"arcs":[[-6228,-6363,6575,6576,-6573]]},{"type":"Polygon","id":5033,"arcs":[[6577,6578,-6344,-6280,-6230,-6572]]},{"type":"Polygon","id":47121,"arcs":[[6579,6580,6581,-6531,-6418]]},{"type":"Polygon","id":35037,"arcs":[[-6401,6582,6583,6584,6585,6586,6587,-6492,-6299,-5803]]},{"type":"MultiPolygon","id":37013,"arcs":[[[6588,6589,6590,-6523]],[[-6460,6591,6592,-6521,-6384]]]},{"type":"Polygon","id":5115,"arcs":[[-6368,-6566,6593,6594,6595,-6576,-6362]]},{"type":"Polygon","id":40017,"arcs":[[6596,6597,6598,6599,6600,-6329,-6325]]},{"type":"Polygon","id":40109,"arcs":[[-6470,6601,6602,-6597,-6328]]},{"type":"Polygon","id":5023,"arcs":[[-6473,6603,6604,-6563,-6358]]},{"type":"Polygon","id":47117,"arcs":[[6605,6606,6607,-6511,-6411,-6372]]},{"type":"Polygon","id":5111,"arcs":[[-6441,6608,6609,-6482,-6451]]},{"type":"MultiPolygon","id":37095,"arcs":[[[-6283,6610]],[[-6453,6611,-6285,6612,-6592,-6459]]]},{"type":"Polygon","id":47031,"arcs":[[-6463,-6510,6613,6614,6615,6616,-6370]]},{"type":"Polygon","id":47003,"arcs":[[-6617,6617,6618,-6606,-6371]]},{"type":"Polygon","id":37173,"arcs":[[6619,6620,6621,-6486,-6422,-6569]]},{"type":"Polygon","id":47123,"arcs":[[-6488,6622,6623,6624,6625,-6478,6626,-6480]]},{"type":"Polygon","id":47101,"arcs":[[-6514,6627,6628,-6519,-6457]]},{"type":"Polygon","id":37079,"arcs":[[6629,6630,-6496,-6526]]},{"type":"MultiPolygon","id":47167,"arcs":[[[6631,6632,-6439]],[[-6542,6633,6634,-6437,-6467]]]},{"type":"Polygon","id":47107,"arcs":[[-6481,-6627,-6477,-6626,6635,6636,-6580,-6417]]},{"type":"Polygon","id":40135,"arcs":[[-6345,-6579,6637,6638,6639,-6504,-6342]]},{"type":"Polygon","id":40107,"arcs":[[-6503,6640,6641,6642,6643,-6468,-6336]]},{"type":"Polygon","id":37105,"arcs":[[6644,6645,-6490]]},{"type":"Polygon","id":48359,"arcs":[[-6410,6646,6647,-6583,-6400]]},{"type":"Polygon","id":48065,"arcs":[[-6393,6648,6649,6650,-6408,-6397]]},{"type":"Polygon","id":48179,"arcs":[[6651,6652,-6649,-6392]]},{"type":"Polygon","id":48483,"arcs":[[-6429,6653,6654,-6652,-6396]]},{"type":"Polygon","id":48375,"arcs":[[-6651,6655,-6647,-6409]]},{"type":"Polygon","id":37161,"arcs":[[6656,6657,6658,6659,6660,-6528,-6464,-6446]]},{"type":"Polygon","id":37191,"arcs":[[-6631,6661,6662,6663,-6543,-6497]]},{"type":"Polygon","id":47023,"arcs":[[6664,6665,6666,-6561,-6537]]},{"type":"Polygon","id":37085,"arcs":[[-6545,6667,6668,6669,-6645,-6489,-6381]]},{"type":"Polygon","id":37045,"arcs":[[6670,6671,6672,6673,-6657,-6445]]},{"type":"Polygon","id":37109,"arcs":[[-6405,6674,6675,-6671,-6444,-6527]]},{"type":"Polygon","id":47153,"arcs":[[6676,6677,6678,-6508,-6535,-6575]]},{"type":"Polygon","id":40091,"arcs":[[6679,6680,6681,-6641,-6502,-6506]]},{"type":"Polygon","id":40015,"arcs":[[-6601,6682,6683,6684,6685,-6546,-6330]]},{"type":"Polygon","id":5145,"arcs":[[-6485,6686,6687,6688,6689,-6604,-6472]]},{"type":"Polygon","id":47061,"arcs":[[6690,6691,-6614,-6509,-6679]]},{"type":"Polygon","id":4025,"arcs":[[6692,6693,6694,6695,-5752]]},{"type":"Polygon","id":37099,"arcs":[[-6568,6696,6697,6698,6699,-6620]]},{"type":"Polygon","id":37119,"arcs":[[6700,6701,6702,6703,6704,-6675,-6404]]},{"type":"Polygon","id":37125,"arcs":[[-6646,-6670,6705,6706,6707,6708,-6475,-6491]]},{"type":"Polygon","id":37123,"arcs":[[-6709,6709,6710,-6425,-6476]]},{"type":"Polygon","id":37025,"arcs":[[6711,6712,-6701,-6403,-6500]]},{"type":"Polygon","id":40009,"arcs":[[-6548,6713,6714,6715,6716,6717,-6654,-6428]]},{"type":"Polygon","id":37167,"arcs":[[-6711,6718,6719,-6712,-6499]]},{"type":"Polygon","id":37089,"arcs":[[-6661,6720,6721,6722,-6529]]},{"type":"Polygon","id":47181,"arcs":[[6723,6724,6725,-6516,-6520,-6629]]},{"type":"Polygon","id":40133,"arcs":[[6726,6727,6728,-6643]]},{"type":"Polygon","id":37075,"arcs":[[6729,6730,-6623,-6487,-6622]]},{"type":"Polygon","id":40149,"arcs":[[-6686,6731,-6714,-6547]]},{"type":"Polygon","id":40125,"arcs":[[-6644,-6729,6732,6733,6734,-6602,-6469]]},{"type":"Polygon","id":5029,"arcs":[[6735,6736,6737,-6594,-6565]]},{"type":"Polygon","id":47099,"arcs":[[-6513,6738,6739,-6724,-6628]]},{"type":"Polygon","id":40061,"arcs":[[6740,6741,6742,-6680,-6505,-6640]]},{"type":"Polygon","id":47065,"arcs":[[-6582,6743,6744,6745,6746,6747,-6677,-6574,-6532]]},{"type":"Polygon","id":47055,"arcs":[[-6608,6748,6749,6750,-6739,-6512]]},{"type":"Polygon","id":5131,"arcs":[[-6571,6751,6752,6753,-6638,-6578]]},{"type":"Polygon","id":5037,"arcs":[[6754,6755,6756,-6483,-6610]]},{"type":"Polygon","id":5147,"arcs":[[-6757,6757,6758,6759,-6687,-6484]]},{"type":"Polygon","id":5035,"arcs":[[-6440,-6633,6760,6761,6762,6763,6764,-6755,-6609]]},{"type":"Polygon","id":47069,"arcs":[[-6562,-6667,6765,6766,6767,6768,6769,-6540]]},{"type":"Polygon","id":37107,"arcs":[[6770,6771,6772,-6662,-6630,-6525]]},{"type":"Polygon","id":5083,"arcs":[[-6577,-6596,6773,6774,-6752,-6570]]},{"type":"Polygon","id":37175,"arcs":[[6775,6776,6777,-6697,-6567,-6723]]},{"type":"Polygon","id":47071,"arcs":[[-6726,6778,6779,6780,6781,-6665,-6536,-6517]]},{"type":"Polygon","id":37071,"arcs":[[-6705,6782,-6672,-6676]]},{"type":"MultiPolygon","id":37049,"arcs":[[[6783,6784,6785]],[[-6591,6786,6787,6788,-6771,-6524]]]},{"type":"Polygon","id":47127,"arcs":[[6789,6790,-6618,-6616]]},{"type":"Polygon","id":47157,"arcs":[[6791,6792,6793,-6761,-6632,-6438,-6635]]},{"type":"Polygon","id":37149,"arcs":[[6794,6795,-6721,-6660]]},{"type":"Polygon","id":47047,"arcs":[[-6770,6796,6797,-6792,-6634,-6541]]},{"type":"Polygon","id":47109,"arcs":[[-6782,6798,-6766,-6666]]},{"type":"Polygon","id":40079,"arcs":[[6799,6800,6801,6802,6803,-6741,-6639,-6754]]},{"type":"Polygon","id":40051,"arcs":[[6804,6805,6806,6807,-6683,-6600]]},{"type":"Polygon","id":40027,"arcs":[[-6735,6808,-6598,-6603]]},{"type":"Polygon","id":47103,"arcs":[[-6791,6809,6810,6811,-6749,-6607,-6619]]},{"type":"Polygon","id":5045,"arcs":[[-6605,-6690,6812,6813,6814,-6736,-6564]]},{"type":"Polygon","id":47051,"arcs":[[-6692,6815,6816,6817,-6810,-6790,-6615]]},{"type":"Polygon","id":47011,"arcs":[[6818,6819,6820,-6744,-6581,-6637]]},{"type":"Polygon","id":35006,"arcs":[[-6297,6821,6822,6823,6824,-5796,-6430]]},{"type":"Polygon","id":40087,"arcs":[[-6734,6825,6826,-6805,-6599,-6809]]},{"type":"Polygon","id":37113,"arcs":[[6827,6828,6829,-6730,-6621,-6700]]},{"type":"MultiPolygon","id":37137,"arcs":[[[-6787,-6590,6830]]]},{"type":"Polygon","id":47115,"arcs":[[-6748,6831,6832,-6816,-6691,-6678]]},{"type":"Polygon","id":37163,"arcs":[[-6664,6833,6834,6835,6836,-6668,-6544]]},{"type":"Polygon","id":5149,"arcs":[[-6738,6837,6838,6839,6840,-6774,-6595]]},{"type":"Polygon","id":40121,"arcs":[[-6743,6841,6842,6843,6844,6845,-6681]]},{"type":"Polygon","id":37039,"arcs":[[-6830,6846,6847,6848,6849,-6624,-6731]]},{"type":"Polygon","id":40063,"arcs":[[-6682,-6846,6850,6851,-6727,-6642]]},{"type":"Polygon","id":47139,"arcs":[[-6850,6852,6853,-6819,-6636,-6625]]},{"type":"Polygon","id":37051,"arcs":[[-6837,6854,6855,6856,-6706,-6669]]},{"type":"Polygon","id":37103,"arcs":[[6857,-6785,6858,6859,6860,-6772,-6789]]},{"type":"Polygon","id":35001,"arcs":[[-6433,6861,6862,-6822,-6296]]},{"type":"Polygon","id":35019,"arcs":[[-6588,6863,6864,6865,-6493]]},{"type":"Polygon","id":45045,"arcs":[[-6796,6866,6867,6868,6869,-6776,-6722]]},{"type":"Polygon","id":37007,"arcs":[[6870,6871,6872,6873,-6719]]},{"type":"Polygon","id":37093,"arcs":[[-6857,6874,6875,-6707]]},{"type":"Polygon","id":37179,"arcs":[[-6874,6876,6877,-6702,-6713,-6720]]},{"type":"Polygon","id":45083,"arcs":[[6878,6879,-6867,-6795,-6659,6880]]},{"type":"Polygon","id":37061,"arcs":[[-6773,-6861,6881,6882,-6834,-6663]]},{"type":"Polygon","id":48117,"arcs":[[6883,6884,6885,6886,-6584,-6648]]},{"type":"Polygon","id":45021,"arcs":[[6887,-6881,-6658,-6674,6888]]},{"type":"Polygon","id":48129,"arcs":[[6889,6890,6891,6892,-6653]]},{"type":"Polygon","id":48087,"arcs":[[-6718,6893,6894,6895,-6890,-6655]]},{"type":"Polygon","id":48381,"arcs":[[6896,6897,6898,-6884,-6656]]},{"type":"Polygon","id":37153,"arcs":[[-6708,6899,6900,-6871,-6710]]},{"type":"Polygon","id":48011,"arcs":[[-6893,6901,6902,-6897,-6650]]},{"type":"Polygon","id":45091,"arcs":[[6903,6904,6905,-6889,-6673,-6783,-6704]]},{"type":"Polygon","id":37043,"arcs":[[-6829,6906,6907,6908,-6847]]},{"type":"Polygon","id":5123,"arcs":[[-6765,6909,6910,-6758,-6756]]},{"type":"Polygon","id":40075,"arcs":[[-6685,6911,6912,6913,6914,-6715,-6732]]},{"type":"MultiPolygon","id":37031,"arcs":[[[6915,-6859,-6784,6916]]]},{"type":"Polygon","id":40055,"arcs":[[-6915,6917,6918,-6716]]},{"type":"MultiPolygon","id":6083,"arcs":[[[6919]],[[6920]],[[-6560,6921,6922,-6556]]]},{"type":"Polygon","id":5105,"arcs":[[6923,6924,6925,-6838,-6737,-6815]]},{"type":"Polygon","id":5127,"arcs":[[-6841,6926,6927,-6800,-6753,-6775]]},{"type":"Polygon","id":5117,"arcs":[[-6760,6928,6929,6930,-6688]]},{"type":"Polygon","id":45077,"arcs":[[-6870,6931,6932,-6777]]},{"type":"Polygon","id":5085,"arcs":[[-6931,6933,6934,6935,-6813,-6689]]},{"type":"Polygon","id":45057,"arcs":[[-6878,6936,6937,6938,6939,-6904,-6703]]},{"type":"Polygon","id":40077,"arcs":[[-6804,6940,-6842,-6742]]},{"type":"Polygon","id":45073,"arcs":[[6941,6942,6943,6944,6945,6946,-6698,-6778,-6933]]},{"type":"Polygon","id":35057,"arcs":[[-6866,6947,6948,6949,-6862,-6432,-6494]]},{"type":"Polygon","id":37165,"arcs":[[6950,6951,-6900,-6876]]},{"type":"Polygon","id":40057,"arcs":[[-6919,6952,6953,6954,-6894,-6717]]},{"type":"Polygon","id":5119,"arcs":[[-6936,6955,6956,6957,-6924,-6814]]},{"type":"Polygon","id":1077,"arcs":[[6958,6959,6960,-6779,-6725,-6740,-6751,6961]]},{"type":"Polygon","id":5095,"arcs":[[-6911,6962,6963,6964,-6929,-6759]]},{"type":"Polygon","id":13241,"arcs":[[6965,6966,-6907,-6828,-6699,-6947]]},{"type":"Polygon","id":1083,"arcs":[[-6812,6967,6968,6969,-6962,-6750]]},{"type":"Polygon","id":28003,"arcs":[[6970,6971,-6767,-6799,-6781,6972]]},{"type":"Polygon","id":28141,"arcs":[[6973,6974,6975,-6973,-6780,-6961,6976]]},{"type":"Polygon","id":28139,"arcs":[[6977,6978,6979,-6768,-6972]]},{"type":"Polygon","id":28033,"arcs":[[6980,6981,-6762,-6794,6982]]},{"type":"Polygon","id":28009,"arcs":[[6983,6984,-6797,-6769,-6980]]},{"type":"Polygon","id":28093,"arcs":[[6985,6986,6987,-6983,-6793,-6798,-6985]]},{"type":"Polygon","id":13281,"arcs":[[-6967,6988,6989,6990,-6908]]},{"type":"Polygon","id":1089,"arcs":[[-6818,6991,6992,6993,-6968,-6811]]},{"type":"Polygon","id":1071,"arcs":[[6994,6995,-6992,-6817,-6833,6996]]},{"type":"Polygon","id":13213,"arcs":[[6997,6998,6999,-6820,-6854,7000]]},{"type":"Polygon","id":13111,"arcs":[[-6849,7001,7002,7003,7004,-7001,-6853]]},{"type":"Polygon","id":13313,"arcs":[[7005,7006,7007,-6821,-7000]]},{"type":"Polygon","id":13047,"arcs":[[7008,-6745,-7008]]},{"type":"Polygon","id":13291,"arcs":[[-6909,-6991,7009,7010,-7002,-6848]]},{"type":"MultiPolygon","id":37133,"arcs":[[[-6916,7011,7012,-6882,-6860]]]},{"type":"Polygon","id":13083,"arcs":[[-6832,-6747,7013,7014,-6997]]},{"type":"Polygon","id":13295,"arcs":[[-7007,7015,7016,7017,7018,-7014,-6746,-7009]]},{"type":"Polygon","id":40123,"arcs":[[-6728,-6852,7019,7020,7021,7022,-6826,-6733]]},{"type":"Polygon","id":35061,"arcs":[[-6863,-6950,7023,-6823]]},{"type":"Polygon","id":37155,"arcs":[[7024,7025,7026,7027,7028,-6951,-6875,-6856]]},{"type":"Polygon","id":35009,"arcs":[[-6887,7029,7030,7031,-6585]]},{"type":"Polygon","id":45087,"arcs":[[-6906,7032,7033,7034,7035,-6879,-6888]]},{"type":"Polygon","id":5077,"arcs":[[-6764,7036,7037,-6963,-6910]]},{"type":"Polygon","id":1033,"arcs":[[7038,7039,-6977,-6960]]},{"type":"MultiPolygon","id":6111,"arcs":[[[7040]],[[7041,-6922,-6559,7042]]]},{"type":"Polygon","id":28143,"arcs":[[-6763,-6982,7043,7044,7045,7046,7047,-7037]]},{"type":"Polygon","id":1049,"arcs":[[-7019,7048,7049,7050,7051,-6995,-7015]]},{"type":"Polygon","id":37017,"arcs":[[7052,7053,-7025,-6855,-6836]]},{"type":"Polygon","id":40065,"arcs":[[7054,7055,7056,-6953,-6918,-6914]]},{"type":"Polygon","id":5125,"arcs":[[-6958,7057,7058,7059,-6925]]},{"type":"Polygon","id":40031,"arcs":[[-6684,-6808,7060,7061,7062,-6912]]},{"type":"Polygon","id":40049,"arcs":[[-7023,7063,7064,7065,-6806,-6827]]},{"type":"Polygon","id":13123,"arcs":[[7066,7067,7068,-6998,-7005]]},{"type":"Polygon","id":13137,"arcs":[[-6946,7069,7070,7071,7072,-6989,-6966]]},{"type":"MultiPolygon","id":6037,"arcs":[[[7073]],[[7074]],[[-6553,7075,7076,-7043,-6558]]]},{"type":"Polygon","id":45023,"arcs":[[-6940,7077,-7033,-6905]]},{"type":"Polygon","id":45007,"arcs":[[7078,7079,7080,-6942,-6932,-6869]]},{"type":"Polygon","id":45025,"arcs":[[7081,7082,-6937,-6877,-6873,7083]]},{"type":"Polygon","id":45069,"arcs":[[-7029,7084,7085,7086,-7084,-6872,-6901,-6952]]},{"type":"Polygon","id":1079,"arcs":[[-6970,7087,7088,7089,7090,-7039,-6959]]},{"type":"Polygon","id":13311,"arcs":[[7091,7092,-7010,-6990,-7073]]},{"type":"Polygon","id":45059,"arcs":[[-7036,7093,7094,7095,-6868,-6880]]},{"type":"Polygon","id":35011,"arcs":[[7096,7097,7098,-6864,-6587]]},{"type":"Polygon","id":28137,"arcs":[[-6988,7099,7100,-7044,-6981]]},{"type":"Polygon","id":5051,"arcs":[[-7060,7101,7102,-6839,-6926]]},{"type":"Polygon","id":40029,"arcs":[[-6845,7103,7104,-7020,-6851]]},{"type":"Polygon","id":28117,"arcs":[[-6976,7105,7106,7107,-6978,-6971]]},{"type":"Polygon","id":48437,"arcs":[[-6903,7108,7109,7110,7111,-6898]]},{"type":"Polygon","id":48045,"arcs":[[-6892,7112,7113,7114,-7109,-6902]]},{"type":"Polygon","id":48069,"arcs":[[-7112,7115,7116,7117,-6885,-6899]]},{"type":"Polygon","id":48191,"arcs":[[-6896,7118,7119,7120,-7113,-6891]]},{"type":"Polygon","id":48075,"arcs":[[-6955,7121,7122,-7119,-6895]]},{"type":"Polygon","id":5097,"arcs":[[-7103,7123,7124,7125,7126,-6927,-6840]]},{"type":"Polygon","id":48369,"arcs":[[-7118,7127,7128,-7030,-6886]]},{"type":"Polygon","id":13187,"arcs":[[-7093,7129,7130,-7003,-7011]]},{"type":"MultiPolygon","id":37141,"arcs":[[[7131,7132]],[[-7013,7133,7134,7135,7136,-7053,-6835,-6883]]]},{"type":"Polygon","id":5113,"arcs":[[7137,7138,7139,-6801,-6928,-7127]]},{"type":"Polygon","id":1103,"arcs":[[-6994,7140,7141,-7088,-6969]]},{"type":"Polygon","id":13257,"arcs":[[7142,7143,-7070,-6945]]},{"type":"Polygon","id":40137,"arcs":[[-7066,7144,7145,7146,-7061,-6807]]},{"type":"Polygon","id":40005,"arcs":[[7147,7148,7149,7150,-7104,-6844]]},{"type":"Polygon","id":40127,"arcs":[[-6803,7151,7152,-7148,-6843,-6941]]},{"type":"Polygon","id":5107,"arcs":[[-7048,7153,7154,7155,7156,-6964,-7038]]},{"type":"Polygon","id":40099,"arcs":[[-7022,7157,7158,-7064]]},{"type":"Polygon","id":40141,"arcs":[[-7063,7159,7160,7161,-7055,-6913]]},{"type":"Polygon","id":13129,"arcs":[[-7069,7162,7163,7164,-7016,-7006,-6999]]},{"type":"Polygon","id":45033,"arcs":[[7165,7166,7167,-7085,-7028]]},{"type":"Polygon","id":13085,"arcs":[[-7131,7168,7169,7170,7171,-7067,-7004]]},{"type":"Polygon","id":45055,"arcs":[[7172,7173,7174,7175,7176,-6938,-7083]]},{"type":"Polygon","id":35041,"arcs":[[-7032,7177,7178,7179,7180,-7097,-6586]]},{"type":"Polygon","id":28145,"arcs":[[-7108,7181,7182,7183,-6986,-6984,-6979]]},{"type":"Polygon","id":1095,"arcs":[[-7052,7184,7185,7186,-7141,-6993,-6996]]},{"type":"Polygon","id":13055,"arcs":[[7187,7188,-7049,-7018]]},{"type":"Polygon","id":13115,"arcs":[[-7165,7189,7190,7191,-7188,-7017]]},{"type":"Polygon","id":35053,"arcs":[[-6949,7192,7193,7194,-6824,-7024]]},{"type":"Polygon","id":35003,"arcs":[[-7195,7195,7196,7197,-5797,-6825]]},{"type":"Polygon","id":1059,"arcs":[[-7091,7198,7199,7200,-6974,-7040]]},{"type":"Polygon","id":48197,"arcs":[[-7057,7201,7202,7203,-7122,-6954]]},{"type":"Polygon","id":45039,"arcs":[[-6939,-7177,7204,7205,-7034,-7078]]},{"type":"Polygon","id":5001,"arcs":[[-6965,-7157,7206,7207,7208,-6934,-6930]]},{"type":"Polygon","id":13227,"arcs":[[-7172,7209,-7163,-7068]]},{"type":"Polygon","id":28071,"arcs":[[-7184,7210,7211,7212,7213,-7100,-6987]]},{"type":"Polygon","id":28107,"arcs":[[-7214,7214,7215,7216,-7045,-7101]]},{"type":"Polygon","id":13119,"arcs":[[7217,7218,7219,-7143,-6944]]},{"type":"Polygon","id":45031,"arcs":[[-7087,7220,7221,-7173,-7082]]},{"type":"Polygon","id":45071,"arcs":[[-7206,7222,7223,7224,7225,-7094,-7035]]},{"type":"Polygon","id":1019,"arcs":[[-7192,7226,7227,7228,7229,-7050,-7189]]},{"type":"Polygon","id":28027,"arcs":[[7230,7231,7232,-7154,-7047,7233]]},{"type":"Polygon","id":28119,"arcs":[[-7217,7234,-7234,-7046]]},{"type":"Polygon","id":13139,"arcs":[[-7072,7235,7236,7237,7238,-7169,-7130,-7092]]},{"type":"Polygon","id":28081,"arcs":[[7239,7240,7241,7242,-7182,-7107]]},{"type":"Polygon","id":40019,"arcs":[[-7159,7243,7244,7245,7246,-7145,-7065]]},{"type":"Polygon","id":40069,"arcs":[[-7021,-7105,-7151,7247,7248,-7244,-7158]]},{"type":"Polygon","id":40033,"arcs":[[-7147,7249,7250,7251,-7160,-7062]]},{"type":"Polygon","id":40089,"arcs":[[-7140,7252,7253,7254,7255,7256,-7152,-6802]]},{"type":"Polygon","id":5059,"arcs":[[-7059,7257,7258,7259,-7124,-7102]]},{"type":"Polygon","id":4007,"arcs":[[-5749,7260,7261,7262,-6693,-5751]]},{"type":"Polygon","id":13147,"arcs":[[-7081,7263,7264,-7218,-6943]]},{"type":"Polygon","id":5053,"arcs":[[-6957,7265,7266,7267,-7258,-7058]]},{"type":"Polygon","id":5069,"arcs":[[-6935,-7209,7268,7269,-7266,-6956]]},{"type":"Polygon","id":13011,"arcs":[[7270,7271,-7236,-7071,-7144,-7220]]},{"type":"Polygon","id":37047,"arcs":[[-7137,7272,7273,-7026,-7054]]},{"type":"Polygon","id":45001,"arcs":[[7274,7275,7276,-7079,-7096]]},{"type":"Polygon","id":28057,"arcs":[[-6975,-7201,7277,7278,-7240,-7106]]},{"type":"Polygon","id":48487,"arcs":[[-7162,7279,7280,7281,-7202,-7056]]},{"type":"Polygon","id":13015,"arcs":[[7282,7283,7284,-7190,-7164,7285]]},{"type":"Polygon","id":13057,"arcs":[[-7171,7286,7287,7288,-7286,-7210]]},{"type":"Polygon","id":45047,"arcs":[[-7226,7289,7290,7291,-7275,-7095]]},{"type":"MultiPolygon","id":37129,"arcs":[[[7292,7293]],[[7294,-7132,7295,7296,-7135]]]},{"type":"Polygon","id":28115,"arcs":[[-7243,7297,7298,-7211,-7183]]},{"type":"Polygon","id":45061,"arcs":[[7299,7300,-7174,-7222]]},{"type":"MultiPolygon","id":37019,"arcs":[[[7301,7302]],[[-7293,7303]],[[-7297,7304,7305,7306,7307,-7273,-7136]]]},{"type":"Polygon","id":5061,"arcs":[[7308,7309,7310,7311,-7138]]},{"type":"Polygon","id":5109,"arcs":[[7312,7313,7314,-7309,-7126]]},{"type":"Polygon","id":35027,"arcs":[[-7099,7315,7316,7317,-7193,-6948,-6865]]},{"type":"Polygon","id":5019,"arcs":[[-7260,7318,7319,7320,-7313,-7125]]},{"type":"Polygon","id":13117,"arcs":[[-7239,7321,7322,-7287,-7170]]},{"type":"Polygon","id":1093,"arcs":[[7323,7324,7325,7326,7327,-7278,-7200]]},{"type":"Polygon","id":4012,"arcs":[[-6695,7328,7329,7330,7331,-6550,7332]]},{"type":"Polygon","id":48345,"arcs":[[7333,7334,7335,-7114,-7121]]},{"type":"Polygon","id":48101,"arcs":[[-7123,-7204,7336,7337,-7334,-7120]]},{"type":"Polygon","id":1043,"arcs":[[-7187,7338,7339,7340,-7089,-7142]]},{"type":"Polygon","id":48153,"arcs":[[-7336,7341,7342,-7110,-7115]]},{"type":"Polygon","id":48189,"arcs":[[-7116,-7111,-7343,7343,7344]]},{"type":"Polygon","id":48279,"arcs":[[-7345,7345,7346,-7128,-7117]]},{"type":"Polygon","id":45041,"arcs":[[7347,7348,7349,7350,-7300,-7221,-7086,-7168]]},{"type":"Polygon","id":48017,"arcs":[[-7347,7351,-7178,-7031,-7129]]},{"type":"Polygon","id":1133,"arcs":[[-7090,-7341,7352,-7324,-7199]]},{"type":"MultiPolygon","id":45051,"arcs":[[[-7302,7353]],[[-7306,7354]],[[-7274,-7308,7355,7356,7357,-7166,-7027]]]},{"type":"Polygon","id":45067,"arcs":[[-7358,7358,7359,-7348,-7167]]},{"type":"Polygon","id":13157,"arcs":[[7360,7361,7362,-7237,-7272]]},{"type":"Polygon","id":40067,"arcs":[[-7247,7363,7364,7365,-7250,-7146]]},{"type":"Polygon","id":13105,"arcs":[[-7277,7366,7367,7368,7369,7370,-7264,-7080]]},{"type":"Polygon","id":13195,"arcs":[[-7371,7371,7372,-7361,-7271,-7219,-7265]]},{"type":"Polygon","id":45079,"arcs":[[7373,7374,7375,-7223,-7205,-7176]]},{"type":"Polygon","id":1009,"arcs":[[-7186,7376,7377,7378,7379,-7339]]},{"type":"Polygon","id":48155,"arcs":[[-7203,-7282,7380,7381,7382,-7337]]},{"type":"Polygon","id":48485,"arcs":[[-7252,7383,7384,-7280,-7161]]},{"type":"Polygon","id":1055,"arcs":[[-7230,7385,7386,-7377,-7185,-7051]]},{"type":"Polygon","id":45063,"arcs":[[7387,7388,7389,7390,-7224,-7376]]},{"type":"Polygon","id":28161,"arcs":[[7391,7392,7393,-7215,-7213]]},{"type":"Polygon","id":5133,"arcs":[[7394,-7253,-7139,-7312]]},{"type":"Polygon","id":45081,"arcs":[[-7391,7395,7396,-7290,-7225]]},{"type":"Polygon","id":13121,"arcs":[[-7323,7397,7398,7399,7400,7401,7402,7403,7404,-7288]]},{"type":"Polygon","id":40095,"arcs":[[7405,7406,7407,-7245,-7249]]},{"type":"Polygon","id":5079,"arcs":[[-7208,7408,7409,7410,-7269]]},{"type":"Polygon","id":45085,"arcs":[[-7351,7411,7412,-7374,-7175,-7301]]},{"type":"Polygon","id":13135,"arcs":[[7413,7414,7415,7416,-7398,-7322,-7238]]},{"type":"Polygon","id":28135,"arcs":[[-7394,7417,7418,7419,-7231,-7235,-7216]]},{"type":"Polygon","id":28013,"arcs":[[-7299,7420,7421,7422,-7392,-7212]]},{"type":"Polygon","id":40013,"arcs":[[7423,7424,7425,7426,-7406,-7248,-7150]]},{"type":"Polygon","id":40023,"arcs":[[-7257,7427,7428,-7424,-7149,-7153]]},{"type":"Polygon","id":5039,"arcs":[[-7268,7429,7430,7431,-7319,-7259]]},{"type":"Polygon","id":48077,"arcs":[[-7366,7432,7433,7434,-7384,-7251]]},{"type":"Polygon","id":13013,"arcs":[[7435,7436,-7414,-7363]]},{"type":"Polygon","id":28011,"arcs":[[7437,7438,7439,7440,-7155,-7233]]},{"type":"Polygon","id":5041,"arcs":[[-7441,7441,7442,-7409,-7207,-7156]]},{"type":"Polygon","id":13233,"arcs":[[-7285,7443,7444,7445,-7227,-7191]]},{"type":"Polygon","id":28095,"arcs":[[-7279,-7328,7446,7447,7448,7449,-7241]]},{"type":"Polygon","id":35005,"arcs":[[-7181,7450,7451,7452,-7316,-7098]]},{"type":"Polygon","id":13067,"arcs":[[-7405,7453,7454,-7283,-7289]]},{"type":"Polygon","id":6065,"arcs":[[7455,7456,7457,-6551,-7332]]},{"type":"Polygon","id":13223,"arcs":[[-7455,7458,7459,7460,-7444,-7284]]},{"type":"Polygon","id":45065,"arcs":[[-7292,7461,7462,7463,-7367,-7276]]},{"type":"Polygon","id":28017,"arcs":[[-7242,-7450,7464,7465,-7421,-7298]]},{"type":"Polygon","id":40085,"arcs":[[-7408,7466,7467,7468,-7364,-7246]]},{"type":"Polygon","id":5025,"arcs":[[-7411,7469,7470,7471,-7430,-7267,-7270]]},{"type":"Polygon","id":1075,"arcs":[[7472,7473,7474,-7447,-7327]]},{"type":"Polygon","id":13221,"arcs":[[-7370,7475,7476,7477,7478,7479,-7372]]},{"type":"Polygon","id":4013,"arcs":[[-7263,7480,7481,7482,-7329,-6694]]},{"type":"Polygon","id":13059,"arcs":[[-7480,7483,-7362,-7373]]},{"type":"Polygon","id":5057,"arcs":[[7484,7485,7486,7487,-7310,-7315]]},{"type":"Polygon","id":1127,"arcs":[[-7340,-7380,7488,7489,7490,-7325,-7353]]},{"type":"Polygon","id":13317,"arcs":[[7491,7492,7493,7494,-7476,-7369]]},{"type":"Polygon","id":48337,"arcs":[[-7469,7495,7496,7497,-7433,-7365]]},{"type":"Polygon","id":1115,"arcs":[[7498,7499,7500,7501,-7378,-7387]]},{"type":"Polygon","id":28133,"arcs":[[-7420,7502,7503,7504,-7438,-7232]]},{"type":"Polygon","id":13181,"arcs":[[-7464,7505,7506,-7492,-7368]]},{"type":"Polygon","id":45037,"arcs":[[7507,7508,7509,-7462,-7291,-7397]]},{"type":"Polygon","id":13089,"arcs":[[-7417,7510,7511,7512,-7399]]},{"type":"Polygon","id":1015,"arcs":[[7513,-7499,-7386,-7229,7514]]},{"type":"Polygon","id":13219,"arcs":[[-7479,7515,7516,7517,-7436,-7484]]},{"type":"Polygon","id":1029,"arcs":[[-7446,7518,7519,7520,7521,7522,-7515,-7228]]},{"type":"Polygon","id":48387,"arcs":[[-7256,7523,7524,7525,7526,7527,7528,-7428]]},{"type":"Polygon","id":5099,"arcs":[[-7321,7529,7530,7531,-7485,-7314]]},{"type":"Polygon","id":48181,"arcs":[[-7427,7532,7533,7534,7535,-7467,-7407]]},{"type":"Polygon","id":48097,"arcs":[[-7536,7536,7537,-7496,-7468]]},{"type":"Polygon","id":5081,"arcs":[[-7311,-7488,7538,7539,-7254,-7395]]},{"type":"Polygon","id":45027,"arcs":[[7540,7541,7542,7543,-7412,-7350]]},{"type":"MultiPolygon","id":6059,"arcs":[[[-6552,-7458,7544,7545,-7076]]]},{"type":"Polygon","id":48277,"arcs":[[-7529,7546,7547,-7425,-7429]]},{"type":"Polygon","id":13297,"arcs":[[-7518,7548,7549,7550,-7415,-7437]]},{"type":"Polygon","id":1057,"arcs":[[-7491,7551,7552,-7473,-7326]]},{"type":"Polygon","id":13143,"arcs":[[7553,-7519,-7445,-7461]]},{"type":"Polygon","id":28043,"arcs":[[-7423,7554,7555,7556,7557,-7418,-7393]]},{"type":"Polygon","id":45089,"arcs":[[-7360,7558,7559,-7541,-7349]]},{"type":"Polygon","id":48147,"arcs":[[-7548,7560,7561,7562,-7533,-7426]]},{"type":"Polygon","id":45017,"arcs":[[-7413,-7544,7563,-7388,-7375]]},{"type":"Polygon","id":45003,"arcs":[[7564,7565,7566,7567,-7508,-7396,-7390]]},{"type":"Polygon","id":1073,"arcs":[[-7502,7568,7569,7570,-7489,-7379]]},{"type":"Polygon","id":48269,"arcs":[[-7383,7571,7572,7573,-7338]]},{"type":"Polygon","id":48275,"arcs":[[7574,7575,-7572,-7382]]},{"type":"Polygon","id":48009,"arcs":[[-7435,7576,7577,7578,-7385]]},{"type":"Polygon","id":48125,"arcs":[[-7574,7579,7580,-7335]]},{"type":"Polygon","id":48107,"arcs":[[-7581,7581,7582,-7342]]},{"type":"Polygon","id":48023,"arcs":[[-7579,7583,-7575,-7381,-7281]]},{"type":"Polygon","id":48303,"arcs":[[-7583,7584,7585,-7344]]},{"type":"Polygon","id":48079,"arcs":[[7586,7587,7588,-7179,-7352]]},{"type":"Polygon","id":48219,"arcs":[[-7586,7589,-7587,-7346]]},{"type":"Polygon","id":5103,"arcs":[[-7432,7590,7591,7592,-7530,-7320]]},{"type":"Polygon","id":13211,"arcs":[[7593,7594,7595,7596,-7549,-7517]]},{"type":"Polygon","id":13045,"arcs":[[-7403,7597,7598,7599,-7520,-7554,-7460,7600]]},{"type":"Polygon","id":28083,"arcs":[[-7558,7601,7602,7603,-7503,-7419]]},{"type":"Polygon","id":28025,"arcs":[[-7449,7604,7605,7606,-7465]]},{"type":"Polygon","id":13097,"arcs":[[-7404,-7601,-7459,-7454]]},{"type":"Polygon","id":5013,"arcs":[[7607,7608,-7591,-7431,-7472]]},{"type":"Polygon","id":5043,"arcs":[[-7443,7609,7610,7611,-7470,-7410]]},{"type":"Polygon","id":13247,"arcs":[[7612,-7511,-7416,-7551,7613]]},{"type":"MultiPolygon","id":45043,"arcs":[[[7614,7615]],[[-7357,7616,7617,7618,-7559,-7359]]]},{"type":"Polygon","id":4011,"arcs":[[-7198,7619,7620,7621,7622,-5798]]},{"type":"Polygon","id":13133,"arcs":[[7623,7624,7625,-7594,-7516,-7478]]},{"type":"Polygon","id":28087,"arcs":[[-7475,7626,7627,7628,-7605,-7448]]},{"type":"Polygon","id":13217,"arcs":[[-7597,7629,7630,7631,-7614,-7550]]},{"type":"Polygon","id":28155,"arcs":[[-7607,7632,7633,7634,-7555,-7422,-7466]]},{"type":"Polygon","id":13265,"arcs":[[7635,7636,-7624,-7477,-7495]]},{"type":"Polygon","id":48037,"arcs":[[-7540,7637,7638,7639,-7524,-7255]]},{"type":"Polygon","id":45075,"arcs":[[-7543,7640,7641,7642,7643,7644,-7565,-7389,-7564]]},{"type":"Polygon","id":5011,"arcs":[[-7612,7645,7646,-7608,-7471]]},{"type":"Polygon","id":13073,"arcs":[[-7463,-7510,7647,7648,-7506]]},{"type":"Polygon","id":1121,"arcs":[[-7514,-7523,7649,7650,7651,-7500]]},{"type":"Polygon","id":28097,"arcs":[[-7635,7652,7653,7654,-7556]]},{"type":"Polygon","id":28015,"arcs":[[-7655,7655,7656,-7602,-7557]]},{"type":"Polygon","id":13189,"arcs":[[-7507,-7649,7657,7658,7659,-7493]]},{"type":"Polygon","id":4009,"arcs":[[-7623,7660,7661,7662,-7261,-5748,-5799]]},{"type":"Polygon","id":13063,"arcs":[[-7513,7663,7664,7665,-7400]]},{"type":"Polygon","id":13151,"arcs":[[-7613,-7632,7666,7667,-7664,-7512]]},{"type":"Polygon","id":5091,"arcs":[[7668,7669,7670,7671,-7638,-7539,-7487]]},{"type":"Polygon","id":13301,"arcs":[[7672,7673,7674,-7636,-7494,-7660]]},{"type":"Polygon","id":1125,"arcs":[[-7490,-7571,7675,7676,7677,7678,-7552]]},{"type":"Polygon","id":35025,"arcs":[[-7589,7679,7680,7681,7682,7683,7684,-7451,-7180]]},{"type":"Polygon","id":28105,"arcs":[[-7629,7685,7686,7687,-7633,-7606]]},{"type":"Polygon","id":5017,"arcs":[[-7440,7688,7689,7690,7691,7692,7693,-7610,-7442]]},{"type":"Polygon","id":13113,"arcs":[[7694,7695,-7401,-7666]]},{"type":"Polygon","id":1117,"arcs":[[-7652,7696,7697,7698,-7569,-7501]]},{"type":"Polygon","id":13245,"arcs":[[7699,7700,-7658,-7648,-7509,-7568]]},{"type":"Polygon","id":1107,"arcs":[[-7553,-7679,7701,7702,7703,-7627,-7474]]},{"type":"Polygon","id":28019,"arcs":[[-7688,7704,7705,-7653,-7634]]},{"type":"Polygon","id":28151,"arcs":[[-7505,7706,7707,7708,-7689,-7439]]},{"type":"Polygon","id":13159,"arcs":[[7709,7710,7711,7712,-7630,-7596]]},{"type":"Polygon","id":13077,"arcs":[[-7696,7713,7714,7715,7716,-7598,-7402]]},{"type":"Polygon","id":45015,"arcs":[[-7619,7717,7718,7719,7720,-7641,-7542,-7560]]},{"type":"MultiPolygon","id":6073,"arcs":[[[7721,7722,-7545,-7457]]]},{"type":"Polygon","id":1111,"arcs":[[-7600,7723,7724,7725,7726,7727,-7521]]},{"type":"Polygon","id":1027,"arcs":[[-7728,7728,7729,-7650,-7522]]},{"type":"Polygon","id":48119,"arcs":[[-7528,7730,7731,7732,-7561,-7547]]},{"type":"Polygon","id":45011,"arcs":[[7733,7734,7735,-7566,-7645]]},{"type":"Polygon","id":13237,"arcs":[[7736,7737,7738,-7710,-7595,-7626]]},{"type":"Polygon","id":35051,"arcs":[[-7318,7739,7740,7741,7742,-7196,-7194]]},{"type":"Polygon","id":5073,"arcs":[[-7532,7743,7744,7745,-7669,-7486]]},{"type":"Polygon","id":13141,"arcs":[[-7675,7746,7747,7748,-7737,-7625,-7637]]},{"type":"Polygon","id":4027,"arcs":[[-7483,7749,7750,7751,-7330]]},{"type":"Polygon","id":4021,"arcs":[[-7262,-7663,7752,-7481]]},{"type":"Polygon","id":48237,"arcs":[[-7498,7753,7754,7755,7756,-7577,-7434]]},{"type":"Polygon","id":5027,"arcs":[[-7593,7757,7758,7759,-7744,-7531]]},{"type":"Polygon","id":13035,"arcs":[[-7713,7760,7761,7762,-7667,-7631]]},{"type":"Polygon","id":45009,"arcs":[[7763,7764,-7734,-7644]]},{"type":"Polygon","id":6025,"arcs":[[-7752,7765,-7722,-7456,-7331]]},{"type":"Polygon","id":48497,"arcs":[[-7538,7766,7767,7768,-7754,-7497]]},{"type":"Polygon","id":48121,"arcs":[[7769,7770,7771,-7767,-7537,-7535]]},{"type":"Polygon","id":13149,"arcs":[[-7717,7772,-7724,-7599]]},{"type":"Polygon","id":48231,"arcs":[[7773,7774,7775,7776,7777,-7562,-7733,7778]]},{"type":"Polygon","id":48085,"arcs":[[-7563,-7778,7779,7780,-7770,-7534]]},{"type":"Polygon","id":48263,"arcs":[[7781,7782,7783,7784,-7580]]},{"type":"Polygon","id":48433,"arcs":[[7785,7786,7787,-7782,-7573]]},{"type":"Polygon","id":48449,"arcs":[[7788,7789,7790,-7526]]},{"type":"Polygon","id":48169,"arcs":[[-7785,7791,7792,7793,-7582]]},{"type":"Polygon","id":48447,"arcs":[[7794,7795,7796,7797,-7584]]},{"type":"Polygon","id":48503,"arcs":[[-7757,7798,7799,-7795,-7578]]},{"type":"Polygon","id":48207,"arcs":[[-7798,7800,7801,-7786,-7576]]},{"type":"Polygon","id":5003,"arcs":[[-7694,7802,7803,-7646,-7611]]},{"type":"Polygon","id":48305,"arcs":[[-7794,7804,7805,7806,-7585]]},{"type":"Polygon","id":35035,"arcs":[[-7453,7807,7808,7809,7810,7811,-7740,-7317]]},{"type":"Polygon","id":48501,"arcs":[[7812,7813,-7680,-7588]]},{"type":"Polygon","id":48445,"arcs":[[-7807,7814,7815,-7813,-7590]]},{"type":"Polygon","id":48159,"arcs":[[-7791,7816,7817,7818,-7731,-7527]]},{"type":"Polygon","id":5139,"arcs":[[-7609,-7647,-7804,7819,7820,-7758,-7592]]},{"type":"Polygon","id":48223,"arcs":[[-7819,7821,7822,-7779,-7732]]},{"type":"Polygon","id":48343,"arcs":[[-7640,7823,7824,7825,7826,-7789,-7525]]},{"type":"Polygon","id":28051,"arcs":[[-7657,7827,7828,7829,-7603]]},{"type":"Polygon","id":13255,"arcs":[[-7763,7830,7831,7832,-7714,-7695,-7665,-7668]]},{"type":"Polygon","id":45035,"arcs":[[-7721,7833,7834,-7642]]},{"type":"Polygon","id":28053,"arcs":[[-7604,-7830,7835,7836,-7707,-7504]]},{"type":"Polygon","id":13125,"arcs":[[7837,7838,-7747,-7674]]},{"type":"Polygon","id":13163,"arcs":[[7839,7840,7841,7842,-7838,-7673,-7659,-7701]]},{"type":"Polygon","id":48067,"arcs":[[-7672,7843,7844,-7824,-7639]]},{"type":"Polygon","id":13033,"arcs":[[-7567,-7736,7845,7846,7847,7848,-7840,-7700]]},{"type":"Polygon","id":28103,"arcs":[[-7704,7849,7850,7851,-7686,-7628]]},{"type":"Polygon","id":28159,"arcs":[[-7852,7852,7853,7854,-7705,-7687]]},{"type":"Polygon","id":28007,"arcs":[[-7855,7855,7856,-7828,-7656,-7654,-7706]]},{"type":"Polygon","id":1007,"arcs":[[7857,7858,7859,-7676,-7570,-7699]]},{"type":"Polygon","id":13303,"arcs":[[-7839,-7843,7860,7861,7862,-7748]]},{"type":"Polygon","id":13199,"arcs":[[-7833,7863,7864,7865,7866,7867,-7715]]},{"type":"Polygon","id":13285,"arcs":[[-7868,7868,7869,-7725,-7773,-7716]]},{"type":"Polygon","id":35017,"arcs":[[-7743,7870,7871,-7620,-7197]]},{"type":"Polygon","id":13231,"arcs":[[7872,7873,-7864,-7832]]},{"type":"Polygon","id":13171,"arcs":[[7874,7875,-7873,-7831,-7762]]},{"type":"Polygon","id":13207,"arcs":[[-7712,7876,7877,7878,7879,-7875,-7761]]},{"type":"Polygon","id":13009,"arcs":[[-7749,-7863,7880,7881,-7738]]},{"type":"Polygon","id":13169,"arcs":[[-7882,7882,7883,7884,-7877,-7711,-7739]]},{"type":"MultiPolygon","id":45029,"arcs":[[[7885,7886]],[[-7835,7887,7888,7889,7890,-7764,-7643]]]},{"type":"Polygon","id":45005,"arcs":[[-7765,7891,7892,-7846,-7735]]},{"type":"Polygon","id":1063,"arcs":[[7893,7894,7895,-7702,-7678]]},{"type":"Polygon","id":1017,"arcs":[[-7870,7896,7897,7898,-7726]]},{"type":"Polygon","id":1123,"arcs":[[-7899,7899,7900,7901,7902,-7729,-7727]]},{"type":"Polygon","id":1037,"arcs":[[-7903,7903,7904,-7697,-7651,-7730]]},{"type":"Polygon","id":28125,"arcs":[[-7837,7905,7906,-7708]]},{"type":"Polygon","id":48063,"arcs":[[-7827,7907,7908,-7817,-7790]]},{"type":"Polygon","id":1021,"arcs":[[-7905,7909,7910,7911,7912,-7858,-7698]]},{"type":"Polygon","id":35013,"arcs":[[7913,7914,7915,-7741,-7812]]},{"type":"Polygon","id":13251,"arcs":[[7916,7917,7918,7919,-7847,-7893]]},{"type":"Polygon","id":45049,"arcs":[[7920,7921,7922,-7917,-7892,-7891]]},{"type":"Polygon","id":28163,"arcs":[[7923,7924,7925,7926,-7906,-7836,-7829]]},{"type":"Polygon","id":22017,"arcs":[[7927,7928,7929,7930,7931,-7844,-7671,7932]]},{"type":"Polygon","id":22015,"arcs":[[7933,7934,7935,-7933,-7670,-7746]]},{"type":"Polygon","id":22119,"arcs":[[7936,-7934,-7745,-7760,7937]]},{"type":"Polygon","id":22027,"arcs":[[7938,7939,-7938,-7759,-7821,7940]]},{"type":"Polygon","id":22111,"arcs":[[7941,7942,-7941,-7820,7943]]},{"type":"Polygon","id":48499,"arcs":[[7944,7945,7946,7947,-7822,-7818,-7909]]},{"type":"Polygon","id":28055,"arcs":[[-7907,-7927,7948,7949,-7690,-7709]]},{"type":"Polygon","id":13319,"arcs":[[-7862,7950,7951,7952,-7883,-7881]]},{"type":"Polygon","id":22067,"arcs":[[-7693,7953,7954,7955,-7944,-7803]]},{"type":"Polygon","id":1065,"arcs":[[-7860,7956,7957,-7894,-7677]]},{"type":"Polygon","id":22123,"arcs":[[7958,7959,-7954,-7692]]},{"type":"Polygon","id":48363,"arcs":[[7960,7961,7962,7963,7964,-7799,-7756]]},{"type":"Polygon","id":22035,"arcs":[[7965,7966,7967,-7959,-7691,-7950]]},{"type":"Polygon","id":48367,"arcs":[[7968,7969,7970,-7961,-7755,-7769]]},{"type":"Polygon","id":13293,"arcs":[[-7876,-7880,7971,7972,7973,-7865,-7874]]},{"type":"Polygon","id":1119,"arcs":[[7974,7975,7976,7977,-7850,-7703,-7896]]},{"type":"Polygon","id":48439,"arcs":[[-7768,-7772,7978,7979,7980,-7969]]},{"type":"Polygon","id":48113,"arcs":[[-7781,7981,7982,7983,-7979,-7771]]},{"type":"Polygon","id":48397,"arcs":[[-7777,7984,-7982,-7780]]},{"type":"Polygon","id":48379,"arcs":[[-7948,7985,-7774,-7823]]},{"type":"Polygon","id":48415,"arcs":[[7986,7987,7988,-7792,-7784]]},{"type":"Polygon","id":35015,"arcs":[[-7685,7989,7990,7991,-7808,-7452]]},{"type":"Polygon","id":48151,"arcs":[[-7788,7992,7993,-7987,-7783]]},{"type":"Polygon","id":48033,"arcs":[[-7989,7994,7995,7996,-7805,-7793]]},{"type":"Polygon","id":48115,"arcs":[[-7997,7997,7998,-7815,-7806]]},{"type":"Polygon","id":48165,"arcs":[[-7816,-7999,7999,8000,-7681,-7814]]},{"type":"Polygon","id":48253,"arcs":[[-7802,8001,8002,8003,-7993,-7787]]},{"type":"Polygon","id":48417,"arcs":[[8004,8005,8006,-8002,-7801,-7797]]},{"type":"Polygon","id":48429,"arcs":[[-7965,8007,-8005,-7796,-7800]]},{"type":"Polygon","id":13165,"arcs":[[-7920,8008,8009,-7848]]},{"type":"Polygon","id":13021,"arcs":[[8010,8011,8012,-7878,-7885]]},{"type":"Polygon","id":28099,"arcs":[[8013,8014,8015,-7854]]},{"type":"Polygon","id":28079,"arcs":[[-8016,8016,8017,8018,-7856]]},{"type":"Polygon","id":28069,"arcs":[[-7978,8019,-8014,-7853,-7851]]},{"type":"Polygon","id":48459,"arcs":[[-7826,8020,8021,8022,8023,-7945,-7908]]},{"type":"Polygon","id":13289,"arcs":[[8024,8025,-8011,-7884,-7953]]},{"type":"Polygon","id":13263,"arcs":[[8026,8027,8028,8029,8030,-7866,-7974]]},{"type":"Polygon","id":48315,"arcs":[[-7932,8031,-8021,-7825,-7845]]},{"type":"Polygon","id":28089,"arcs":[[-8019,8032,8033,8034,-7924,-7857]]},{"type":"Polygon","id":1105,"arcs":[[-7913,8035,8036,-7957,-7859]]},{"type":"Polygon","id":13145,"arcs":[[-8031,8037,8038,-7897,-7869,-7867]]},{"type":"Polygon","id":13079,"arcs":[[-8013,8039,8040,8041,-7972,-7879]]},{"type":"Polygon","id":48257,"arcs":[[8042,8043,8044,-7983,-7985,-7776]]},{"type":"Polygon","id":13107,"arcs":[[-7849,-8010,8045,8046,8047,8048,8049,8050,-7841]]},{"type":"Polygon","id":48467,"arcs":[[-7986,-7947,8051,8052,-8043,-7775]]},{"type":"Polygon","id":13167,"arcs":[[-8051,8053,-7951,-7861,-7842]]},{"type":"Polygon","id":48203,"arcs":[[-7931,8054,8055,8056,-8022,-8032]]},{"type":"Polygon","id":35023,"arcs":[[8057,8058,8059,-7621,-7872]]},{"type":"Polygon","id":1051,"arcs":[[8060,8061,8062,-7910,-7904,-7902]]},{"type":"Polygon","id":22061,"arcs":[[8063,8064,8065,-7939,-7943]]},{"type":"MultiPolygon","id":45053,"arcs":[[[8066,8067,8068,8069,8070,8071,8072,8073,8074,8075,-7922]]]},{"type":"Polygon","id":13269,"arcs":[[-8042,8076,8077,8078,8079,-8027,-7973]]},{"type":"Polygon","id":1081,"arcs":[[-8039,8080,8081,8082,-7900,-7898]]},{"type":"Polygon","id":1047,"arcs":[[8083,8084,8085,8086,-8036,-7912]]},{"type":"Polygon","id":22073,"arcs":[[8087,8088,8089,-8064,-7942,-7956]]},{"type":"Polygon","id":13175,"arcs":[[8090,8091,8092,-7952,-8054,8093]]},{"type":"Polygon","id":1001,"arcs":[[-8063,8094,8095,-8084,-7911]]},{"type":"MultiPolygon","id":45013,"arcs":[[[8096]],[[-8073,8097]],[[-8071,8098]],[[-8069,8099]],[[8100]],[[8101]],[[8102,-8067,-7921,-7890]]]},{"type":"Polygon","id":13225,"arcs":[[8103,-8077,-8041,8104]]},{"type":"Polygon","id":13153,"arcs":[[-8026,8105,8106,8107,8108,-8105,-8040,-8012]]},{"type":"Polygon","id":48423,"arcs":[[-8024,8109,8110,8111,8112,-8052,-7946]]},{"type":"Polygon","id":22083,"arcs":[[-7968,8113,8114,8115,-8088,-7955,-7960]]},{"type":"Polygon","id":48183,"arcs":[[-8057,8116,-8110,-8023]]},{"type":"Polygon","id":13031,"arcs":[[8117,8118,8119,8120,-8046,-8009,-7919]]},{"type":"Polygon","id":28123,"arcs":[[-8018,8121,8122,8123,-8033]]},{"type":"Polygon","id":28149,"arcs":[[8124,8125,8126,8127,8128,8129,-7966,-7949,-7926]]},{"type":"Polygon","id":13215,"arcs":[[-8030,8130,8131,-8081,-8038]]},{"type":"Polygon","id":35029,"arcs":[[-7916,8132,-8058,-7871,-7742]]},{"type":"Polygon","id":13103,"arcs":[[-8076,8133,8134,-8118,-7918,-7923]]},{"type":"Polygon","id":1087,"arcs":[[8135,8136,8137,-8061,-7901,-8083]]},{"type":"Polygon","id":28121,"arcs":[[-8124,8138,8139,8140,-8034]]},{"type":"Polygon","id":22013,"arcs":[[-8066,8141,8142,8143,8144,-7935,-7937,-7940]]},{"type":"Polygon","id":13023,"arcs":[[8145,8146,-8106,-8025,-8093]]},{"type":"Polygon","id":28075,"arcs":[[-7977,8147,8148,8149,-8020]]},{"type":"Polygon","id":28101,"arcs":[[-8150,8150,-8122,-8017,-8015]]},{"type":"Polygon","id":28049,"arcs":[[-8141,8151,8152,-8125,-7925,-8035]]},{"type":"MultiPolygon","id":22065,"arcs":[[[8153,-8128]],[[8154,8155,-8114,-7967,-8130]]]},{"type":"Polygon","id":13197,"arcs":[[8156,8157,8158,8159,8160,-8028,-8080]]},{"type":"Polygon","id":13043,"arcs":[[8161,8162,-8047,-8121]]},{"type":"Polygon","id":48221,"arcs":[[8163,8164,8165,-7962,-7971]]},{"type":"Polygon","id":48251,"arcs":[[8166,8167,8168,8169,-8164,-7970,-7981]]},{"type":"Polygon","id":48139,"arcs":[[-7984,-8045,8170,8171,8172,-8167,-7980]]},{"type":"Polygon","id":1091,"arcs":[[-7958,-8037,-8087,8173,8174,8175,-7975,-7895]]},{"type":"Polygon","id":13053,"arcs":[[-8161,8176,8177,-8131,-8029]]},{"type":"Polygon","id":48227,"arcs":[[8178,8179,8180,8181,-7996]]},{"type":"Polygon","id":48335,"arcs":[[8182,8183,8184,-8179,-7995,-7988]]},{"type":"Polygon","id":48353,"arcs":[[8185,8186,8187,-8183,-7994]]},{"type":"Polygon","id":48317,"arcs":[[-8182,8188,8189,8190,-8000,-7998]]},{"type":"Polygon","id":48003,"arcs":[[-8191,8191,8192,8193,-7682,-8001]]},{"type":"Polygon","id":13193,"arcs":[[-8104,-8109,8194,8195,8196,-8078]]},{"type":"Polygon","id":48441,"arcs":[[8197,8198,8199,-8186,-8004]]},{"type":"Polygon","id":48133,"arcs":[[-7964,8200,8201,8202,8203,-8006,-8008]]},{"type":"Polygon","id":48143,"arcs":[[-7963,-8166,8204,8205,8206,8207,-8201]]},{"type":"Polygon","id":48059,"arcs":[[-8204,8208,8209,-8198,-8003,-8007]]},{"type":"Polygon","id":4019,"arcs":[[-7662,8210,8211,8212,-7750,-7482,-7753]]},{"type":"Polygon","id":13283,"arcs":[[8213,8214,-8094,-8050]]},{"type":"Polygon","id":1113,"arcs":[[-8132,-8178,8215,8216,8217,-8136,-8082]]},{"type":"Polygon","id":1101,"arcs":[[-8138,8218,8219,8220,8221,-8095,-8062]]},{"type":"Polygon","id":22049,"arcs":[[-8090,8222,8223,-8142,-8065]]},{"type":"Polygon","id":13091,"arcs":[[8224,8225,8226,8227,-8146,-8092]]},{"type":"Polygon","id":13249,"arcs":[[-8197,8228,-8157,-8079]]},{"type":"Polygon","id":4003,"arcs":[[-7622,-8060,8229,8230,-8211,-7661]]},{"type":"Polygon","id":1085,"arcs":[[-8222,8231,8232,8233,-8085,-8096]]},{"type":"Polygon","id":48401,"arcs":[[8234,8235,8236,8237,-8111,-8117,-8056]]},{"type":"Polygon","id":22041,"arcs":[[-8156,8238,8239,8240,-8115]]},{"type":"Polygon","id":13235,"arcs":[[-8147,-8228,8241,8242,-8107]]},{"type":"Polygon","id":48365,"arcs":[[-7930,8243,8244,-8235,-8055]]},{"type":"Polygon","id":48213,"arcs":[[-8053,-8113,8245,8246,8247,8248,-8171,-8044]]},{"type":"Polygon","id":13209,"arcs":[[8249,8250,-8214,8251]]},{"type":"Polygon","id":13279,"arcs":[[-8049,8252,8253,8254,-8252]]},{"type":"Polygon","id":22031,"arcs":[[8255,8256,8257,8258,-8244,-7929]]},{"type":"Polygon","id":48349,"arcs":[[8259,8260,8261,-8172,-8249]]},{"type":"Polygon","id":48425,"arcs":[[-8170,8262,-8205,-8165]]},{"type":"Polygon","id":13267,"arcs":[[8263,8264,8265,8266,8267,-8253,-8048,-8163]]},{"type":"Polygon","id":1023,"arcs":[[8268,8269,8270,8271,-8148,-7976,-8176]]},{"type":"Polygon","id":13309,"arcs":[[-8251,8272,8273,-8225,-8091,-8215]]},{"type":"Polygon","id":1011,"arcs":[[-8218,8274,8275,-8219,-8137]]},{"type":"Polygon","id":13093,"arcs":[[-8243,8276,8277,8278,-8195,-8108]]},{"type":"Polygon","id":13109,"arcs":[[-8120,8279,8280,-8264,-8162]]},{"type":"Polygon","id":22021,"arcs":[[-8116,-8241,8281,8282,8283,-8223,-8089]]},{"type":"Polygon","id":1131,"arcs":[[-8086,-8234,8284,8285,8286,-8174]]},{"type":"Polygon","id":48217,"arcs":[[-8262,8287,8288,8289,-8168,-8173]]},{"type":"Polygon","id":48093,"arcs":[[8290,8291,8292,-8202,-8208]]},{"type":"Polygon","id":22107,"arcs":[[-8129,-8154,-8127,8293,8294,8295,8296,8297,-8239,-8155]]},{"type":"MultiPolygon","id":13029,"arcs":[[[8298,8299,8300,-8280,-8119,-8135]]]},{"type":"Polygon","id":22081,"arcs":[[-7936,-8145,8301,-8256,-7928]]},{"type":"MultiPolygon","id":13051,"arcs":[[[8302]],[[8303]],[[8304,-8299,-8134,-8075]]]},{"type":"Polygon","id":13307,"arcs":[[8305,8306,8307,8308,-8159]]},{"type":"Polygon","id":13259,"arcs":[[-8309,8309,8310,8311,-8216,-8177,-8160]]},{"type":"Polygon","id":13261,"arcs":[[-8279,8312,8313,8314,-8306,-8158,-8229,-8196]]},{"type":"Polygon","id":28023,"arcs":[[-8272,8315,8316,-8149]]},{"type":"Polygon","id":28021,"arcs":[[-8153,8317,8318,-8294,-8126]]},{"type":"Polygon","id":28061,"arcs":[[-8317,8319,8320,8321,-8151]]},{"type":"Polygon","id":28129,"arcs":[[-8322,8322,8323,8324,-8139,-8123]]},{"type":"Polygon","id":48035,"arcs":[[-8169,-8290,8325,8326,8327,-8206,-8263]]},{"type":"Polygon","id":13271,"arcs":[[8328,8329,8330,8331,-8226,-8274]]},{"type":"Polygon","id":22127,"arcs":[[-8224,-8284,8332,8333,8334,-8143]]},{"type":"Polygon","id":22069,"arcs":[[-8335,8335,8336,8337,8338,-8257,-8302,-8144]]},{"type":"Polygon","id":1005,"arcs":[[-8312,8339,8340,8341,8342,8343,-8275,-8217]]},{"type":"Polygon","id":48073,"arcs":[[-8238,8344,8345,8346,8347,-8246,-8112]]},{"type":"Polygon","id":13315,"arcs":[[-8227,-8332,8348,8349,8350,-8277,-8242]]},{"type":"MultiPolygon","id":13179,"arcs":[[[8351]],[[8352,8353,8354,-8265,-8281,-8301,8355]]]},{"type":"Polygon","id":48431,"arcs":[[-8185,8356,8357,8358,8359,-8180]]},{"type":"Polygon","id":48173,"arcs":[[-8360,8360,8361,-8189,-8181]]},{"type":"Polygon","id":48329,"arcs":[[-8362,8362,8363,-8192,-8190]]},{"type":"Polygon","id":48135,"arcs":[[-8364,8364,8365,8366,8367,-8193]]},{"type":"Polygon","id":48495,"arcs":[[-8368,8368,8369,-7683,-8194]]},{"type":"Polygon","id":48081,"arcs":[[-8188,8370,8371,-8357,-8184]]},{"type":"Polygon","id":48001,"arcs":[[8372,8373,8374,-8247,-8348]]},{"type":"Polygon","id":48083,"arcs":[[-8210,8375,8376,8377,8378,-8199]]},{"type":"Polygon","id":48399,"arcs":[[-8379,8379,8380,-8371,-8187,-8200]]},{"type":"Polygon","id":48049,"arcs":[[-8293,8381,8382,8383,-8376,-8209,-8203]]},{"type":"Polygon","id":1109,"arcs":[[-8344,8384,8385,8386,-8220,-8276]]},{"type":"Polygon","id":1041,"arcs":[[-8387,8387,8388,8389,-8232,-8221]]},{"type":"Polygon","id":28029,"arcs":[[8390,8391,8392,8393,-8318,-8152]]},{"type":"Polygon","id":28127,"arcs":[[-8325,8394,8395,8396,-8391,-8140]]},{"type":"Polygon","id":13081,"arcs":[[-8351,8397,8398,8399,-8313,-8278]]},{"type":"Polygon","id":48193,"arcs":[[8400,-8291,-8207,-8328,8401,8402]]},{"type":"Polygon","id":13183,"arcs":[[8403,8404,-8266,-8355]]},{"type":"Polygon","id":48161,"arcs":[[8405,8406,-8260,-8248,-8375]]},{"type":"Polygon","id":48109,"arcs":[[8407,8408,8409,-7809,-7992]]},{"type":"Polygon","id":48229,"arcs":[[8410,8411,8412,-7810,-8410]]},{"type":"Polygon","id":48141,"arcs":[[8413,-7914,-7811,-8413]]},{"type":"Polygon","id":48301,"arcs":[[8414,8415,-7990,-7684,-8370]]},{"type":"Polygon","id":48389,"arcs":[[8416,8417,8418,-8408,-7991,-8416]]},{"type":"Polygon","id":13239,"arcs":[[8419,8420,-8340,-8311]]},{"type":"Polygon","id":1025,"arcs":[[-8287,8421,8422,8423,-8269,-8175]]},{"type":"Polygon","id":48419,"arcs":[[8424,8425,8426,8427,-8236,-8245,-8259]]},{"type":"Polygon","id":22025,"arcs":[[-8298,8428,8429,8430,-8282,-8240]]},{"type":"Polygon","id":13161,"arcs":[[8431,8432,8433,-8329,-8273,-8250,-8255]]},{"type":"Polygon","id":13273,"arcs":[[8434,8435,8436,8437,-8307,-8315]]},{"type":"Polygon","id":13001,"arcs":[[-8268,8438,8439,8440,-8432,-8254]]},{"type":"Polygon","id":1013,"arcs":[[-8390,8441,8442,8443,-8285,-8233]]},{"type":"Polygon","id":13243,"arcs":[[-8308,-8438,8444,8445,-8420,-8310]]},{"type":"Polygon","id":22059,"arcs":[[-8431,8446,8447,8448,-8333,-8283]]},{"type":"Polygon","id":13177,"arcs":[[-8400,8449,8450,-8435,-8314]]},{"type":"Polygon","id":28153,"arcs":[[8451,8452,8453,-8320,-8316,-8271,8454]]},{"type":"Polygon","id":28063,"arcs":[[-8394,8455,8456,8457,-8295,-8319]]},{"type":"Polygon","id":48309,"arcs":[[8458,-8326,-8289,8459,8460,8461]]},{"type":"Polygon","id":13287,"arcs":[[8462,8463,8464,8465,-8398,-8350]]},{"type":"Polygon","id":13017,"arcs":[[-8331,8466,8467,-8463,-8349]]},{"type":"Polygon","id":13321,"arcs":[[-8399,-8466,8468,8469,8470,8471,-8450]]},{"type":"Polygon","id":48347,"arcs":[[-8428,8472,8473,-8345,-8237]]},{"type":"Polygon","id":22085,"arcs":[[-8339,8474,8475,8476,-8425,-8258]]},{"type":"Polygon","id":13305,"arcs":[[-8267,-8405,8477,8478,8479,8480,-8439]]},{"type":"Polygon","id":1099,"arcs":[[-8444,8481,8482,8483,-8422,-8286]]},{"type":"Polygon","id":28067,"arcs":[[8484,8485,8486,-8323,-8321,-8454]]},{"type":"Polygon","id":48293,"arcs":[[8487,-8460,-8288,-8261,-8407,8488,8489]]},{"type":"Polygon","id":13069,"arcs":[[8490,8491,8492,8493,8494,-8467,-8330,-8434]]},{"type":"Polygon","id":22043,"arcs":[[-8449,8495,-8336,-8334]]},{"type":"Polygon","id":28031,"arcs":[[-8487,8496,8497,8498,-8395,-8324]]},{"type":"Polygon","id":13061,"arcs":[[-8446,8499,8500,8501,-8341,-8421]]},{"type":"Polygon","id":1067,"arcs":[[-8502,8502,8503,8504,-8342]]},{"type":"Polygon","id":28065,"arcs":[[8505,8506,8507,-8396,-8499]]},{"type":"Polygon","id":13155,"arcs":[[-8495,8508,8509,-8464,-8468]]},{"type":"Polygon","id":22029,"arcs":[[-8297,8510,8511,8512,8513,8514,8515,-8429]]},{"type":"Polygon","id":28077,"arcs":[[8516,8517,8518,-8392,-8397,-8508]]},{"type":"Polygon","id":1035,"arcs":[[8519,8520,-8482,-8443]]},{"type":"Polygon","id":28001,"arcs":[[8521,8522,-8511,-8296,-8458]]},{"type":"Polygon","id":4023,"arcs":[[-8231,8523,-8212]]},{"type":"Polygon","id":48333,"arcs":[[8524,8525,-8382,-8292,-8401]]},{"type":"Polygon","id":28085,"arcs":[[8526,8527,8528,8529,-8456,-8393,-8519]]},{"type":"Polygon","id":13005,"arcs":[[8530,8531,-8491,-8433,-8441]]},{"type":"Polygon","id":48099,"arcs":[[8532,-8402,-8327,-8459,8533]]},{"type":"Polygon","id":48451,"arcs":[[-8381,8534,8535,8536,8537,-8358,-8372]]},{"type":"Polygon","id":1129,"arcs":[[-8424,8538,8539,8540,-8455,-8270]]},{"type":"MultiPolygon","id":13191,"arcs":[[[8541,8542]],[[8543,8544]],[[8545]],[[8546,8547,-8478,-8404,-8354]]]},{"type":"Polygon","id":48289,"arcs":[[8548,8549,8550,-8489,-8406,-8374]]},{"type":"Polygon","id":48461,"arcs":[[8551,8552,8553,-8365,-8363]]},{"type":"Polygon","id":48103,"arcs":[[-8554,8554,8555,8556,-8366]]},{"type":"Polygon","id":48405,"arcs":[[8557,8558,8559,-8473,-8427]]},{"type":"Polygon","id":48383,"arcs":[[-8359,-8538,8560,8561,-8552,-8361]]},{"type":"Polygon","id":48475,"arcs":[[-8415,-8369,-8367,-8557,8562,-8417]]},{"type":"Polygon","id":13095,"arcs":[[-8472,8563,8564,8565,-8436,-8451]]},{"type":"Polygon","id":13037,"arcs":[[-8437,-8566,8566,8567,-8500,-8445]]},{"type":"Polygon","id":1045,"arcs":[[-8505,8568,8569,8570,-8385,-8343]]},{"type":"Polygon","id":1031,"arcs":[[8571,8572,-8388,-8386,-8571]]},{"type":"Polygon","id":28037,"arcs":[[-8530,8573,8574,-8522,-8457]]},{"type":"Polygon","id":48403,"arcs":[[-8477,8575,8576,-8558,-8426]]},{"type":"Polygon","id":13277,"arcs":[[-8510,8577,8578,8579,-8469,-8465]]},{"type":"Polygon","id":48225,"arcs":[[8580,8581,8582,8583,-8549,-8373,-8347]]},{"type":"Polygon","id":48095,"arcs":[[-8378,8584,8585,-8535,-8380]]},{"type":"Polygon","id":13229,"arcs":[[-8481,8586,8587,-8531,-8440]]},{"type":"Polygon","id":1039,"arcs":[[-8573,8588,8589,8590,8591,-8520,-8442,-8389]]},{"type":"Polygon","id":48235,"arcs":[[8592,8593,-8561,-8537]]},{"type":"Polygon","id":48005,"arcs":[[-8560,8594,8595,8596,8597,-8581,-8346,-8474]]},{"type":"Polygon","id":13099,"arcs":[[-8568,8598,8599,8600,8601,-8503,-8501]]},{"type":"Polygon","id":48145,"arcs":[[8602,8603,-8461,-8488,8604]]},{"type":"Polygon","id":22079,"arcs":[[-8448,8605,8606,8607,8608,-8337,-8496]]},{"type":"Polygon","id":48307,"arcs":[[-8384,8609,8610,8611,-8585,-8377]]},{"type":"Polygon","id":48411,"arcs":[[-8526,8612,8613,8614,8615,-8610,-8383]]},{"type":"Polygon","id":13019,"arcs":[[-8494,8616,8617,8618,8619,-8578,-8509]]},{"type":"Polygon","id":13299,"arcs":[[-8588,8620,8621,8622,8623,8624,-8492,-8532]]},{"type":"Polygon","id":48281,"arcs":[[8625,8626,-8613,-8525,-8403,-8533]]},{"type":"MultiPolygon","id":13127,"arcs":[[[-8542,8627]],[[8628,-8544,8629,8630,8631,-8479,-8548]]]},{"type":"Polygon","id":13007,"arcs":[[-8565,8632,8633,8634,-8599,-8567]]},{"type":"Polygon","id":13205,"arcs":[[-8471,8635,8636,8637,8638,-8633,-8564]]},{"type":"Polygon","id":28041,"arcs":[[-8541,8639,8640,8641,-8452]]},{"type":"Polygon","id":28035,"arcs":[[8642,8643,8644,8645,-8497,-8486]]},{"type":"Polygon","id":28073,"arcs":[[-8498,-8646,8646,8647,-8506]]},{"type":"Polygon","id":28111,"arcs":[[-8453,-8642,8648,8649,-8643,-8485]]},{"type":"Polygon","id":28091,"arcs":[[-8648,8650,8651,8652,-8517,-8507]]},{"type":"Polygon","id":13003,"arcs":[[-8625,8653,8654,-8617,-8493]]},{"type":"Polygon","id":48455,"arcs":[[8655,8656,8657,-8582,-8598]]},{"type":"Polygon","id":48371,"arcs":[[-8556,8658,8659,8660,8661,-8418,-8563]]},{"type":"Polygon","id":13025,"arcs":[[-8632,8662,8663,-8621,-8587,-8480]]},{"type":"Polygon","id":28157,"arcs":[[-8575,8664,8665,8666,-8512,-8523]]},{"type":"Polygon","id":22115,"arcs":[[-8609,8667,8668,8669,-8475,-8338]]},{"type":"Polygon","id":48395,"arcs":[[8670,8671,8672,-8605,-8490,-8551]]},{"type":"Polygon","id":13075,"arcs":[[8673,8674,8675,-8579,-8620]]},{"type":"Polygon","id":28005,"arcs":[[-8529,8676,8677,8678,8679,-8665,-8574]]},{"type":"Polygon","id":28113,"arcs":[[8680,8681,8682,-8677,-8528]]},{"type":"Polygon","id":28147,"arcs":[[-8653,8683,-8681,-8527,-8518]]},{"type":"Polygon","id":22009,"arcs":[[-8430,-8516,8684,8685,8686,8687,-8606,-8447]]},{"type":"Polygon","id":13071,"arcs":[[-8580,-8676,8688,8689,-8636,-8470]]},{"type":"Polygon","id":48027,"arcs":[[-8604,8690,8691,8692,-8626,-8534,-8462]]},{"type":"MultiPolygon","id":1003,"arcs":[[[8693,8694]],[[-8484,8695,8696,8697,8698,8699,8700,-8539,-8423]]]},{"type":"Polygon","id":1069,"arcs":[[-8602,8701,8702,8703,-8569,-8504]]},{"type":"Polygon","id":1053,"arcs":[[-8592,8704,8705,8706,-8696,-8483,-8521]]},{"type":"Polygon","id":13201,"arcs":[[-8635,8707,8708,-8600]]},{"type":"Polygon","id":1061,"arcs":[[8709,8710,8711,-8589,-8572,-8570,-8704]]},{"type":"Polygon","id":48351,"arcs":[[8712,8713,8714,8715,-8576,-8476,-8670]]},{"type":"Polygon","id":13065,"arcs":[[-8624,8716,8717,8718,8719,-8654]]},{"type":"Polygon","id":13173,"arcs":[[-8720,8720,8721,-8618,-8655]]},{"type":"MultiPolygon","id":1097,"arcs":[[[-8699,8722]],[[8723,8724,8725,-8640,-8540,-8701]]]},{"type":"MultiPolygon","id":13039,"arcs":[[[8726]],[[8727,8728,8729,-8663,-8631]]]},{"type":"Polygon","id":48241,"arcs":[[8730,8731,8732,-8595,-8559,-8577,-8716]]},{"type":"Polygon","id":48373,"arcs":[[8733,8734,8735,8736,-8656,-8597]]},{"type":"Polygon","id":48331,"arcs":[[8737,8738,8739,-8691,-8603,-8673]]},{"type":"Polygon","id":48243,"arcs":[[8740,-8411,-8409,-8419,-8662,8741]]},{"type":"Polygon","id":48313,"arcs":[[8742,8743,8744,-8550,-8584]]},{"type":"Polygon","id":48327,"arcs":[[-8612,8745,8746,8747,-8586]]},{"type":"Polygon","id":48413,"arcs":[[-8748,8748,8749,-8593,-8536]]},{"type":"Polygon","id":48105,"arcs":[[-8553,-8562,-8594,-8750,8750,8751,8752,-8659,-8555]]},{"type":"Polygon","id":13087,"arcs":[[-8634,-8639,8753,8754,8755,-8708]]},{"type":"Polygon","id":13131,"arcs":[[8756,8757,8758,-8754,-8638]]},{"type":"MultiPolygon","id":13027,"arcs":[[[8759,8760]],[[8761,8762,8763,8764,-8689,-8675]]]},{"type":"Polygon","id":13275,"arcs":[[-8690,-8765,8765,8766,-8757,-8637]]},{"type":"Polygon","id":13253,"arcs":[[-8756,8767,8768,-8702,-8601,-8709]]},{"type":"Polygon","id":13049,"arcs":[[8769,8770,-8622,-8664,-8730]]},{"type":"Polygon","id":48457,"arcs":[[-8733,8771,-8734,-8596]]},{"type":"Polygon","id":48471,"arcs":[[8772,8773,8774,-8743,-8583,-8658]]},{"type":"MultiPolygon","id":22125,"arcs":[[[8775,8776,-8513,-8667,8777]],[[8778,-8685,-8515]]]},{"type":"Polygon","id":48053,"arcs":[[-8693,8779,8780,8781,8782,-8614,-8627]]},{"type":"Polygon","id":13185,"arcs":[[-8619,-8722,8783,8784,-8761,8785,-8762,-8674]]},{"type":"Polygon","id":22077,"arcs":[[-8514,-8777,8786,8787,8788,8789,-8686,-8779]]},{"type":"Polygon","id":28109,"arcs":[[-8645,8790,8791,8792,8793,-8651,-8647]]},{"type":"Polygon","id":22117,"arcs":[[-8794,8794,8795,-8682,-8684,-8652]]},{"type":"Polygon","id":22039,"arcs":[[-8688,8796,8797,8798,8799,-8607]]},{"type":"Polygon","id":12063,"arcs":[[-8769,8800,8801,8802,8803,8804,-8710,-8703]]},{"type":"Polygon","id":22105,"arcs":[[-8796,8805,8806,8807,8808,8809,-8678,-8683]]},{"type":"MultiPolygon","id":12033,"arcs":[[[-8695,8810]],[[8811,8812]],[[8813,-8697,-8707,8814]]]},{"type":"MultiPolygon","id":12113,"arcs":[[[8815,-8812,8816,8817]],[[8818,8819,-8815,-8706]]]},{"type":"Polygon","id":28039,"arcs":[[-8726,8820,8821,-8649,-8641]]},{"type":"Polygon","id":22091,"arcs":[[-8810,8822,8823,8824,-8679]]},{"type":"MultiPolygon","id":12091,"arcs":[[[-8818,8825]],[[8826,8827]],[[-8591,8828,8829,-8819,-8705]]]},{"type":"Polygon","id":22037,"arcs":[[-8825,8830,8831,-8778,-8666,-8680]]},{"type":"Polygon","id":12059,"arcs":[[-8805,8832,8833,-8711]]},{"type":"Polygon","id":12131,"arcs":[[-8712,-8834,8834,8835,8836,-8827,8837,-8829,-8590]]},{"type":"Polygon","id":48041,"arcs":[[8838,8839,8840,-8671,-8745]]},{"type":"Polygon","id":48319,"arcs":[[-8616,8841,8842,8843,-8746,-8611]]},{"type":"Polygon","id":48299,"arcs":[[-8783,8844,8845,-8842,-8615]]},{"type":"Polygon","id":28131,"arcs":[[-8822,8846,8847,-8791,-8644,-8650]]},{"type":"Polygon","id":48491,"arcs":[[8848,8849,8850,-8780,-8692,-8740]]},{"type":"Polygon","id":48407,"arcs":[[-8737,8851,8852,-8773,-8657]]},{"type":"Polygon","id":22003,"arcs":[[-8800,8853,8854,-8668,-8608]]},{"type":"Polygon","id":22011,"arcs":[[-8855,8855,8856,-8713,-8669]]},{"type":"Polygon","id":13101,"arcs":[[8857,8858,-8784,-8721,-8719]]},{"type":"Polygon","id":48185,"arcs":[[-8775,8859,8860,8861,-8839,-8744]]},{"type":"Polygon","id":22097,"arcs":[[-8790,8862,8863,8864,-8797,-8687]]},{"type":"Polygon","id":12133,"arcs":[[8865,-8835,-8833,-8804]]},{"type":"MultiPolygon","id":12089,"arcs":[[[-8729,8866,8867,8868,-8770]]]},{"type":"MultiPolygon","id":28059,"arcs":[[[-8725,8869,8870,-8847,-8821]]]},{"type":"Polygon","id":48051,"arcs":[[-8841,8871,8872,-8738,-8672]]},{"type":"Polygon","id":22033,"arcs":[[8873,8874,8875,8876,-8831,-8824]]},{"type":"Polygon","id":22103,"arcs":[[-8793,8877,8878,8879,8880,-8806,-8795]]},{"type":"Polygon","id":12039,"arcs":[[-8759,8881,8882,-8801,-8768,-8755]]},{"type":"Polygon","id":48267,"arcs":[[8883,8884,8885,8886,-8747,-8844]]},{"type":"Polygon","id":48435,"arcs":[[-8887,8887,8888,-8751,-8749]]},{"type":"Polygon","id":12073,"arcs":[[-8767,8889,8890,8891,-8882,-8758]]},{"type":"MultiPolygon","id":28047,"arcs":[[[-8871,8892,8893,-8848]]]},{"type":"Polygon","id":12065,"arcs":[[-8764,8894,8895,8896,8897,-8890,-8766]]},{"type":"Polygon","id":48043,"arcs":[[8898,-8742,-8661,8899,8900]]},{"type":"Polygon","id":22121,"arcs":[[8901,-8787,-8776,-8832,-8877]]},{"type":"Polygon","id":48443,"arcs":[[8902,8903,-8900,-8660,-8753]]},{"type":"Polygon","id":22063,"arcs":[[-8809,8904,8905,-8874,-8823]]},{"type":"Polygon","id":12079,"arcs":[[-8786,-8760,8906,8907,8908,8909,-8895,-8763]]},{"type":"Polygon","id":28045,"arcs":[[-8894,8910,-8878,-8792]]},{"type":"Polygon","id":12047,"arcs":[[-8859,8911,8912,-8907,-8785]]},{"type":"Polygon","id":48377,"arcs":[[-8899,8913,-8741]]},{"type":"Polygon","id":48339,"arcs":[[-8853,8914,8915,8916,-8860,-8774]]},{"type":"Polygon","id":48453,"arcs":[[8917,8918,8919,8920,-8781,-8851]]},{"type":"Polygon","id":12013,"arcs":[[8921,8922,8923,-8802]]},{"type":"Polygon","id":12077,"arcs":[[-8892,8924,8925,8926,-8922,-8883]]},{"type":"Polygon","id":12023,"arcs":[[-8718,8927,8928,8929,8930,8931,-8912,-8858]]},{"type":"MultiPolygon","id":12031,"arcs":[[[8932,8933]],[[8934,8935]],[[8936,8937,8938,-8868]]]},{"type":"Polygon","id":12003,"arcs":[[-8869,-8939,8939,8940,8941,-8928,-8717,-8623,-8771]]},{"type":"MultiPolygon","id":12005,"arcs":[[[8942,8943]],[[-8924,8944,8945,-8836,-8866,-8803]]]},{"type":"Polygon","id":48287,"arcs":[[8946,8947,8948,-8849,-8739,-8873]]},{"type":"Polygon","id":48199,"arcs":[[-8732,8949,8950,8951,-8735,-8772]]},{"type":"Polygon","id":48171,"arcs":[[-8846,8952,8953,8954,-8884,-8843]]},{"type":"Polygon","id":48031,"arcs":[[-8782,-8921,8955,8956,8957,-8953,-8845]]},{"type":"MultiPolygon","id":22099,"arcs":[[[8958,8959,8960]],[[8961,8962,-8863,-8789,8963]]]},{"type":"Polygon","id":22047,"arcs":[[-8902,-8876,8964,8965,8966,-8964,-8788]]},{"type":"Polygon","id":48291,"arcs":[[-8952,8967,8968,8969,-8915,-8852,-8736]]},{"type":"Polygon","id":22019,"arcs":[[8970,8971,8972,-8714,-8857]]},{"type":"Polygon","id":22053,"arcs":[[-8799,8973,8974,8975,-8971,-8856,-8854]]},{"type":"Polygon","id":22001,"arcs":[[-8865,8976,8977,-8974,-8798]]},{"type":"Polygon","id":12121,"arcs":[[-8932,8978,8979,-8908,-8913]]},{"type":"Polygon","id":48021,"arcs":[[8980,8981,-8918,-8850,-8949]]},{"type":"Polygon","id":48477,"arcs":[[-8862,8982,8983,8984,-8947,-8872,-8840]]},{"type":"Polygon","id":22055,"arcs":[[-8963,8985,8986,-8977,-8864]]},{"type":"Polygon","id":48209,"arcs":[[8987,8988,-8956,-8920,8989]]},{"type":"Polygon","id":22005,"arcs":[[-8906,8990,8991,8992,-8965,-8875]]},{"type":"Polygon","id":12123,"arcs":[[8993,8994,8995,-8896,-8910]]},{"type":"MultiPolygon","id":12129,"arcs":[[[-8898,8996,8997,-8925,-8891]]]},{"type":"Polygon","id":22095,"arcs":[[8998,8999,9000,-8991,-8905,-8808]]},{"type":"Polygon","id":48465,"arcs":[[-8889,9001,9002,9003,-8903,-8752]]},{"type":"Polygon","id":48137,"arcs":[[9004,9005,9006,9007,-9002,-8888,-8886]]},{"type":"Polygon","id":48265,"arcs":[[9008,9009,9010,-9005,-8885,-8955]]},{"type":"Polygon","id":12067,"arcs":[[-8980,9011,9012,-8994,-8909]]},{"type":"MultiPolygon","id":12109,"arcs":[[[9013,9014]],[[9015,9016,9017,9018,9019,-8933,9020]],[[-8935,9021]]]},{"type":"Polygon","id":48361,"arcs":[[-8973,9022,9023,-8950,-8731,-8715]]},{"type":"Polygon","id":48473,"arcs":[[9024,9025,9026,-8983,-8861,-8917]]},{"type":"Polygon","id":22089,"arcs":[[9027,-8999,9028]]},{"type":"MultiPolygon","id":22051,"arcs":[[[9029,9030]],[[9031,9032,9033,9034,-9029,-8807,-8881]]]},{"type":"Polygon","id":12045,"arcs":[[-8927,9035,9036,9037,9038,-8943,9039,-8945,-8923]]},{"type":"Polygon","id":22071,"arcs":[[9040,9041,9042,-9032,-8880]]},{"type":"Polygon","id":12019,"arcs":[[9043,9044,9045,-8940,-8938]]},{"type":"Polygon","id":48245,"arcs":[[-9024,9046,9047,9048,-8968,-8951]]},{"type":"MultiPolygon","id":48201,"arcs":[[[9049,9050]],[[-8970,9051,9052,9053,9054,9055,-9025,-8916]]]},{"type":"MultiPolygon","id":22087,"arcs":[[[-9057,-9058,-9059,-9060]],[[9060,9061,9062,9063,9064,9065,9066,-9042,9067]]]},{"type":"Polygon","id":22093,"arcs":[[9068,9069,-8992,-9001]]},{"type":"Polygon","id":48149,"arcs":[[9070,9071,9072,9073,9074,-8981,-8948,-8985]]},{"type":"Polygon","id":22113,"arcs":[[-8987,9075,9076,9077,-8975,-8978]]},{"type":"Polygon","id":12007,"arcs":[[-9046,9078,9079,9080,-8941]]},{"type":"Polygon","id":12125,"arcs":[[-9081,9081,-8929,-8942]]},{"type":"Polygon","id":48259,"arcs":[[-8958,9082,9083,9084,-9009,-8954]]},{"type":"MultiPolygon","id":22045,"arcs":[[[9085]],[[-8967,9086,-8960,9087,9088,-9076,-8986,-8962]]]},{"type":"Polygon","id":48015,"arcs":[[-9027,9089,9090,9091,-9071,-8984]]},{"type":"Polygon","id":22007,"arcs":[[-9070,9092,9093,9094,-8961,-9087,-8966,-8993]]},{"type":"Polygon","id":48385,"arcs":[[-9011,9095,9096,-9006]]},{"type":"Polygon","id":48055,"arcs":[[-8982,-9075,9097,9098,-8990,-8919]]},{"type":"Polygon","id":22023,"arcs":[[-8976,-9078,9099,-9047,-9023,-8972]]},{"type":"Polygon","id":48091,"arcs":[[9100,9101,-9083,-8957,-8989]]},{"type":"MultiPolygon","id":12037,"arcs":[[[9102]],[[-9038,9103]],[[-8998,9104,-9036,-8926]]]},{"type":"Polygon","id":48089,"arcs":[[9105,9106,9107,-9072,-9092]]},{"type":"MultiPolygon","id":22101,"arcs":[[[-9095,9108,9109,-9088,-8959]]]},{"type":"Polygon","id":12001,"arcs":[[-9080,9110,9111,9112,9113,-8930,-9082]]},{"type":"Polygon","id":12041,"arcs":[[-9114,9114,9115,-9012,-8979,-8931]]},{"type":"MultiPolygon","id":22057,"arcs":[[[9116,9117]],[[9118,9119,9120,-9030,9121,9122,9123,9124,9125,9126,9127,-9093,-9069,-9000,-9028,-9035,9128]]]},{"type":"Polygon","id":48019,"arcs":[[9129,-9096,-9010,-9085,9130,9131]]},{"type":"MultiPolygon","id":22075,"arcs":[[[9132]],[[-9059,9133]],[[9134,-9058]],[[9135,-9065]],[[9136,9137,9138,9139,-9033,-9043,-9067,9140]]]},{"type":"MultiPolygon","id":48071,"arcs":[[[-9050,9141]],[[-9049,9142,9143,9144,-9052,-8969]]]},{"type":"Polygon","id":48187,"arcs":[[9145,9146,9147,-9101,-8988,-9099]]},{"type":"MultiPolygon","id":12107,"arcs":[[[-9019,9148,9149,9150]],[[9151,9152,-9111,-9079,-9045]]]},{"type":"Polygon","id":48157,"arcs":[[9153,9154,-9090,-9026,-9056]]},{"type":"Polygon","id":48177,"arcs":[[9155,9156,9157,9158,-9146,-9098,-9074]]},{"type":"MultiPolygon","id":22109,"arcs":[[[9159,-9117]],[[9160,-9124]],[[9161,-9126]],[[9162]],[[9163,9164,9165,9166,9167,-9109,-9094,-9128,9168]]]},{"type":"Polygon","id":48029,"arcs":[[-9148,9169,9170,9171,-9131,-9084,-9102]]},{"type":"Polygon","id":48325,"arcs":[[9172,9173,9174,-9132,-9172]]},{"type":"MultiPolygon","id":12035,"arcs":[[[9175,9176,-9149,-9018]],[[-9016,9177]],[[9178,9179,9180,-9015]]]},{"type":"Polygon","id":48481,"arcs":[[-9155,9181,9182,9183,-9106,-9091]]},{"type":"Polygon","id":48285,"arcs":[[9184,9185,9186,-9156,-9073,-9108]]},{"type":"Polygon","id":48463,"arcs":[[-9175,9187,9188,-9007,-9097,-9130]]},{"type":"Polygon","id":48271,"arcs":[[-9189,9189,9190,-9003,-9008]]},{"type":"MultiPolygon","id":48167,"arcs":[[[9191]],[[9192,9193,-9054,9194]],[[9195,-9144]]]},{"type":"MultiPolygon","id":48039,"arcs":[[[-9194,9196,9197,-9182,-9154,-9055]]]},{"type":"MultiPolygon","id":12075,"arcs":[[[-9113,9198,9199,9200,9201,9202,-9115]]]},{"type":"Polygon","id":12083,"arcs":[[9203,9204,9205,9206,-9199,-9112,-9153]]},{"type":"Polygon","id":48493,"arcs":[[-9159,9207,9208,-9170,-9147]]},{"type":"MultiPolygon","id":12127,"arcs":[[[9209,9210]],[[9211,9212,9213,9214,9215,9216,-9150,-9177,9217]],[[9218,-9180]]]},{"type":"Polygon","id":48123,"arcs":[[9219,9220,-9157,-9187,9221]]},{"type":"Polygon","id":12069,"arcs":[[-9216,9222,9223,9224,9225,-9205,9226]]},{"type":"Polygon","id":48239,"arcs":[[9227,9228,9229,9230,9231,9232,9233,9234,-9185,-9107,-9184]]},{"type":"Polygon","id":48013,"arcs":[[9235,9236,-9173,-9171,-9209,9237,9238]]},{"type":"MultiPolygon","id":48321,"arcs":[[[9239,9240,-9228,-9183,-9198,9241]]]},{"type":"Polygon","id":48255,"arcs":[[9242,9243,-9238,-9208,-9158,-9221,9244]]},{"type":"MultiPolygon","id":48469,"arcs":[[[-9233,9245]],[[-9235,9246,9247,9248,9249,-9222,-9186]]]},{"type":"Polygon","id":48163,"arcs":[[-9237,9250,9251,9252,-9174]]},{"type":"Polygon","id":48507,"arcs":[[-9253,9253,9254,-9188]]},{"type":"Polygon","id":48323,"arcs":[[-9255,9255,9256,9257,-9190]]},{"type":"MultiPolygon","id":12017,"arcs":[[[9258,-9201]],[[-9200,-9207,9259,9260,9261]]]},{"type":"Polygon","id":12119,"arcs":[[-9226,9262,9263,9264,-9260,-9206]]},{"type":"Polygon","id":48175,"arcs":[[-9250,9265,9266,-9245,-9220]]},{"type":"Polygon","id":12117,"arcs":[[9267,-9223,-9215]]},{"type":"MultiPolygon","id":12009,"arcs":[[[9268,9269,9270,9271,-9214]],[[9272,-9210,9273,-9212]]]},{"type":"Polygon","id":48297,"arcs":[[9274,9275,9276,-9239,-9244,9277,9278]]},{"type":"Polygon","id":12095,"arcs":[[-9268,-9272,9279,-9224]]},{"type":"MultiPolygon","id":48057,"arcs":[[[9280,9281,9282]],[[9283,9284,9285]],[[-9241,9286,-9229]],[[9287,-9248,9288]],[[9289,-9231,9290]]]},{"type":"Polygon","id":48025,"arcs":[[-9267,9291,9292,-9278,-9243]]},{"type":"Polygon","id":12053,"arcs":[[-9265,9293,9294,-9261]]},{"type":"Polygon","id":48283,"arcs":[[9295,9296,9297,-9251]]},{"type":"Polygon","id":48311,"arcs":[[-9277,9298,-9296,-9236]]},{"type":"Polygon","id":48127,"arcs":[[-9252,-9298,9299,-9256,-9254]]},{"type":"Polygon","id":48391,"arcs":[[-9288,9300,9301,9302,9303,9304,-9292,-9266,-9249]]},{"type":"MultiPolygon","id":12101,"arcs":[[[9305,9306]],[[-9264,9307,9308,9309,9310,-9294]]]},{"type":"Polygon","id":12105,"arcs":[[9311,9312,9313,9314,-9308,-9263,-9225]]},{"type":"Polygon","id":12097,"arcs":[[-9271,9315,9316,-9312,-9280]]},{"type":"MultiPolygon","id":48007,"arcs":[[[9317,9318]],[[9319]],[[9320,9321,-9304]],[[9322,9323]],[[-9285,9324]],[[9325,-9302,9326,-9282,9327]]]},{"type":"Polygon","id":48479,"arcs":[[9328,9329,9330,9331,-9257,-9300,-9297]]},{"type":"Polygon","id":48409,"arcs":[[-9322,9332,-9323,9333,9334,9335,9336,9337,-9279,-9293,-9305]]},{"type":"MultiPolygon","id":12103,"arcs":[[[-9306,9338]],[[9339,9340,-9310]]]},{"type":"MultiPolygon","id":12057,"arcs":[[[-9315,9341,9342,-9340,-9309]]]},{"type":"Polygon","id":48131,"arcs":[[9343,9344,9345,-9329,-9299,-9276]]},{"type":"Polygon","id":48249,"arcs":[[-9338,9346,9347,9348,-9344,-9275]]},{"type":"MultiPolygon","id":48355,"arcs":[[[9349,9350]],[[9351]],[[-9318,9352]],[[-9335,9353]],[[9354,9355,-9347,-9337,9356]]]},{"type":"MultiPolygon","id":12061,"arcs":[[[9357,9358,-9316,-9270,9359]],[[9360,9361]]]},{"type":"Polygon","id":12055,"arcs":[[9362,9363,9364,9365,-9313]]},{"type":"Polygon","id":12049,"arcs":[[-9366,9366,9367,-9314]]},{"type":"MultiPolygon","id":12081,"arcs":[[[9368,9369]],[[-9368,9370,9371,9372,-9342]]]},{"type":"Polygon","id":12093,"arcs":[[-9359,9373,9374,9375,-9363,-9317]]},{"type":"MultiPolygon","id":48273,"arcs":[[[9376,9377,-9350,9378]],[[9379,9380,9381,-9348,-9356]]]},{"type":"MultiPolygon","id":12111,"arcs":[[[9382,9383]],[[9384,-9361]],[[9385,9386,9387,9388,-9374,-9358]]]},{"type":"MultiPolygon","id":12115,"arcs":[[[9389,9390]],[[-9372,9391,9392,9393,9394,9395]],[[-9369,9396]]]},{"type":"Polygon","id":48247,"arcs":[[9397,9398,9399,-9330,-9346]]},{"type":"Polygon","id":12027,"arcs":[[-9365,9400,-9392,-9371,-9367]]},{"type":"Polygon","id":48505,"arcs":[[-9400,9401,9402,-9331]]},{"type":"MultiPolygon","id":48261,"arcs":[[[9403,9404]],[[9405,9406,9407,9408]],[[9409,9410,9411,9412]],[[9413,9414,-9377,9415]],[[9416,9417,9418,9419,9420,-9381,9421]]]},{"type":"Polygon","id":48047,"arcs":[[-9349,-9382,-9421,9422,9423,-9398,-9345]]},{"type":"MultiPolygon","id":12085,"arcs":[[[9424,9425]],[[9426,9427,9428,9429,9430,-9375,-9389,9431]],[[9432,-9383]],[[9433,-9387]]]},{"type":"Polygon","id":12043,"arcs":[[9434,9435,-9364,-9376]]},{"type":"MultiPolygon","id":12015,"arcs":[[[9436,9437]],[[-9390,9438]],[[-9395,9439]],[[-9436,9440,9441,-9393,-9401]]]},{"type":"MultiPolygon","id":12099,"arcs":[[[-9425,9442]],[[9443,-9427]],[[9444,-9429]],[[9445,9446,9447,-9431]]]},{"type":"Polygon","id":12051,"arcs":[[9448,9449,9450,-9435,-9448]]},{"type":"MultiPolygon","id":12071,"arcs":[[[9451]],[[-9451,9452,9453,-9441]],[[-9438,9454]]]},{"type":"Polygon","id":48427,"arcs":[[-9424,9455,9456,-9402,-9399]]},{"type":"Polygon","id":48215,"arcs":[[-9420,9457,9458,9459,-9456,-9423]]},{"type":"MultiPolygon","id":48489,"arcs":[[[9460,9461]],[[-9404,9462]],[[9463,9464,9465,-9407,9466]],[[9467,9468,9469,9470,-9458,-9419,9471]]]},{"type":"MultiPolygon","id":12021,"arcs":[[[9472,9473]],[[9474,9475,9476,9477,-9453,-9450]]]},{"type":"MultiPolygon","id":48061,"arcs":[[[9478,-9459,-9471,9479]],[[-9461,9480]]]},{"type":"Polygon","id":12011,"arcs":[[9481,9482,-9475,-9449,-9447]]},{"type":"MultiPolygon","id":12086,"arcs":[[[9483,9484,9485,9486,9487,9488,-9476,-9483,9489]]]},{"type":"MultiPolygon","id":12087,"arcs":[[[-9487,9490]],[[-9485,9491]],[[9492]],[[9493,-9474]],[[-9489,9494,-9477]]]},{"type":"Polygon","id":4015,"arcs":[[-5753,-6696,-7333,-6549,-5937,-4613,-5352,-5426]]},{"type":"Polygon","id":12029,"arcs":[[-9116,-9203,9495,-8995,-9013]]},{"type":"Polygon","id":27077,"arcs":[[-123,-170,-106,9496]]},{"type":"Polygon","id":27031,"arcs":[[-192,9497]]},{"type":"Polygon","id":55031,"arcs":[[9498,-654,-647,-573,-462,-137,9499]]},{"type":"Polygon","id":55007,"arcs":[[9500,-652,-9499,9501]]},{"id":55003,"type":"MultiPolygon","arcs":[[[-542,-699,-648,-9501,9502]],[[9503]]]},{"id":26083,"type":"MultiPolygon","arcs":[[[9504,9505,9506]],[[9507]]]},{"id":26061,"type":"MultiPolygon","arcs":[[[-434,-572,-424,9508]],[[9509,-9506]]]},{"type":"Polygon","id":26103,"arcs":[[9510,9511,-694,-621,-567,-432,9512]]},{"type":"Polygon","id":26003,"arcs":[[-558,9513,-9511,9514,-473]]},{"type":"Polygon","id":26041,"arcs":[[-9514,-557,9515,-691,-9512]]},{"type":"Polygon","id":55075,"arcs":[[-693,9516,-855,-666,-683,-619]]},{"type":"Polygon","id":55029,"arcs":[[-1144,9517]]},{"id":26033,"type":"MultiPolygon","arcs":[[[9518,9519]],[[9520]],[[9521,9522,9523,-470,9524]]]},{"id":26097,"type":"MultiPolygon","arcs":[[[9525]],[[9526,-9522]],[[9527,-555,-471,-9524]]]},{"type":"Polygon","id":26047,"arcs":[[-749,9528,9529]]},{"id":26029,"type":"MultiPolygon","arcs":[[[-9529,-748,-928,-925,9530]],[[9531]]]},{"id":26089,"type":"MultiPolygon","arcs":[[[9532]],[[9533,-1088,9534]]]},{"type":"Polygon","id":26055,"arcs":[[-923,-1056,-1204,-1085,-9534,9535]]},{"type":"Polygon","id":26007,"arcs":[[9536,-1053,-929,-797]]},{"type":"Polygon","id":26011,"arcs":[[9537,-1413,-1374,-1214,-1212]]},{"type":"Polygon","id":26063,"arcs":[[-1552,-1530,9538]]},{"type":"Polygon","id":26147,"arcs":[[-1903,-1700,-1550,9539]]},{"type":"Polygon","id":26163,"arcs":[[9540,9541,-2106,-1904,-1901]]},{"id":26115,"type":"MultiPolygon","arcs":[[[-2482,9542]],[[9543,-2480]],[[9544,-2478,-2253,-2103,-9542]]]},{"type":"MultiPolygon","id":45019,"arcs":[[[9545,-7886,9546,-7888,-7834,-7720]],[[9547,-7615]],[[9548,-7718,-7618,9549]]]},{"type":"Polygon","id":15005,"arcs":[[9550,9551]]},{"type":"Polygon","id":15001,"arcs":[[9552]]},{"id":15007,"type":"MultiPolygon","arcs":[[[9553]],[[9554]]]},{"id":15009,"type":"MultiPolygon","arcs":[[[-9551,9555]],[[9556]],[[9557]],[[9558]]]},{"type":"Polygon","id":15003,"arcs":[[9559]]},{"type":"MultiPolygon","id":2016,"arcs":[[[9560]],[[9561]],[[9562]],[[9563]],[[9564]],[[9565]],[[9566]],[[9567]],[[9568]],[[9569]],[[9570]],[[9571]],[[9572]],[[9573]],[[9574]],[[9575]],[[9576]],[[9577]],[[9578]],[[9579]],[[9580]],[[9581]],[[9582]],[[9583]]]},{"type":"MultiPolygon","id":2013,"arcs":[[[9584]],[[9585]],[[9586]],[[9587]],[[9588]],[[9589]],[[9590]],[[9591]],[[9592]],[[9593]],[[9594]],[[9595]],[[9596,9597,9598,9599]]]},{"type":"MultiPolygon","id":2130,"arcs":[[[9600]],[[9601]]]},{"type":"Polygon","id":2060,"arcs":[[9602,9603]]},{"type":"MultiPolygon","id":2070,"arcs":[[[9604]],[[9605,9606]],[[9607,9608,9609,9610,9611,9612]]]},{"type":"MultiPolygon","id":2164,"arcs":[[[9613]],[[-9597,9614]],[[9615,9616,9617,-9599,9618,-9604,9619,-9612,9620]]]},{"type":"MultiPolygon","id":2150,"arcs":[[[9621]],[[9622]],[[9623]],[[9624]],[[9625]],[[9626]],[[9627]],[[9628]],[[9629]],[[9630]],[[9631,-9617,9632,9633]]]},{"type":"MultiPolygon","id":2110,"arcs":[[[9634,9635]],[[9636,9637]],[[9638,9639,9640,9641]],[[9642]],[[9643,9644,9645,9646,9647,9648]],[[9649,9650]]]},{"type":"MultiPolygon","id":2280,"arcs":[[[9651]],[[9652,9653]],[[9654]],[[9655]],[[9656]],[[9657]],[[9658]],[[9659]],[[9660,9661,9662,9663]]]},{"type":"MultiPolygon","id":2232,"arcs":[[[-9647,9664]],[[9665,9666,9667,9668]],[[9669]],[[-9664,9670,-9635,9671,-9645,9672]],[[-9639,9673,-9637,9674]],[[9675,9676,9677,9678,9679]],[[9680,9681]],[[-9641,9682]],[[9683,9684,9685,9686,9687]],[[9688,9689,9690,9691]]]},{"type":"MultiPolygon","id":2100,"arcs":[[[-9681,9692]],[[-9649,9693,-9650,9694,-9689,9695]],[[9696,-9687,9697,-9691]]]},{"type":"MultiPolygon","id":2220,"arcs":[[[9698]],[[9699,-9653,9700]],[[-9668,9701]],[[9702,-9666,9703,-9679,9704]]]},{"type":"MultiPolygon","id":2270,"arcs":[[[9705]],[[9706]],[[9707]],[[9708,9709,9710,9711]]]},{"type":"MultiPolygon","id":2050,"arcs":[[[9712]],[[9713]],[[9714]],[[9715]],[[9716,-9711,9717,9718,9719,-9621,-9611,9720,-9607,9721]]]},{"type":"Polygon","id":2170,"arcs":[[9722,9723,9724,9725,-9719,9726,9727,9728]]},{"type":"Polygon","id":2068,"arcs":[[9729,9730,-9728,9731]]},{"type":"MultiPolygon","id":2020,"arcs":[[[-9724,9732,9733,9734]]]},{"type":"MultiPolygon","id":2261,"arcs":[[[9735]],[[9736]],[[9737]],[[9738]],[[9739]],[[9740]],[[9741]],[[9742]],[[9743,9744]],[[9745]],[[9746]],[[9747,9748]],[[9749]],[[9750,9751]],[[9752]],[[9753,9754,-9733,-9723,9755,9756,9757,9758]]]},{"type":"MultiPolygon","id":2122,"arcs":[[[9759,-9634]],[[9760]],[[9761]],[[9762]],[[9763,-9748]],[[-9734,-9755,9764,-9751,9765,-9744,9766]],[[-9633,-9616,-9720,-9726,9767]]]},{"type":"MultiPolygon","id":2282,"arcs":[[[9768,9769,-9758,9770,-9685,9771]]]},{"type":"Polygon","id":2290,"arcs":[[9772,9773,9774,-9732,-9727,-9718,-9710,9775,9776,9777]]},{"type":"Polygon","id":2090,"arcs":[[9778,-9730,-9775]]},{"type":"Polygon","id":2240,"arcs":[[-9756,-9729,-9731,-9779,-9774,9779]]},{"type":"MultiPolygon","id":2185,"arcs":[[[9780]],[[9781,-9778,9782,9783]]]},{"type":"MultiPolygon","id":2188,"arcs":[[[-9777,9784,9785,-9783]]]},{"type":"MultiPolygon","id":2180,"arcs":[[[9786]],[[9787]],[[9788]],[[9789,-9785,-9776,-9709,9790]]]},{"id":2201,"type":"MultiPolygon","arcs":[[[9791]],[[9792]],[[9793]],[[9794]],[[9795]],[[9796]],[[9797]],[[9798]],[[9799]],[[9800]],[[9801]],[[9802]],[[9803]],[[9804]],[[9805]],[[-9662,9806]]]},{"type":"Polygon","id":72125,"arcs":[[9807,9808,9809,9810,9811,9812]]},{"type":"Polygon","id":72003,"arcs":[[9813,9814,9815,9816,9817]]},{"type":"Polygon","id":72097,"arcs":[[9818,9819,-9813,9820,9821,9822,9823]]},{"type":"Polygon","id":72065,"arcs":[[9824,9825,9826,9827,9828]]},{"type":"Polygon","id":72055,"arcs":[[9829,9830,9831,9832]]},{"type":"Polygon","id":72083,"arcs":[[9833,9834,-9819,9835,9836]]},{"type":"Polygon","id":72025,"arcs":[[9837,9838,9839,9840,9841,9842,9843]]},{"type":"Polygon","id":72045,"arcs":[[9844,9845,9846,9847,9848]]},{"type":"Polygon","id":72133,"arcs":[[9849,9850,9851,9852]]},{"type":"Polygon","id":72121,"arcs":[[-9833,9853,-9809,9854,9855]]},{"type":"Polygon","id":72027,"arcs":[[-9828,9856,9857,9858,9859]]},{"type":"Polygon","id":72033,"arcs":[[9860,9861,9862,9863]]},{"type":"Polygon","id":72001,"arcs":[[9864,9865,9866,9867,9868,9869]]},{"type":"Polygon","id":72111,"arcs":[[9870,9871,9872,-9866]]},{"type":"Polygon","id":72047,"arcs":[[9873,9874,9875,9876,9877,9878]]},{"type":"Polygon","id":72091,"arcs":[[9879,9880,9881,9882,9883,9884]]},{"type":"Polygon","id":72013,"arcs":[[9885,9886,9887,9888,-9825,9889]]},{"type":"Polygon","id":72145,"arcs":[[9890,9891,-9880,9892]]},{"type":"Polygon","id":72031,"arcs":[[9893,9894,9895,9896,9897,9898]]},{"type":"Polygon","id":72061,"arcs":[[9899,9900,9901,-9861,9902]]},{"type":"Polygon","id":72129,"arcs":[[9903,9904,9905,9906,9907,-9840,9908]]},{"type":"MultiPolygon","id":72075,"arcs":[[[9909,9910,-9853,9911,9912,9913,9914]]]},{"type":"Polygon","id":72063,"arcs":[[-9895,9915,-9909,-9839,9916]]},{"type":"Polygon","id":72073,"arcs":[[9917,-9914,9918,9919,9920]]},{"type":"Polygon","id":72143,"arcs":[[9921,9922,-9878,9923,-9891,9924]]},{"type":"Polygon","id":72011,"arcs":[[9925,-9836,-9824,9926,9927,-9815,9928]]},{"type":"Polygon","id":72081,"arcs":[[-9827,9929,-9869,9930,9931,-9834,9932,-9857]]},{"type":"Polygon","id":72015,"arcs":[[9933,9934,9935]]},{"type":"Polygon","id":72079,"arcs":[[-9854,-9832,9936,9937,-9810]]},{"type":"Polygon","id":72009,"arcs":[[9938,9939,9940,9941,9942]]},{"type":"Polygon","id":72099,"arcs":[[9943,9944,-9929,-9814,9945]]},{"type":"Polygon","id":72023,"arcs":[[9946,-9811,-9938,9947,-9822]]},{"type":"Polygon","id":72109,"arcs":[[9948,9949,9950,-9936,9951,9952,-9907]]},{"type":"Polygon","id":72101,"arcs":[[-9924,-9877,9953,9954,-9881,-9892]]},{"type":"Polygon","id":72117,"arcs":[[-9928,9955,-9816]]},{"type":"Polygon","id":72005,"arcs":[[-9946,-9818,9956,9957]]},{"type":"Polygon","id":72059,"arcs":[[-9873,9958,9959,-9867]]},{"type":"Polygon","id":72021,"arcs":[[-9902,9960,-9845,9961,9962,9963,-9862]]},{"type":"Polygon","id":72141,"arcs":[[9964,-9920,9965,-9870,-9930,-9826,-9889]]},{"type":"Polygon","id":72041,"arcs":[[-9842,9966,-9943,9967,-9847,9968]]},{"type":"Polygon","id":72123,"arcs":[[9969,9970,-9851,9971,-9940,9972]]},{"type":"Polygon","id":72131,"arcs":[[9973,-9858,-9933,-9837,-9926,-9945,9974]]},{"type":"Polygon","id":72035,"arcs":[[-9908,-9953,9975,-9973,-9939,-9967,-9841]]},{"type":"Polygon","id":72135,"arcs":[[-9963,9976,-9879,-9923,9977,9978]]},{"type":"Polygon","id":72115,"arcs":[[-9859,-9974,9979,9980]]},{"type":"Polygon","id":72054,"arcs":[[-9883,9981,-9887,9982]]},{"type":"Polygon","id":72105,"arcs":[[-9962,-9849,9983,-9874,-9977]]},{"type":"Polygon","id":72017,"arcs":[[-9884,-9983,-9886,9984]]},{"type":"Polygon","id":72127,"arcs":[[-9897,9985,-9844,9986,-9900,9987]]},{"type":"Polygon","id":72139,"arcs":[[-9896,-9917,-9838,-9986]]},{"type":"Polygon","id":72057,"arcs":[[-9952,-9935,9988,-9970,-9976]]},{"type":"Polygon","id":72153,"arcs":[[-9868,-9960,9989,-9830,-9856,9990,-9931]]},{"type":"Polygon","id":72043,"arcs":[[9991,9992,-9941,-9972,-9850,-9911,9993]]},{"type":"Polygon","id":72149,"arcs":[[-9994,-9910,9994]]},{"type":"Polygon","id":72039,"arcs":[[-9955,9995,-9921,-9965,-9888,-9982,-9882]]},{"type":"MultiPolygon","id":72113,"arcs":[[[-9913,9996,-9871,-9865,-9966,-9919]]]},{"type":"Polygon","id":72107,"arcs":[[9997,-9992,-9995,-9915,-9918,-9996,-9954,-9876]]},{"type":"Polygon","id":72067,"arcs":[[-9812,-9947,-9821]]},{"type":"Polygon","id":72071,"arcs":[[-9980,-9975,-9944,-9958,9998]]},{"type":"Polygon","id":72007,"arcs":[[-9843,-9969,-9846,-9961,-9901,-9987]]},{"type":"Polygon","id":72019,"arcs":[[-9848,-9968,-9942,-9993,-9998,-9875,-9984]]},{"type":"Polygon","id":72093,"arcs":[[-9932,-9991,-9855,-9808,-9820,-9835]]},{"type":"Polygon","id":72151,"arcs":[[9999,10000,10001,-9949,-9906,10002]]},{"type":"Polygon","id":72137,"arcs":[[-9863,-9964,-9979,10003,10004]]},{"type":"Polygon","id":78030,"arcs":[[10005]]},{"type":"Polygon","id":72089,"arcs":[[10006,10007,10008,10009]]},{"type":"Polygon","id":72087,"arcs":[[10010,10011,-9899,10012]]},{"type":"Polygon","id":72095,"arcs":[[10013,-9950,-10002]]},{"type":"Polygon","id":72119,"arcs":[[-10009,10014,10015,10016,10017,-10011,10018]]},{"type":"Polygon","id":72103,"arcs":[[10019,10020,10021,10022,-10016]]},{"type":"Polygon","id":72085,"arcs":[[-10023,10023,-10003,-9905,10024,10025,-10017]]},{"type":"Polygon","id":72029,"arcs":[[-10018,-10026,10026,-9894,-10012]]},{"type":"MultiPolygon","id":72053,"arcs":[[[10027,-10007,10028]]]},{"type":"Polygon","id":72077,"arcs":[[-10025,-9904,-9916,-10027]]},{"type":"MultiPolygon","id":72037,"arcs":[[[10029,-10020,-10015,-10008,-10028]]]},{"type":"Polygon","id":72069,"arcs":[[10030,-10000,-10024,-10022]]},{"type":"Polygon","id":72147,"arcs":[[10031]]},{"type":"Polygon","id":78010,"arcs":[[10032]]},{"type":"Polygon","id":72051,"arcs":[[-10004,-9978,-9922,10033]]}]},"states":{"type":"GeometryCollection","geometries":[{"type":"MultiPolygon","arcs":[[[6960,-6779,-6725,-6740,-6751,-6750,-6812,-6811,-6818,-6817,-6833,6996,-7015,-7019,7048,-7189,-7192,7226,-7446,7518,7519,-7600,7723,7724,-7870,7896,-8039,8080,-8132,-8178,8215,-8312,8339,8340,-8502,8502,-8602,8701,8702,8709,8710,8711,8589,8590,8704,8705,8706,8696,8697,8722,8699,8723,8724,8725,-8640,8540,-8455,8270,8271,-8148,7976,7977,-7850,7703,-7627,7474,-7447,7327,-7278,7200,-6974,-6977]],[[8693,8694]]],"id":1},{"type":"MultiPolygon","arcs":[[[9560]],[[9561]],[[9562]],[[9563]],[[9564]],[[9565]],[[9566]],[[9567]],[[9568]],[[9569]],[[9570]],[[9571]],[[9572]],[[9573]],[[9574]],[[9575]],[[9576]],[[9577]],[[9578]],[[9579]],[[9580]],[[9581]],[[9582]],[[9583]],[[9584]],[[9585]],[[9586]],[[9587]],[[9588]],[[9589]],[[9590]],[[9591]],[[9592]],[[9593]],[[9594]],[[9595]],[[9597,9618,9602,9619,9612,9607,9608,9609,9720,9605,9721,9716,9711,9790,9789,9785,9783,9781,9772,9779,9756,9770,9685,9697,9691,9695,9643,9672,9660,9806,9662,9670,9635,9671,9645,9664,9647,9693,9650,9694,9689,9696,9687,9683,9771,9768,9769,9758,9753,9764,9751,9765,9744,9766,9734,9724,9767,9759,9631,9617,9599,9614]],[[9600]],[[9601]],[[9604]],[[9613]],[[9621]],[[9622]],[[9623]],[[9624]],[[9625]],[[9626]],[[9627]],[[9628]],[[9629]],[[9630]],[[9641,9673,9637,9674,9639,9682]],[[9642]],[[9651]],[[9699,9653,9700]],[[9654]],[[9655]],[[9656]],[[9657]],[[9658]],[[9659]],[[9675,9676,9677,9704,9702,9666,9701,9668,9703,9679]],[[9669]],[[9681,9692]],[[9698]],[[9705]],[[9706]],[[9707]],[[9712]],[[9713]],[[9714]],[[9715]],[[9735]],[[9736]],[[9737]],[[9738]],[[9739]],[[9740]],[[9741]],[[9742]],[[9745]],[[9746]],[[9748,9763]],[[9749]],[[9752]],[[9760]],[[9761]],[[9762]],[[9780]],[[9786]],[[9787]],[[9788]],[[9791]],[[9792]],[[9793]],[[9794]],[[9795]],[[9796]],[[9797]],[[9798]],[[9799]],[[9800]],[[9801]],[[9802]],[[9803]],[[9804]],[[9805]]],"id":2},{"type":"MultiPolygon","arcs":[[[-5794,5794,5795,5796,-7198,7619,7620,-8060,8229,8523,8212,7750,7751,7330,7331,-6550,-6549,-5937,-4613,-5352,-5426,-5425,-4742,-4741,-4740]]],"id":4},{"type":"MultiPolygon","arcs":[[[-5831,-5946,-5945,-5944,-5957,-5956,-5955,-5732,-5915,-5914,-5913,-5942,-5941,-5888,-5999,-5998,-5997,-5996,-6190,-6303,6435,6436,6437,6438,-6633,6760,6761,6762,7036,-7048,7153,7154,-7441,-7440,7688,7689,7690,7691,7692,7802,7819,7820,7758,7759,7744,7745,7669,7670,7671,-7638,7539,-7254,-7253,7139,-6801,-6800,6753,-6638,6578,-6344,6280,6144,-5971,-5961,-5861,-5860]]],"id":5},{"type":"MultiPolygon","arcs":[[[-1578,2356,-2368,-2367,-2366,-2365,4113,4114,-4294,-4293,-4292,-3783,-4308,4555,-4763,-4239,5475,-5938,6548,6549,-7332,-7331,-7752,7765,7722,7545,7076,7041,6922,6556,5900,5619,5299,5203,5296,5447,5164,5019,4847,4539,4685,4457,4684,4459,4467,4841,4469,3604,2658,2346,-1865,-1947,-1946,-1854,-1575,-1574]],[[6919]],[[6920]],[[7040]],[[7073]],[[7074]]],"id":6},{"type":"MultiPolygon","arcs":[[[-2525,-2706,-2705,-2675,-2674,-2799,-2948,-2947,3119,-3174,3341,3342,-3598,3920,3921,-4261,4340,-4580,-4579,4881,4882,-5255,5325,5326,5327,5217,5218,5315,5316,5520,5521,5506,5507,5331,5329,-4739,-4738,-4737,4616,-4007,3557,-3077,-3076,2957,2958,-2168,-2110,-2109,-2108,-2114,-2113,-2526]]],"id":8},{"type":"MultiPolygon","arcs":[[[-2145,-1989,-1988,2290,2291,-2430,2499,2500,2532,2534,2531,2535,2520,2521,2522,-2258,-2257,-1970,-2147,-2146]]],"id":9},{"type":"MultiPolygon","arcs":[[[3730,3731]],[[3732,3733]],[[4084,4085,-4061,3736,3737,-3477,-3567,-3475,-3566,3734,4086,4378,4379,4380,4381,4382,4383,-4251]]],"id":10},{"type":"MultiPolygon","arcs":[[[4371,4372,-4099,-4281]]],"id":11},{"type":"MultiPolygon","arcs":[[[-8759,-8758,-8767,-8766,-8764,-8763,-8786,-8760,-8785,-8859,-8858,-8718,-8717,-8623,-8771,-8770,-8729,8866,8936,9043,9151,9203,9226,9216,9150,9019,8933,9020,9177,9016,9175,9217,9272,9210,9273,9212,9268,9359,9385,9433,9387,9431,9443,9427,9444,9429,9445,9481,9489,9483,9491,9485,9490,9487,9494,9477,9453,9441,9393,9439,9395,9372,9342,9340,9310,9294,9261,9258,9201,9495,8995,8896,8996,9104,9036,9103,9038,8943,9039,8945,8836,8827,8837,8829,8819,8813,-8697,-8707,-8706,-8705,-8591,-8590,-8712,-8711,-8710,-8703,-8769,-8768,-8755]],[[-8695,8810]],[[8815,8812,8816,8825]],[[8935,9021]],[[9180,9013,9178,9218]],[[9102]],[[9306,9338]],[[9361,9384]],[[9369,9396]],[[9383,9432]],[[9390,9438]],[[9425,9442]],[[9436,9454]],[[9451]],[[9472,9493]],[[9492]]],"id":12},{"type":"MultiPolygon","arcs":[[[-6945,-6944,-6943,-7081,-7080,-7277,7366,-7464,-7463,-7510,-7509,-7568,-7567,-7736,7845,-7893,7916,-7923,-8076,-8075,8304,8299,8355,8352,8546,8628,8544,8629,8727,8728,8769,8770,8622,8716,8717,8857,8858,8784,8759,8785,8762,8763,8765,8766,8757,8758,8754,8767,8768,-8702,8601,-8503,8501,-8341,-8340,8311,-8216,8177,8131,-8081,8038,-7897,7869,-7725,-7724,7599,-7520,-7519,7445,-7227,7191,7188,-7049,7018,7014,-6997,-6832,-6747,-6746,-6745,-6821,-6820,-6854,-6853,-6849,-6848,-6909,-6908,-6907,-6828,-6699,-6947,-6946]],[[8302]],[[8303]],[[8351]],[[8542,8627]],[[8545]],[[8726]]],"id":13},{"type":"MultiPolygon","arcs":[[[9551,9555]],[[9552]],[[9553]],[[9554]],[[9556]],[[9557]],[[9558]],[[9559]]],"id":15},{"type":"MultiPolygon","arcs":[[[-338,-317,-316,500,-510,-715,-714,-713,-736,-631,1095,-1149,-1148,1566,-1708,-1707,2023,2114,2078,2079,1992,1993,1868,1554,1555,-1237,-1236,-1235,1065,-945,889,-688,504,517,518,-384,-383,352,-237,-236,112,-80,21,22,19,-34,109,-191,229]]],"id":16},{"type":"MultiPolygon","arcs":[[[-1919,-1925,-1924,-1932,-1931,-2001,-2000,2087,2207,2208,2483,-2506,2748,-2801,2932,-3136,3303,3304,-3526,3705,-3900,4008,-4157,4222,-4431,-4430,4671,-4693,4908,-4932,5144,-5180,5367,5368,5370,-5502,5572,5584,5585,5578,5579,5580,-5360,-5359,-5169,5134,4937,4938,4713,4714,-4442,-4441,-4509,4365,4366,4367,4149,4052,4053,-3910,3728,3729,-3663,3495,-3471,3221,3222,-3095,2902,-2898,2740,-2691,2422,2423,2424,-2288,-2287,2203,-2133,2068,-1995,-1752,-1940,-1939,-1920]]],"id":17},{"type":"MultiPolygon","arcs":[[[-2264,-2269,2443,-2512,2593,-2690,2769,2770,-2995,3015,-3148,3271,-3385,-3384,3592,-3694,3789,-3913,-4123,4128,-4250,-4249,4410,4411,4419,4420,-4544,4647,4648,4799,4792,4793,4794,4789,4895,4896,4897,4955,4956,4925,4926,4975,4929,4930,4931,-4909,4692,-4672,4429,4430,-4223,4156,-4009,3899,-3706,3525,-3305,-3304,3135,-2933,2800,-2749,2505,-2484,-2209,2506,2508,2440,-2178,-2177,-2274,-2273,-2271,-2270,-2265]]],"id":18},{"type":"MultiPolygon","arcs":[[[-1475,-1474,-1487,-1486,-1479,-1478,-1539,1628,-1670,-1754,-1753,1994,-2069,2132,-2204,2286,2287,-2425,-2424,-2423,2690,-2741,2897,-2903,3094,3095,3055,3056,3058,3059,3070,3071,3068,3069,3065,3066,3062,3063,3052,3053,3049,3050,3046,3047,-2910,2856,-2805,2631,2632,-2516,2389,-2278,2181,-2165,2036,2037,-1806,-1805,1730,-1634,1625,-1495,-1464,-1468,-1467,-1491,-1490,-1489,-1500,-1499,-1493,-1492,-1471,-1470,-1476]]],"id":19},{"type":"MultiPolygon","arcs":[[[-3407,-3406,-3401,-3400,-3411,-3410,-3391,-3390,-3388,-3387,-3395,-3394,-3404,-3405,-3297,-3296,-3464,-3463,-3468,-3467,-3466,-3470,-3541,3642,-3743,3865,-3984,-3983,-4028,-4183,-4182,4332,-4487,4533,-4762,4814,-5045,-5044,5308,5309,-5554,5566,5567,5568,5541,5542,5533,5534,5604,5605,5455,5456,5458,5459,5536,5537,5468,5469,5538,5539,5465,5466,5462,5530,5531,5529,5526,5527,-5326,5254,-4883,-4882,4578,4579,-4341,4260,-3922,-3921,3597,-3343,-3398,-3397,-3409]]],"id":20},{"type":"MultiPolygon","arcs":[[[-4120,-4145,-4144,-4143,-4166,-4165,-4337,-4336,-4359,-4358,-4484,-4483,4732,-4799,-4798,-5111,-5110,5243,5244,5245,5624,-5656,5742,5844,5845,5834,5835,5842,5843,5822,5823,5909,5910,5853,5938,5939,5856,5857,5918,5919,5712,5714,5715,5675,5676,5753,5962,5963,5849,5850,5957,5958,5983,5984,-5924,-5725,-5724,-5723,5645,-5579,-5586,-5585,-5573,5501,-5371,-5369,-5368,5179,-5145,-4931,-4930,-4976,-4927,-4926,-4957,-4956,-4898,-4897,-4896,-4790,-4795,-4794,-4793,-4800,-4649,-4648,4543,-4421,-4420,-4412,-4411,4248,4249,-4129,-4122,-4121]],[[5982,-5926]]],"id":21},{"type":"MultiPolygon","arcs":[[[7929,7930,7931,-7844,-7671,-7670,-7746,-7745,-7760,-7759,-7821,-7820,-7803,-7693,-7692,-7691,-7950,7965,-8130,-8129,-8128,-8127,8293,8294,8295,8510,8511,-8667,-8666,-8680,-8679,-8678,-8683,-8682,-8684,-8652,-8794,-8793,8877,8878,9040,9067,9060,9061,9062,9063,9135,9065,9140,9136,9137,9138,9139,9033,9128,9118,9119,9120,9030,9121,9122,9160,9124,9161,9126,9168,9163,9164,9165,9166,9167,9109,9088,9076,9099,-9047,-9023,8972,-8714,-8713,8669,8475,8476,-8425,8258,-8244]],[[9133,9134],[-9057],[-9060]],[[9085]],[[9117,9159]],[[9132]],[[9162]]],"id":22},{"type":"MultiPolygon","arcs":[[[895,578,1091,1255,1259,1365,1363,1364,1261,1108,1366,1353,1362,1355,1511,1512,-1273,864,865,866,795,548,348,791]],[[893]],[[894]]],"id":23},{"type":"MultiPolygon","arcs":[[[-3480,-3479,-3427,-3426,-3478,-3738,-3737,4060,-4086,-4085,4250,-4384,-4383,-4382,4778,4774,4775,4776,4860,4859,4678,4569,4567,4252,4394,4153,4062,3805,3815,3821,4072,4177,4071,4178,4174,4509,4277,4601,4719,4718,4603,4279,4280,4098,4099,4100,3830,3792,3793,3794,3795,3797,3798,3799,3801,3802,3803,-3527,-3439,-3438,-3423,-3517,-3516,-3436,-3435,-3564,-3563,-3481]],[[4150]],[[4771,4772]],[[-4380,4773]],[[4850,4851]],[[4852,4853,4854,4855]],[[4856,4857,4858]]],"id":24},{"type":"MultiPolygon","arcs":[[[1977,2092,1979,2089,2140,2151,2138,2147,2261,2260,2149,2221,2222,2223,2224,2225,2143,1986,1987,1988,2144,2145,2146,1969,1970,1971,-1859,-1711,-1710,-1728,-1780,-1779,-1757,-1756,-1755,-1723,1906,2093]],[[2604]],[[2700]]],"id":25},{"type":"MultiPolygon","arcs":[[[464,465,466,422,9508,434,9512,9514,473,9524,9526,9522,9527,555,9515,691,692,618,619,568,569,570]],[[9536,1049,1210,9537,1408,1534,9538,1552,9539,1899,9540,9544,-2478,2253,2254,2266,2267,2268,2263,2264,2269,2270,2272,2273,2176,2177,2178,2124,1960,1759,1660,1507,1351,1199,1086,9534,9535,923,9530,9529,749,798]],[[9506,9504,9509]],[[9507]],[[9518,9519]],[[9520]],[[9525]],[[9531]],[[9532]],[[-2482,9542]],[[9543,-2480]]],"id":26},{"type":"MultiPolygon","arcs":[[[9496,123,140,193,9497,192,135,136,461,572,573,-646,772,-778,876,877,1025,-1049,1113,-1137,-1162,-1161,-1165,1343,-1397,1476,1477,1478,1485,1486,1473,1474,1475,1469,1470,1491,1492,1498,1499,1488,1489,1490,1466,1467,1463,1464,1309,-1194,1154,-1019,-1018,892,-868,817,-724,681,-533,528,403,-392,336,-306,207,-199,153,154,-53,58,104]]],"id":27},{"type":"MultiPolygon","arcs":[[[-6767,-6799,-6781,-6780,-6961,6976,6973,-7201,7277,-7328,7446,-7475,7626,-7704,7849,-7978,-7977,8147,-8272,-8271,8454,-8541,8639,-8726,-8725,8869,8892,8910,-8878,8792,8793,8651,8683,8681,8682,8677,8678,8679,8665,8666,-8512,-8511,-8296,-8295,-8294,8126,8127,8128,8129,-7966,7949,-7690,-7689,7439,7440,-7155,-7154,7047,-7037,-6763,-6762,-6794,-6793,-6798,-6797,-6769,-6768]]],"id":28},{"type":"MultiPolygon","arcs":[[[-3496,3662,-3730,-3729,3909,-4054,-4053,-4150,-4368,-4367,-4366,4508,4440,4441,-4715,-4714,-4939,-4938,-5135,5168,5358,5359,-5581,-5580,-5646,5722,5723,5724,5923,5924,5925,5926,-6128,6188,6189,5995,5996,5997,5998,5887,5940,5941,5912,5913,5914,5731,5954,5955,5956,5943,5944,5945,5830,5859,5860,5960,5961,-5781,5735,-5567,5553,-5310,-5309,5043,5044,-4815,4761,-4534,4486,-4333,4181,4182,4027,3982,3983,-3866,3742,-3643,3540,3469,-3465,-3284,3253,-3109,-3047,-3051,-3050,-3054,-3053,-3064,-3063,-3067,-3066,-3070,-3069,-3072,-3071,-3060,-3059,-3057,-3056,-3096,-3223,-3222,3470]]],"id":29},{"type":"MultiPolygon","arcs":[[[108,-91,106,-131,210,-220,361,-369,496,497,498,654,655,656,741,742,743,676,677,804,805,634,628,629,630,735,712,713,714,509,-501,315,316,337,-230,190,-110,33,-20,34,18,41,97,86,44,48,27,8,51]]],"id":30},{"type":"MultiPolygon","arcs":[[[1833,-1618,-1658,-1657,-1548,-1547,-1683,-1684,-1526,-1656,-1655,-1646,-1645,-1785,-1783,-1782,-1811,-1810,-1808,-1807,-2038,-2037,2164,-2182,2277,-2390,2515,-2633,-2632,2804,-2857,2909,-3048,3108,-3254,3283,3464,3465,3466,3467,3462,3463,3295,3296,3404,3403,3393,3394,3386,3387,3389,3390,3409,3410,3399,3400,3405,3406,3408,3396,3397,-3342,3173,-3120,2946,2947,2798,2673,2674,2704,2705,-2524,2514,-2019,-2018]]],"id":31},{"type":"MultiPolygon","arcs":[[[-1402,-1238,-1556,-1555,-1869,-1994,-2330,2334,-2897,3541,3542,-3979,4609,4610,4611,4612,5936,5937,-5476,4238,4762,-4556,4307,3782,4291,4292,4293,-4115,-4114,2364,2365,2366,2367,-2357,-1577,-1403]]],"id":32},{"type":"MultiPolygon","arcs":[[[-1513,1589,1721,1722,1754,1755,1756,1778,1779,-1727,1588,-1430,1243,1244,-1089,-971,874,875,-866,-865,1272]]],"id":33},{"type":"MultiPolygon","arcs":[[[-2743,2873,3093,3043,3136,3236,3325,3511,3501,3786,4108,3927,3924,3765,3925,3768,-3732,3769,-3734,3770,3704,3646,3504,3505,-3234,-3233,3104,2887,2888,-2790,2730,-2575,-2542,-2541,-2744]]],"id":34},{"type":"MultiPolygon","arcs":[[[-5316,-5219,-5218,-5328,5799,5800,5801,-6401,6582,6583,-6887,7029,7030,7177,7178,-7589,7679,7680,7681,7682,7683,7989,7990,7991,7808,7809,7810,7913,7914,8132,8058,8059,-7621,-7620,7197,-5797,-5796,-5795,5793,-5330,-5332,-5508,-5507,-5522,-5521,-5317]]],"id":35},{"type":"MultiPolygon","arcs":[[[1178,-1269,-1459,1518,-1712,1858,-1972,-1971,2256,2257,-2523,-2522,2726,2723,3022,3074,3024,2725,2601,2744,2742,2743,2540,2541,2542,2297,2298,2064,2126,2127,2129,2130,2162,2163,2027,2028,2059,2060,2056,2057,2031,2032,2033,1803,1690,1687,1695,1698,1675,1544,1252,1539,1254,1249,994,1006,980,-976,977,-1106]],[[1799]],[[2756,3028]],[[3031,3099,3132,3101,3033,2758,3029,3096]],[[3098,3131]],[[3217]]],"id":36},{"type":"MultiPolygon","arcs":[[[-5951,-5875,-5923,-5922,-5935,-5934,-5691,-5690,-5989,-5688,-5730,-5729,-5728,-5909,-5908,-5907,-5741,-5740,-5902,-5820,-5819,-5818,-5897,-5896,-5933,-5932,-5870,6067,6065,6075,6114,6205,6220,6063,6084,6272,6460,6454,6611,6285,6612,6592,6521,6588,6830,6787,6857,6785,6916,7011,7133,7294,7132,7295,7304,7305,7306,7307,7273,7026,7027,7028,6951,6900,6871,6872,6876,6877,6702,6703,6782,6672,6673,6657,6658,6794,6795,6721,6775,6776,6777,6697,6698,6827,6906,6907,6908,6847,6848,6849,-6624,-6623,-6487,-6486,-6422,-6421,-6315,-6314,-6201,-6258,-6257,-6256,-6112,-6111,-6012,-6011,-6010,-5953,-5952]],[[6070,-5866,6068,6286]],[[-6072,5863,-6073,5867]],[[6283,6281,6610]],[[7293,7303]],[[7301,7302]]],"id":37},{"type":"MultiPolygon","arcs":[[[30,62,66,55,52,-155,-154,198,-208,305,-337,391,-404,-529,532,533,606,607,612,613,610,611,512,564,602,603,604,614,-498,-497,368,-362,219,-211,130,-107,90,91,96,101,38]]],"id":38},{"type":"MultiPolygon","arcs":[[[-2648,2874,-2882,3009,3010,-3226,3242,3243,-3500,3508,-3587,3715,3716,3874,3875,3876,3973,-4051,4212,4210,4348,4349,4480,4481,4482,4483,4357,4358,4335,4336,4164,4165,4142,4143,4144,4119,4120,4121,4122,3912,-3790,3693,-3593,3383,3384,-3272,3147,-3016,2994,-2771,-2770,2689,-2594,2511,-2444,-2268,-2267,-2255,-2254,2477,2478,2479,2480,2481,2482,2487,2641,2489,2642,2567,2608,2553,2416,2374,-2174,2370,-2420,2638]]],"id":39},{"type":"MultiPolygon","arcs":[[[-5569,-5568,-5736,5780,-5962,5970,-6145,-6281,6343,-6579,6637,-6754,6799,6800,-7140,7252,7253,7254,7255,7427,7428,7424,7425,7426,7406,7466,7467,7468,7364,7365,7250,7251,7160,7161,7055,7056,6953,6954,-6894,6717,-6654,6428,-6395,6041,6042,5813,5814,5809,5810,5811,5806,5807,-5800,-5327,-5528,-5527,-5530,-5532,-5531,-5463,-5467,-5466,-5540,-5539,-5470,-5469,-5538,-5537,-5460,-5459,-5457,-5456,-5606,-5605,-5535,-5534,-5543,-5542]]],"id":40},{"type":"MultiPolygon","arcs":[[[637,-670,-583,-582,-675,-674,-673,-672,-671,-485,-484,-539,-537,-536,-495,-564,-518,-505,687,-890,944,-1066,1234,1235,1236,1237,1401,1402,1576,1577,1573,1574,1853,1945,1946,1864,1865,1585,1438,1271,955,754,601,640,-587]]],"id":41},{"type":"MultiPolygon","arcs":[[[-2057,-2061,-2060,-2029,-2028,-2164,-2163,-2131,-2130,-2128,-2127,-2065,-2299,-2298,-2543,2574,-2731,2789,-2889,-2888,-3105,3232,3233,-3506,3533,3564,3565,3474,3566,3476,3477,3425,3426,3478,3479,3480,3562,3563,3434,3435,3515,3516,3422,3437,3438,3526,3527,3528,3588,3589,-3588,3317,3318,3319,-3224,3083,-3010,2881,-2875,2647,-2639,2419,-2371,2173,2174,-2033,-2032,-2058]]],"id":42},{"type":"MultiPolygon","arcs":[[[2425,2294,2427,2526,-2500,2429,-2292,-2291,-1987,-2144,-2226,-2225]],[[2517]],[[2518,-2223]]],"id":44},{"type":"MultiPolygon","arcs":[[[-6658,-6674,-6673,-6783,-6704,-6703,-6878,-6877,-6873,-6872,-6901,-6952,-7029,-7028,-7027,-7274,-7308,7355,7616,9549,9548,7718,9545,7886,9546,7888,8102,8067,8099,8069,8098,8071,8097,8073,8074,8075,7922,-7917,7892,-7846,7735,7566,7567,7508,7509,7462,7463,-7367,7276,7079,7080,6942,6943,6944,6945,6946,-6698,-6778,-6777,-6776,-6722,-6796,-6795,-6659]],[[-7302,7353]],[[-7306,7354]],[[7615,9547]],[[8096]],[[8100]],[[8101]]],"id":45},{"type":"MultiPolygon","arcs":[[[-513,-612,-611,-614,-613,-608,-607,-534,-682,723,-818,867,-893,1017,1018,-1155,1193,-1310,-1465,1494,-1626,1633,-1731,1804,1805,1806,1807,1809,1810,1781,1782,1784,1644,1645,1654,1655,1525,1683,1682,1546,1547,1656,1657,-1617,1461,-1347,1222,1158,-1008,914,-656,-655,-499,-615,-605,-604,-603,-565]]],"id":46},{"type":"MultiPolygon","arcs":[[[-5715,-5713,-5920,-5919,-5858,-5857,-5940,-5939,-5854,-5911,-5910,-5824,-5823,-5844,-5843,-5836,-5835,-5846,-5906,-5905,-5918,-5917,-5916,-5881,-5994,-5885,-5884,-5954,6009,6010,6011,6110,6111,6255,6256,6257,6200,6313,6314,6420,6421,6485,6486,6622,6623,-6850,6852,6853,6819,6820,6744,6745,6746,6831,6832,6816,6817,6810,6811,6749,6750,6739,6724,6778,6779,6780,6798,6766,6767,6768,6796,6797,6792,6793,-6761,6632,-6439,-6438,-6437,-6436,6302,-6189,6127,-5927,-5983,-5925,-5985,-5984,-5959,-5958,-5851,-5850,-5964,-5963,-5754,-5677,-5676,-5716]]],"id":47},{"type":"MultiPolygon","arcs":[[[-5808,-5807,-5812,-5811,-5810,-5815,-5814,-6043,-6042,6394,-6429,6653,-6718,6893,-6955,-6954,-7057,-7056,-7162,-7161,-7252,-7251,-7366,-7365,-7469,-7468,-7467,-7407,-7427,-7426,-7425,-7429,-7428,-7256,-7255,-7540,7637,-7672,7843,-7932,-7931,-7930,8243,-8259,8424,-8477,-8476,-8670,8712,8713,-8973,9022,9046,9047,9142,9195,9144,9052,9194,9192,9196,9241,9239,9286,9229,9290,9289,9231,9245,9233,9246,9288,9300,9326,9282,9280,9327,9325,9302,9320,9332,9323,9333,9353,9335,9356,9354,9379,9421,9416,9417,9471,9467,9468,9469,9479,9478,9459,9456,9402,9331,9257,9190,9003,8903,8900,8913,8411,8413,-7914,-7811,-7810,-7809,-7992,-7991,-7990,-7684,-7683,-7682,-7681,-7680,7588,-7179,-7178,-7031,-7030,6886,-6584,-6583,6400,-5802,-5801]],[[9050,9141]],[[9191]],[[9285,9283,9324]],[[9318,9352]],[[9319]],[[9413,9414,9377,9350,9378,9415]],[[9351]],[[9404,9462]],[[9463,9464,9465,9407,9408,9405,9466]],[[9409,9410,9411,9412]],[[9461,9480]]],"id":48},{"type":"MultiPolygon","arcs":[[[-1706,2316,-2593,-2170,-2169,-2959,-2958,3075,3076,-3558,4006,-4617,4736,4737,4738,4739,4740,4741,5424,5425,5351,-4612,-4611,-4610,3978,-3543,-3542,2896,-2335,2329,-1993,-2080,-2079,-2115,-2024]]],"id":49},{"type":"MultiPolygon","arcs":[[[973,-875,970,1088,-1245,-1244,1429,-1589,1726,1727,1709,1710,1711,-1519,1458,1268,-1179,1105,-978,975,976,969,983]]],"id":50},{"type":"MultiPolygon","arcs":[[[-4005,-3793,-3831,-4101,-4100,-4373,4405,4487,4326,4395,4657,4804,4869,5072,5198,5197,5006,4868,4801,4983,5235,5379,5433,5381,5117,5157,5338,5483,5546,5671,5669,5699,5651,5482,5440,5292,5403,5589,5593,5639,5682,5897,5893,5929,5890,5839,5930,5871,5841,5872,5865,5866,5867,5868,5869,5931,5932,5895,5896,5817,5818,5819,5901,5739,5740,5906,5907,5908,5727,5728,5729,5687,5988,5689,5690,5933,5934,5921,5922,5874,5950,5951,5952,5953,5883,5884,5993,5880,5915,5916,5917,5904,5905,-5845,-5743,5655,-5625,-5246,-5245,-5244,-5109,-5421,-5420,-5387,-5386,-5385,-5189,-5285,-5284,5130,-4886,4880,-4523,-4522,-4389,-4388,-4387,-4173,-4172,4024,-3980,-3846,-3891,-4006],[5598]],[[5060,-4854]],[[5061,-4858]],[[-4851,5062]],[[5065,5066,-4776,5067,5063,5414]],[[-4772,5068]],[[5862,5863,5864]]],"id":51},{"type":"MultiPolygon","arcs":[[[83,78,81,-22,79,-113,235,236,-353,382,383,-519,563,494,535,536,538,483,484,670,671,672,673,674,581,582,669,-638,586,587,589,455,331,222,176,223,307,265,359,262,360,309,395,358,295,185,173,186,127,124,2,73]],[[116]],[[117]],[[118]],[[174]],[[260]],[[292]]],"id":53},{"type":"MultiPolygon","arcs":[[[-3529,-3528,-3804,-3803,-3802,-3800,-3799,-3798,-3796,-3795,-3794,4004,4005,3890,3845,3979,-4025,4171,4172,4386,4387,4388,4521,4522,-4881,4885,-5131,5283,5284,5188,5384,5385,5386,5419,5420,5108,5109,5110,4797,4798,-4733,-4482,-4481,-4350,-4349,-4211,-4213,4050,-3974,-3877,-3876,-3875,-3717,-3716,3586,-3509,3499,-3244,-3243,3225,-3011,-3084,3223,-3320,-3319,-3318,3587,-3590,-3589]]],"id":54},{"type":"MultiPolygon","arcs":[[[-570,-569,-620,-619,-693,9516,855,1129,857,1130,1142,9517,1140,1262,1448,1597,1773,1932,1998,1999,2000,1930,1931,1923,1924,1918,1919,1938,1939,1751,1752,1753,1669,-1629,1538,-1477,1396,-1344,1164,1160,1161,1136,-1114,1048,-1026,-878,-877,777,-773,645,-574,-573,-462,-137,9499,9501,9502,542,-466,-465,-571]],[[9503]]],"id":55},{"type":"MultiPolygon","arcs":[[[-677,-744,-743,-742,-657,-915,1007,-1159,-1223,1346,-1462,1616,1617,-1834,2017,2018,-2515,2523,2524,2525,2112,2113,2107,2108,2109,2167,2168,2169,2592,-2317,1705,1706,1707,-1567,1147,1148,-1096,-630,-629,-635,-806,-805,-678]]],"id":56},{"type":"MultiPolygon","arcs":[[[9947,9822,9926,9955,9816,9956,9998,9980,9859,9828,9889,9984,9884,9892,9924,10033,10004,9863,9902,9987,9897,10012,10018,10009,10028,10029,10020,10030,10000,10013,9950,9933,9988,9970,9851,9911,9996,9871,9958,9989,9830,9936]],[[10031]]],"id":72},{"type":"MultiPolygon","arcs":[[[10005]],[[10032]]],"id":78}]},"land":{"type":"MultiPolygon","arcs":[[[5868,6067,6065,6075,6114,6205,6220,6063,6084,6272,6460,6454,6611,6285,6612,6592,6521,6588,6830,6787,6857,6785,6916,7011,7133,7294,7132,7295,7304,7354,7306,7355,7616,9549,9548,7718,9545,7886,9546,7888,8102,8067,8099,8069,8098,8071,8097,8073,8304,8299,8355,8352,8546,8628,8544,8629,8727,8866,8936,9043,9151,9203,9226,9216,9150,9019,8933,9020,9177,9016,9175,9217,9272,9210,9273,9212,9268,9359,9385,9433,9387,9431,9443,9427,9444,9429,9445,9481,9489,9483,9491,9485,9490,9487,9494,9477,9453,9441,9393,9439,9395,9372,9342,9340,9310,9294,9261,9258,9201,9495,8995,8896,8996,9104,9036,9103,9038,8943,9039,8945,8836,8827,8837,8829,8819,8813,8697,8722,8699,8723,8869,8892,8910,8878,9040,9067,9060,9061,9062,9063,9135,9065,9140,9136,9137,9138,9139,9033,9128,9118,9119,9120,9030,9121,9122,9160,9124,9161,9126,9168,9163,9164,9165,9166,9167,9109,9088,9076,9099,9047,9142,9195,9144,9052,9194,9192,9196,9241,9239,9286,9229,9290,9289,9231,9245,9233,9246,9288,9300,9326,9282,9280,9327,9325,9302,9320,9332,9323,9333,9353,9335,9356,9354,9379,9421,9416,9417,9471,9467,9468,9469,9479,9478,9459,9456,9402,9331,9257,9190,9003,8903,8900,8913,8411,8413,7914,8132,8058,8229,8523,8212,7750,7765,7722,7545,7076,7041,6922,6556,5900,5619,5299,5203,5296,5447,5164,5019,4847,4539,4685,4457,4684,4459,4467,4841,4469,3604,2658,2346,1865,1585,1438,1271,955,754,601,640,587,589,455,331,222,176,223,307,265,359,262,360,309,395,358,295,185,173,186,127,124,2,73,83,78,81,22,34,18,41,97,86,44,48,27,8,51,108,91,96,101,38,30,62,66,55,58,104,9496,123,140,193,9497,192,135,9499,9501,9502,542,466,422,9508,434,9512,9514,473,9524,9526,9522,9527,555,9515,691,9516,855,1129,857,1130,1142,9517,1140,1262,1448,1597,1773,1932,1998,2087,2207,2506,2508,2440,2178,2124,1960,1759,1660,1507,1351,1199,1086,9534,9535,923,9530,9529,749,798,9536,1049,1210,9537,1408,1534,9538,1552,9539,1899,9540,9544,2478,9543,2480,9542,2482,2487,2641,2489,2642,2567,2608,2553,2416,2374,2174,2033,1803,1690,1687,1695,1698,1675,1544,1252,1539,1254,1249,994,1006,980,976,969,983,973,875,866,795,548,348,791,895,578,1091,1255,1259,1365,1363,1364,1261,1108,1366,1353,1362,1355,1511,1589,1721,1906,2093,1977,2092,1979,2089,2140,2151,2138,2147,2261,2260,2149,2221,2518,2223,2425,2294,2427,2526,2500,2532,2534,2531,2535,2520,2726,2723,3022,3074,3024,2725,2601,2744,2873,3093,3043,3136,3236,3325,3511,3501,3786,4108,3927,3924,3765,3925,3768,3730,3769,3732,3770,3704,3646,3504,3533,3564,3734,4086,4378,4773,4380,4778,4774,5067,5063,5414,5065,5066,4776,4860,4859,4678,4569,4567,4252,4394,4153,4062,3805,3815,3821,4072,4177,4071,4178,4174,4509,4277,4601,4719,4718,4603,4279,4371,4405,4487,4326,4395,4657,4804,4869,5072,5198,5197,5006,4868,4801,4983,5235,5379,5433,5381,5117,5157,5338,5483,5546,5671,5669,5699,5651,5482,5440,5292,5403,5589,5593,5639,5682,5897,5893,5929,5890,5839,5930,5871,5841,5872,6068,6286,6070,5866],[5598],[5864,5862],[-6072],[-6073]],[[8693,8810]],[[9560]],[[9561]],[[9562]],[[9563]],[[9564]],[[9565]],[[9566]],[[9567]],[[9568]],[[9569]],[[9570]],[[9571]],[[9572]],[[9573]],[[9574]],[[9575]],[[9576]],[[9577]],[[9578]],[[9579]],[[9580]],[[9581]],[[9582]],[[9583]],[[9584]],[[9585]],[[9586]],[[9587]],[[9588]],[[9589]],[[9590]],[[9591]],[[9592]],[[9593]],[[9594]],[[9595]],[[9597,9618,9602,9619,9612,9607,9608,9609,9720,9605,9721,9716,9711,9790,9789,9785,9783,9781,9772,9779,9756,9770,9685,9697,9691,9695,9643,9672,9660,9806,9662,9670,9635,9671,9645,9664,9647,9693,9650,9694,9689,9696,9687,9683,9771,9768,9769,9758,9753,9764,9751,9765,9744,9766,9734,9724,9767,9759,9631,9617,9599,9614]],[[9600]],[[9601]],[[9604]],[[9613]],[[9621]],[[9622]],[[9623]],[[9624]],[[9625]],[[9626]],[[9627]],[[9628]],[[9629]],[[9630]],[[9641,9673,9637,9674,9639,9682]],[[9642]],[[9651]],[[9699,9653,9700]],[[9654]],[[9655]],[[9656]],[[9657]],[[9658]],[[9659]],[[9675,9676,9677,9704,9702,9666,9701,9668,9703,9679]],[[9669]],[[9681,9692]],[[9698]],[[9705]],[[9706]],[[9707]],[[9712]],[[9713]],[[9714]],[[9715]],[[9735]],[[9736]],[[9737]],[[9738]],[[9739]],[[9740]],[[9741]],[[9742]],[[9745]],[[9746]],[[9748,9763]],[[9749]],[[9752]],[[9760]],[[9761]],[[9762]],[[9780]],[[9786]],[[9787]],[[9788]],[[9791]],[[9792]],[[9793]],[[9794]],[[9795]],[[9796]],[[9797]],[[9798]],[[9799]],[[9800]],[[9801]],[[9802]],[[9803]],[[9804]],[[9805]],[[6919]],[[6920]],[[7040]],[[7073]],[[7074]],[[8815,8812,8816,8825]],[[8935,9021]],[[9180,9013,9178,9218]],[[9102]],[[9306,9338]],[[9361,9384]],[[9369,9396]],[[9383,9432]],[[9390,9438]],[[9425,9442]],[[9436,9454]],[[9451]],[[9472,9493]],[[9492]],[[8302]],[[8303]],[[8351]],[[8542,8627]],[[8545]],[[8726]],[[9551,9555]],[[9552]],[[9553]],[[9554]],[[9556]],[[9557]],[[9558]],[[9559]],[[-9060,-9057],[9133],[9134]],[[9085]],[[9117,9159]],[[9132]],[[9162]],[[893]],[[894]],[[4150]],[[4772,5068]],[[4851,5062]],[[4854,4855,4852,5060]],[[4858,4856,5061]],[[2604]],[[2700]],[[9506,9504,9509]],[[9507]],[[9518,9519]],[[9520]],[[9525]],[[9531]],[[9532]],[[1799]],[[2756,3028]],[[3031,3099,3132,3101,3033,2758,3029,3096]],[[3098,3131]],[[3217]],[[6283,6281,6610]],[[7293,7303]],[[7302,7353]],[[2517]],[[7615,9547]],[[8096]],[[8100]],[[8101]],[[9050,9141]],[[9191]],[[9285,9283,9324]],[[9318,9352]],[[9319]],[[9413,9414,9377,9350,9378,9415]],[[9351]],[[9404,9462]],[[9463,9464,9465,9407,9408,9405,9466]],[[9409,9410,9411,9412]],[[9461,9480]],[[116]],[[117]],[[118]],[[174]],[[260]],[[292]],[[9503]],[[9947,9822,9926,9955,9816,9956,9998,9980,9859,9828,9889,9984,9884,9892,9924,10033,10004,9863,9902,9987,9897,10012,10018,10009,10028,10029,10020,10030,10000,10013,9950,9933,9988,9970,9851,9911,9996,9871,9958,9989,9830,9936]],[[10031]],[[10005]],[[10032]]]}},"arcs":[[[162416,583189],[236,-863],[95,-3199],[219,-1079],[-271,-1241]],[[162695,576807],[-442,-309],[-4397,83]],[[157856,576581],[-6,1800],[-436,606],[-476,3062],[168,1173],[2781,-89],[2529,56]],[[203483,583173],[-111,-3268],[363,0],[1,-4838],[604,-14]],[[204340,575053],[0,-6406],[-109,-3],[-1,-3886]],[[204230,564758],[-1074,300],[-477,661],[-50,-1946]],[[202629,563773],[-411,-2654],[-860,-2419],[-1043,-435],[-462,561]],[[199853,558826],[29,9804],[98,1617],[303,-7],[-114,3279],[302,529],[-36,7508],[73,1628]],[[200508,583184],[2975,-11]],[[181317,583162],[311,-3373],[318,707],[389,-2530],[-133,-1970],[788,-1235],[-38,-1636],[347,-662],[20,-2123]],[[183319,570340],[343,-1416],[-25,-1101],[608,-791]],[[184245,567032],[386,-2907],[-296,-415]],[[184335,563710],[-284,-667],[56,-2638],[-277,-1038],[59,-2315]],[[183889,557052],[-896,67]],[[182993,557119],[-467,-2]],[[182526,557117],[100,1562],[-233,2072],[31,1860],[-477,1550],[-122,1357],[-537,-515],[111,-1134],[-1263,-12],[41,-3203],[-354,-5]],[[179823,560649],[-1,1601],[-1073,-52],[-59,2655]],[[178690,564853],[-26,3920],[480,4],[-5,6467],[-109,1580],[659,10],[112,1730],[-222,1681],[-105,2947]],[[179474,583192],[1843,-30]],[[175797,583199],[0,-9287]],[[175797,573912],[-2052,-27],[-1,6456],[-689,-16]],[[173055,580325],[0,2843]],[[173055,583168],[2742,31]],[[199853,558826],[-98,-1296],[-681,144],[-597,-3335]],[[198477,554339],[-62,2205],[-374,544],[-689,-277]],[[197352,556811],[-1279,1156],[-333,1654]],[[195740,559621],[8,3507],[732,-36],[49,1308],[485,-270],[63,8718],[182,-420],[75,5957],[166,-2],[64,4793]],[[197564,583176],[2944,8]],[[221924,574709],[-1819,8]],[[220105,574717],[-103,3252],[0,5201]],[[220002,583170],[1831,2]],[[221833,583172],[2,-5232],[89,-3231]],[[178690,564853],[-400,43],[-118,-1893],[-228,-480],[-676,326],[-47,1381],[-616,5246],[-698,-1924],[-112,1019]],[[175795,568571],[2,5341]],[[175797,583199],[3677,-7]],[[220105,574717],[-364,-10]],[[219741,574707],[-362,-1],[-1,1624],[-1819,10],[0,-1619]],[[217559,574721],[-1091,11],[0,3239],[-126,10],[-1,5192]],[[216341,583173],[3661,-3]],[[186539,583158],[26,-9701]],[[186565,573457],[-1094,108],[-10,-3230],[-2142,5]],[[181317,583162],[5222,-4]],[[193948,567081],[-537,-46],[0,1653],[-331,-43],[0,1617],[-2172,10],[0,-1617],[-363,1]],[[190545,568656],[35,14501]],[[190580,583157],[3492,32]],[[194072,583189],[-44,-7981],[115,-2218],[-242,-2960],[47,-2949]],[[195740,559621],[-133,1040],[-934,56],[-434,-1362],[-302,350]],[[193937,559705],[11,7376]],[[194072,583189],[3492,-13]],[[206421,583169],[53,-6498],[181,-1619]],[[206655,575052],[-2315,1]],[[203483,583173],[2938,-4]],[[228232,583191],[206,-4516],[166,-1312],[-187,-2686]],[[228417,574677],[-2130,0]],[[226287,574677],[-64,8514]],[[226223,583191],[2009,0]],[[230526,583184],[49,-8486]],[[230575,574698],[-2158,-21]],[[228232,583191],[2294,-7]],[[223380,574698],[-1,-3220],[-639,-11]],[[222740,571467],[-816,5]],[[221924,571472],[0,3237]],[[221833,583172],[1465,10]],[[223298,583182],[0,-5247],[82,-3237]],[[226287,574677],[-1090,12]],[[225197,574689],[-1817,9]],[[223298,583182],[2925,9]],[[168030,583185],[-42,-19407]],[[167988,563778],[-338,-262]],[[167650,563516],[-25,343]],[[167625,563859],[88,1056],[-308,2218],[-250,148],[-799,-1281],[-586,-1450],[-104,1726],[-570,-649],[55,-1799]],[[165151,563828],[-497,4],[-263,1945],[-611,1931],[-82,1381],[-532,1417],[-331,3957]],[[162835,574463],[-140,2344]],[[162416,583189],[2209,-15],[3405,11]],[[171951,583192],[-373,-3049],[-183,-3367],[361,7],[-7,-9706],[182,-1632]],[[171931,565445],[-274,12],[-1,-4693],[-435,1680],[-360,-1132]],[[170861,561312],[-166,418],[-548,-890],[-371,2707],[-364,-947]],[[169412,562600],[-132,2358],[406,757],[-5,1210],[289,2381],[-186,3288],[316,4420],[-332,3951],[45,2219]],[[169813,583184],[2138,8]],[[173055,580325],[-26,-14911]],[[173029,565414],[-1098,31]],[[171951,583192],[1104,-24]],[[169412,562600],[-45,-983],[-485,620],[-109,1069],[-446,-598],[-339,1070]],[[168030,583185],[1783,-1]],[[188725,567028],[0,1624]],[[188725,568652],[374,0],[11,14480]],[[189110,583132],[1470,25]],[[190545,568656],[-199,-1611],[-1621,-17]],[[212324,583170],[-3,-5175],[150,-1639]],[[212471,576356],[-3238,15]],[[209233,576371],[-1,6810]],[[209232,583181],[3092,-11]],[[214880,583162],[-1,-3570]],[[214879,579592],[-364,-1],[-1,-1615],[-226,0],[0,-3239]],[[214288,574737],[-1816,1]],[[212472,574738],[-1,1618]],[[212324,583170],[2556,-8]],[[186539,583158],[2571,-26]],[[188725,568652],[-718,-1],[2,2422],[-362,804],[-543,30],[-81,1075],[-458,475]],[[217559,574721],[0,-1621]],[[217559,573100],[-2179,21],[-1,4852],[-499,6],[-1,1613]],[[214880,583162],[1461,11]],[[233488,574623],[-724,-26]],[[232764,574597],[-2189,101]],[[230526,583184],[3019,-21]],[[233545,583163],[240,-2188],[403,729],[4,-3863],[-704,-3],[0,-3215]],[[209233,576371],[4,-4555]],[[209237,571816],[-1620,-3],[1,1621],[-362,4],[0,1612],[-601,2]],[[206421,583169],[2811,12]],[[175795,568571],[0,-4401]],[[175795,564170],[-503,1655],[-278,-929],[18,-2373]],[[175032,562523],[-508,-3],[3,1886],[-1500,-263]],[[173027,564143],[2,1271]],[[217559,573100],[127,-1619],[-2,-6492],[124,-2],[-3,-3238]],[[217805,561749],[-2509,-20]],[[215296,561729],[1,3269],[-141,0],[0,6485],[-143,10],[1,3259],[-726,-15]],[[156776,574866],[145,-2337],[-336,700],[191,1637]],[[156018,576189],[368,-1161],[-54,-1922],[-275,764],[-39,2319]],[[156711,577874],[437,-1002],[-571,-1183],[-175,1060],[309,1125]],[[239768,576252],[-26,-7172],[46,-6527]],[[239788,562553],[-1935,132],[-2,-978],[-1789,-11]],[[236062,561696],[-28,9709]],[[236034,571405],[-5,6210]],[[236029,577615],[339,174],[235,-1132],[1048,-348],[153,-2131],[908,561],[5,841],[719,948],[332,-276]],[[157403,572219],[-141,1501],[247,575],[362,-1247],[-15,3533]],[[162835,574463],[-341,252],[-585,-931],[-105,-3299],[196,-417]],[[162000,570068],[-3852,30]],[[158148,570098],[-745,2121]],[[212472,574738],[0,-3235],[160,6],[-1,-4641]],[[212631,566868],[-1040,215],[-261,-1783],[-494,-877],[-372,2569],[-383,-709],[-219,-2109],[-618,321]],[[209244,564495],[-7,7321]],[[219741,574707],[109,-3230],[1,-6504],[110,0],[-1,-3243]],[[219960,561730],[-1079,-3]],[[218881,561727],[-1076,22]],[[243362,568345],[29,-12216],[-17,-11281]],[[243374,544848],[-834,-2840],[-305,-2385]],[[242235,539623],[-249,57]],[[241986,539680],[-26,1879],[-2117,44]],[[239843,541603],[15,4838]],[[239858,546441],[-15,12916],[-55,3196]],[[239768,576252],[1003,-1644],[260,59],[-216,-1482],[572,-281],[382,-4206],[280,489],[20,1987],[577,82],[136,-1749],[580,-1162]],[[209244,564495],[-258,1115],[-593,-179],[-307,1362],[-1057,19],[-145,560],[-288,-1582],[-558,-1]],[[206038,565789],[-93,497],[-752,103],[-963,-1631]],[[165151,563828],[-358,-3363],[-516,-331],[-140,-2995],[-194,-1515],[49,-2367],[561,-1042],[-26,-1385]],[[164527,550830],[-822,-17],[-768,1528],[-383,1475],[-339,202],[-528,3048]],[[161687,557066],[130,2031],[-145,1372]],[[161672,560469],[136,875],[-308,1331],[76,2649],[386,643],[303,1650],[-265,2451]],[[215296,561729],[-1431,3],[-1,-1673]],[[213864,560059],[-714,1226]],[[213150,561285],[-16,1450],[265,1806],[-308,1452],[-460,875]],[[232764,574597],[24,-6811]],[[232788,567786],[-2527,21]],[[230261,567807],[-1798,-6]],[[228463,567801],[13,367]],[[228476,568168],[71,2441],[-130,4068]],[[221924,571472],[-985,7],[-4,-6507],[100,-3249]],[[221035,561723],[-716,0]],[[220319,561723],[-359,7]],[[223962,564053],[0,0]],[[225197,574689],[73,-6488]],[[225270,568201],[-362,-8],[-2,-3234],[-287,1],[0,-1975]],[[224619,562985],[-263,25],[-571,2718],[-293,-1216],[-752,3114],[0,3841]],[[228476,568168],[-2123,27]],[[226353,568195],[-1083,6]],[[236062,561696],[6,-7465]],[[236068,554231],[0,-615],[-707,-31]],[[235361,553585],[-1430,49]],[[233931,553634],[-29,8560],[-224,1564],[125,1180],[-985,11]],[[232818,564949],[-30,2837]],[[233488,574623],[363,-5],[3,-3239],[2180,26]],[[188725,567028],[1,-2703]],[[188726,564325],[-1602,-42],[-179,1901],[-362,820],[-2338,28]],[[158090,569229],[27,-389]],[[158117,568840],[-233,-1009],[277,-1641],[-369,861],[-55,2156],[353,22]],[[157569,571941],[242,-1775],[-417,-308],[-219,-1058],[350,-340],[186,-3597],[44,1501],[415,-1154],[-2,-2382],[-642,2318],[-19,2344],[-435,1258],[298,3297],[199,-104]],[[156635,565779],[-61,-3686],[-1563,-8],[-1,265],[-3066,-4]],[[151944,562346],[-337,5364],[262,4073],[685,-1863],[396,-423],[748,-1863],[763,57],[868,-995],[776,701],[530,-1618]],[[224619,562985],[0,-1281]],[[224619,561704],[-2150,10]],[[222469,561714],[-1434,9]],[[193937,559705],[-803,-422],[-50,-3212],[-1028,-2365]],[[192056,553706],[-1187,-7]],[[190869,553699],[-361,553],[1,1468],[-266,4],[-517,1364],[204,1867],[-1205,-7]],[[188725,558948],[1,5377]],[[161672,560469],[-3555,-37]],[[158117,560432],[249,3191],[302,1598],[-408,1180],[-143,2439]],[[158090,569229],[58,869]],[[179823,560649],[58,-3498],[649,4],[-91,-2233],[239,252],[-155,-1950],[417,-649],[44,-4053]],[[180984,548522],[-292,-237],[-171,1187],[-551,1163],[-685,298]],[[179285,550933],[-478,836],[-38,1569],[-836,1676],[-964,-135]],[[176969,554879],[-353,1336],[183,799],[-96,1863],[-283,1074],[-625,4219]],[[245498,568092],[24,-13481]],[[245522,554611],[-1265,-6316],[-883,-3447]],[[243362,568345],[243,-1594],[424,-163],[-24,-1207],[886,751],[607,1960]],[[226353,568195],[59,-3242],[-1,-6490]],[[226411,558463],[-285,2]],[[226126,558465],[-1436,0]],[[224690,558465],[-71,3239]],[[228463,567801],[349,-5611],[345,-3679]],[[229157,558511],[-1604,-50]],[[227553,558461],[-1142,2]],[[230261,567807],[50,-3920]],[[230311,563887],[2,-2161],[360,-5],[3,-1615],[1434,5],[0,1629],[356,7],[-1,1624]],[[232465,563371],[355,-34]],[[232820,563337],[78,-8073]],[[232898,555264],[-1430,-21]],[[231468,555243],[-2182,14]],[[229286,555257],[-129,3254]],[[232818,564949],[2,-1612]],[[232465,563371],[-1,531],[-2153,-15]],[[209244,564495],[-2,-11146]],[[209242,553349],[-241,-807],[-805,9]],[[208196,552551],[-259,2],[1,3249],[-225,-5],[0,1619],[-1072,7],[1,1622],[-715,4],[0,1622]],[[205927,560671],[111,1441],[0,3677]],[[167625,563859],[-339,-5],[-301,-1380],[-1,-1619],[-240,-1347],[-120,-2159],[-533,-1577],[-2,-1607],[-954,-85],[-359,-2172],[-6,-1862]],[[164770,550046],[-243,784]],[[213150,561285],[-1,-2790],[-1274,10],[2,-6436]],[[211877,552069],[-1581,12]],[[210296,552081],[-1055,15]],[[209241,552096],[1,1253]],[[156466,557234],[-1440,16],[0,-1664]],[[155026,555586],[-1512,9],[-856,299]],[[152658,555894],[-216,4014],[-498,2438]],[[156635,565779],[272,1293],[567,-4576],[-189,-395],[-250,-3034],[-106,2479],[-245,-3231],[-218,-1081]],[[188725,558948],[-718,-7],[0,-1612],[-713,3],[0,-1972],[-347,194]],[[186947,555554],[-644,-185],[-425,1863],[-410,507],[-656,-490],[-280,1680],[6,2406],[-203,2375]],[[205927,560671],[-357,-7],[-2,-4859],[-120,-3],[1,-6473]],[[205449,549329],[-1188,-10],[0,-1636],[-355,-3],[0,1641],[-353,-3]],[[203553,549318],[-233,0],[-1,3235],[-355,-6],[105,3251],[-1,6317],[-271,1],[-168,1657]],[[176969,554879],[-257,-1008],[473,-1408],[60,-942],[526,-680],[549,-2997],[263,-2458],[251,-686]],[[178834,544700],[-3821,28]],[[175013,544728],[0,1637]],[[175013,546365],[0,7286]],[[175013,553651],[19,8872]],[[182526,557117],[-825,-26],[-59,-2280],[224,-1574],[136,-3906],[-311,-808],[-707,-1]],[[173027,564143],[7,-11380]],[[173034,552763],[0,-1987]],[[173034,550776],[-2181,21]],[[170853,550797],[8,10515]],[[233931,553634],[40,-4845]],[[233971,548789],[-1065,-20]],[[232906,548769],[-8,6495]],[[175013,553651],[-714,16],[-121,-799],[-1144,-105]],[[203553,549318],[1,-5970]],[[203554,543348],[0,-266]],[[203554,543082],[-1766,-14],[-2,268],[-3263,-151]],[[198523,543185],[-142,488],[173,2154],[-209,2560],[-11,4245],[143,1707]],[[167650,563516],[-17,-12699]],[[167633,550817],[-9,-6519],[-1075,22],[-3,-3262]],[[166546,541058],[-233,-1080]],[[166313,539978],[-187,856],[-295,-1494],[-690,-305]],[[165141,539035],[-276,2021]],[[164865,541056],[127,1141],[-311,5065],[89,2784]],[[170853,550797],[-380,1]],[[170473,550798],[-2840,19]],[[186947,555554],[7,-6012],[711,-1191],[-2,-4004],[368,-11]],[[188031,544336],[35,-1329],[411,-1480]],[[188477,541527],[-392,-449],[14,-3150],[-430,-3]],[[187669,537925],[-672,2],[-784,-2757]],[[186213,535170],[46,3441],[-325,1748],[-347,-22],[-1,2529],[-723,-49],[-4,6432],[-731,40],[-1,5728],[-238,2035]],[[157723,558953],[153,-2103],[-264,244],[111,1859]],[[157679,553460],[-209,2]],[[157470,553462],[-20,-1]],[[157450,553461],[-465,10]],[[156985,553471],[3,2162],[-620,2]],[[156368,555635],[296,1874],[456,872],[192,2438],[440,2118],[103,-3143],[-199,148],[-132,-3407],[313,-1094],[-158,-1981]],[[239858,546441],[-2004,75]],[[237854,546516],[-5,4597],[-695,1379],[-255,2407],[-570,-1212],[-261,544]],[[218881,561727],[0,-3232],[-241,5],[-5,-6449]],[[218635,552051],[-211,-1],[0,-3173],[-602,-11]],[[217822,548866],[-251,2540],[-562,-513]],[[217009,550893],[-256,567],[-257,3108],[37,1844],[-852,-555],[-223,-664],[-1091,1452]],[[214367,556645],[-155,784],[-3,3079],[-345,-449]],[[224690,558465],[0,-1585]],[[224690,556880],[-2133,9]],[[222557,556889],[-88,4825]],[[222557,556889],[-1,-4848]],[[222556,552041],[-598,-4]],[[221958,552037],[-1539,4]],[[220419,552041],[1,6449],[-101,3233]],[[220419,552041],[-222,0]],[[220197,552041],[-1562,10]],[[214367,556645],[0,-4588],[170,0],[-1,-5915]],[[214536,546142],[-1061,-2],[0,-539],[-1412,-13]],[[212063,545588],[-1,6481],[-185,0]],[[197352,556811],[-7,-4999],[-769,-161],[-357,-670],[-49,-3204],[357,-6],[-62,-6487]],[[196465,541284],[-1055,22]],[[195410,541306],[-1057,8],[-1,-1068]],[[194352,540246],[-993,3]],[[193359,540249],[-235,2015],[208,1753],[-4,5387],[-1034,1609],[0,1620],[-238,1073]],[[208196,552551],[219,-2029],[101,-3785],[-297,-2572],[-534,-806]],[[207685,543359],[-1181,12],[-117,1073],[-355,1],[1,1079],[-357,1],[0,3802],[-227,2]],[[157999,553531],[-252,-1157],[151,3089],[101,-1932]],[[161687,557066],[-282,-523],[-596,-2846],[-98,-1164],[461,-3838],[-226,-1130]],[[160946,547565],[-1160,1607],[-393,-641],[-440,1038],[-133,1180],[-534,2],[-231,1165]],[[158055,551916],[258,528],[-269,4233],[225,530],[-269,1061],[117,2164]],[[190869,553699],[1,-1066],[-357,2],[-14,-4901],[354,-2],[-21,-5066]],[[190832,542666],[-725,2921],[-441,463],[-27,1540],[-216,-1427],[-1062,-215],[1,-1610],[-331,-2]],[[227553,558461],[-3,-6470],[62,-1591]],[[227612,550400],[-709,17]],[[226903,550417],[-711,10]],[[226192,550427],[-71,1593],[5,6445]],[[226192,550427],[-1411,-1]],[[224781,550426],[-88,1602]],[[224693,552028],[-3,4852]],[[229286,555257],[52,-4884]],[[229338,550373],[-1726,27]],[[156466,557234],[-98,-1599]],[[156985,553471],[-2,-836]],[[156983,552635],[-348,-1456],[-55,-1446],[-357,-1953]],[[156223,547780],[-354,-250]],[[155869,547530],[-800,-41],[-43,8097]],[[182993,557119],[1,-7838],[455,9],[-1,-6466]],[[183448,542824],[-493,-547],[1,-1076],[-524,-538],[1,-1073],[-447,49]],[[181986,539639],[-1407,-5]],[[180579,539634],[-732,-499]],[[179847,539135],[-221,1916]],[[179626,541051],[351,195],[-48,4029],[320,549],[-90,1379],[-636,2137],[-238,1593]],[[186213,535170],[-690,-2887]],[[185523,532283],[-1333,17]],[[184190,532300],[-6,8737],[-214,-420],[-522,2207]],[[198523,543185],[180,-1781]],[[198703,541404],[-2238,-120]],[[164865,541056],[-1495,18],[1,1609],[-349,24],[-1,1604],[-1091,-12],[-177,1484],[-533,1821],[-274,-39]],[[224693,552028],[-2137,13]],[[217009,550893],[-2,-451],[-1411,-2],[0,-4839]],[[215596,545601],[-927,-4]],[[214669,545597],[-133,545]],[[155869,547530],[121,-1651],[-6,-3776]],[[155984,542103],[-586,-24]],[[155398,542079],[-2026,38]],[[153372,542117],[-109,2053],[252,-202],[623,1195],[-914,1191],[-230,4847],[-241,1362],[-95,3331]],[[232906,548769],[-1437,4]],[[231469,548773],[-1,6470]],[[231469,548773],[-356,-11]],[[231113,548762],[-1799,-4]],[[229314,548758],[24,1615]],[[179626,541051],[-762,2278],[-30,1371]],[[237854,546516],[-2,-4236]],[[237852,542280],[-1578,49],[42,-9604],[-40,-223]],[[236276,532502],[-231,1007],[-307,-539],[-331,854]],[[235407,533824],[-217,373]],[[235190,534197],[-133,485],[-22,7620]],[[235035,542302],[354,-11],[-28,11294]],[[310206,521389],[-674,-758]],[[309532,520631],[-1071,-1126],[-11,14937],[-1066,257]],[[307384,534699],[-8,3291],[-2505,26]],[[304871,538016],[-841,2]],[[304030,538018],[72,2263],[2153,14229],[505,-617],[6,-3382],[393,-1252],[815,1288],[76,747],[564,5],[41,1253],[358,12],[783,-2953],[455,-2410],[25,-20921],[-70,-4891]],[[193359,540249],[-620,1062],[-850,-749]],[[191889,540562],[-514,-597],[-543,2701]],[[175013,546365],[-1409,188],[-569,1764]],[[173035,548317],[-1,2459]],[[235035,542302],[-1047,14]],[[233988,542316],[-17,6473]],[[160946,547565],[-214,-3054],[-186,-928],[190,-1670]],[[160736,541913],[-846,11],[-206,-1039],[-1032,653]],[[158652,541538],[-352,1507],[-447,420],[-411,2054],[-166,2243]],[[157276,547762],[314,1630],[142,2452],[323,72]],[[157679,553460],[-93,-2779],[-243,609],[127,2172]],[[157450,553461],[-258,-1218],[-95,-3180],[-195,1415],[81,2157]],[[209241,552096],[0,-12822]],[[209241,539274],[-860,0],[-178,809],[-527,0]],[[207676,540083],[9,3276]],[[212063,545588],[-553,-13],[-2,-6517]],[[211508,539058],[-1052,-1]],[[210456,539057],[-1,6518],[-160,-2],[1,6508]],[[210456,539057],[-534,-4],[0,-1653],[-681,3]],[[209241,537403],[0,1871]],[[221958,552037],[-1,-6447],[97,4],[-9,-6501]],[[222045,539093],[-1299,23]],[[220746,539116],[-461,8]],[[220285,539124],[17,6480],[-110,4],[5,6433]],[[224781,550426],[0,-4857],[78,2],[1,-6489]],[[224860,539082],[-1665,-10]],[[223195,539072],[-1150,21]],[[220285,539124],[-1617,18]],[[218668,539142],[-334,973],[-96,2185],[-334,3330]],[[217904,545630],[153,619],[-235,2617]],[[217904,545630],[-2308,-29]],[[170473,550798],[4,-6433],[-150,-2316],[-550,-960]],[[169777,541089],[-3231,-31]],[[173035,548317],[0,-10900]],[[173035,537417],[-1,-2156]],[[173034,535261],[-434,-110],[-90,788]],[[172510,535939],[-70,1545],[-616,2886],[-768,-191],[-281,-1215]],[[170775,538964],[-894,-1263],[-120,595]],[[169761,538296],[16,2793]],[[226903,550417],[68,-4868],[-1,-6487]],[[226970,539062],[-981,13]],[[225989,539075],[-1129,7]],[[229314,548758],[6,-2652],[212,-1371],[-110,-2548],[11,-3117]],[[229433,539070],[-1343,-23]],[[228090,539047],[-1120,15]],[[158652,541538],[-2669,24],[1,541]],[[156223,547780],[371,1366],[-165,-1817],[270,11],[248,1910],[329,-1488]],[[207676,540083],[-351,-1344]],[[207325,538739],[-350,-4],[-351,-1338],[-710,7],[-584,557],[-128,1621],[-225,-2],[-117,3243],[-599,530],[-707,-5]],[[233988,542316],[-2,-1623]],[[233986,540693],[-2817,4]],[[231169,540697],[-56,8065]],[[231169,540697],[7,-1623],[-303,3]],[[230873,539077],[-1399,-8]],[[229474,539069],[-41,1]],[[175013,544728],[1,-5708],[-353,22]],[[174661,539042],[-461,-3],[-274,-1604],[-891,-18]],[[165141,539035],[21,-10945]],[[165162,528090],[-2024,0],[-2594,63]],[[160544,528153],[1,6416]],[[160545,534569],[358,11],[-157,2684],[251,3313],[-261,1336]],[[191889,540562],[-24,-9124]],[[191865,531438],[0,-676]],[[191865,530762],[-1399,153]],[[190466,530915],[-776,9]],[[189690,530924],[-179,1515],[195,2306],[-474,569],[-329,2182],[-426,4031]],[[239843,541603],[18,-6465]],[[239861,535138],[3,-4862]],[[239864,530276],[-1053,-77]],[[238811,530199],[4,1716],[-1019,-55]],[[237796,531860],[-43,6341],[94,109],[5,3970]],[[251183,535146],[2,-1610]],[[251185,533536],[-1045,-5],[1,3215],[-1046,-7],[1,1617],[-346,12],[-67,3222]],[[248683,541590],[281,994],[1035,453],[493,1189],[335,1615],[517,690]],[[251344,546531],[2,-4942],[190,-1609],[-348,14],[-5,-4848]],[[214669,545597],[2,-4901]],[[214671,540696],[-1,-1616]],[[214670,539080],[-2316,-18]],[[212354,539062],[-846,-4]],[[218668,539142],[261,-751],[-62,-3093]],[[218867,535298],[-369,-883],[-548,257],[-360,-2040]],[[217590,532632],[0,1615],[-698,1],[0,4816],[-1166,16],[-1,1620],[-1054,-4]],[[253820,544308],[0,-2727],[148,-1606],[-346,-13],[-2,-4818]],[[253620,535144],[-1568,10]],[[252052,535154],[1,8036],[354,0],[-1,1615],[281,8]],[[252687,544813],[-46,-3488],[634,3347],[545,-364]],[[179847,539135],[-2884,-2989],[-518,-3856],[-988,1959]],[[175457,534249],[-555,1774]],[[174902,536023],[-221,561],[-20,2458]],[[207325,538739],[0,-2429],[-482,-1],[56,-6431]],[[206899,529878],[-149,-3],[0,-6509],[-119,1]],[[206631,523367],[-3371,30]],[[203260,523397],[2,6486],[106,4],[-6,6446],[97,2],[-1,6474],[96,273]],[[203260,523397],[-118,-5],[-3,-8127],[-123,-28],[0,-3172]],[[203016,512065],[-1361,-4],[-1,3224],[108,-22],[0,5982],[-511,206],[100,2748],[-172,715]],[[201179,524914],[0,4963],[-191,-48],[-58,4865],[-348,2],[-60,1616],[-1571,-11]],[[198951,536301],[-119,265]],[[198832,536566],[-129,4838]],[[184190,532300],[-1,-814],[-687,-6],[16,-3298],[-667,-1968]],[[182851,526214],[-255,180],[-219,1599],[-324,48],[141,1392],[-197,1673],[207,1275],[-352,4420],[134,2838]],[[235190,534197],[-1182,4]],[[234008,534201],[-22,6492]],[[237796,531860],[-40,-1612]],[[237756,530248],[-1572,9],[92,2245]],[[155398,542079],[35,-7597]],[[155433,534482],[-1024,26],[-6,-1764]],[[154403,532744],[-413,-941],[-401,1402],[-173,-854],[164,5827],[40,-3886],[155,66],[162,3197],[-178,1201],[335,1945],[-444,-237],[-278,1653]],[[160545,534569],[-1999,-62]],[[158546,534507],[-2721,-5]],[[155825,534502],[-392,-20]],[[189690,530924],[-791,-90],[-193,-3456],[-681,-3145]],[[188025,524233],[-1,4020],[-345,10],[-10,9662]],[[241986,539680],[-2,-4580]],[[241984,535100],[-2123,38]],[[251185,533536],[-1,-4392]],[[251184,529144],[-281,766],[-2333,3002],[1,-217]],[[248571,532695],[-534,909],[-308,3219],[-519,1035]],[[247210,537858],[1092,2058],[381,1674]],[[198832,536566],[-2,-1875],[-408,1],[-281,-2448],[-812,5],[-234,-521],[0,-1925],[-1045,-3]],[[196050,529800],[2,2684],[-229,1641],[-121,3955],[-233,24],[-59,3202]],[[261640,541399],[0,-9498]],[[261640,531901],[-1747,-14]],[[259893,531887],[0,4842]],[[259893,536729],[1,3447]],[[259894,540176],[1066,-173],[680,1396]],[[196050,529800],[-402,-6]],[[195648,529794],[-1369,1],[-5,-1634]],[[194274,528161],[-523,16],[-137,3240]],[[193614,531417],[700,2],[38,8827]],[[193614,531417],[-1749,21]],[[169761,538296],[-37,88]],[[169724,538384],[-623,62],[-454,-1720],[-56,-1736],[-390,-2246],[-741,-1821]],[[167460,530923],[-633,1584],[-9,4567],[-505,2904]],[[167460,530923],[206,-1170],[-54,-2423]],[[167612,527330],[-388,-1247],[-851,-272]],[[166373,525811],[-390,134],[-274,-1277],[-552,-389]],[[165157,524279],[5,3811]],[[234008,534201],[28,-4881]],[[234036,529320],[-1739,14]],[[232297,529334],[-1380,21]],[[230917,529355],[-44,9722]],[[217590,532632],[-397,-2552],[-436,-1343],[-920,-1191],[-478,1151],[-416,-370]],[[214943,528327],[2,2829]],[[214945,531156],[-136,1457],[-12,6465],[-127,2]],[[172510,535939],[1,-949],[-532,-522],[0,-4866],[-168,2],[-1,-2311]],[[171810,527293],[-342,19]],[[171468,527312],[-25,6322],[-176,-16],[-173,2490],[-351,-8],[32,2864]],[[209241,537403],[-1,-4854]],[[209240,532549],[1,-6235]],[[209241,526314],[3,-1180]],[[209244,525134],[-252,2],[0,1618],[-714,26],[2,1575],[-351,-6],[1,1522],[-1031,7]],[[180579,539634],[-28,-2628],[-263,-4539],[-67,-3326],[-131,-1223],[266,-995],[6,-2349],[-249,-34],[-187,-1419],[188,-1950],[-181,-2072]],[[179933,519099],[-282,-1616],[-344,766],[-14,-1410],[527,-2617],[-250,-1838]],[[179570,512384],[-3571,-27],[-470,-1640]],[[175529,510717],[-390,423],[-163,2564],[-956,-4]],[[174020,513700],[363,4438],[258,2020],[-237,2562],[-686,1930]],[[173718,524650],[258,2609]],[[173976,527259],[447,45],[75,2435],[241,1363],[664,-892],[157,1127],[336,203],[-439,2709]],[[182851,526214],[3,-24]],[[182854,526190],[-34,-1070],[-471,-700],[-670,-2752]],[[181679,521668],[-944,-3979],[-194,-400],[-349,1947],[-259,-137]],[[220746,539116],[-2,-6512],[106,-6]],[[220850,532598],[-4,-6356]],[[220846,526242],[-1761,43]],[[219085,526285],[-327,3734],[198,1955],[-89,3324]],[[223195,539072],[-2,-6466]],[[223193,532606],[-2343,-8]],[[174902,536023],[1,-2306],[-958,-326],[31,-6132]],[[173718,524650],[-339,2599]],[[173379,527249],[-121,1925],[103,1310],[-372,3279],[45,1498]],[[212354,539062],[9,-6488]],[[212363,532574],[-197,-15]],[[212166,532559],[-2926,-10]],[[225989,539075],[-2,-6477]],[[225987,532598],[-2698,1]],[[223289,532599],[-96,7]],[[214945,531156],[-1392,13],[0,1436],[-1190,-31]],[[230917,529355],[-1,-1621]],[[230916,527734],[-866,4]],[[230050,527738],[45,1765],[-114,3994],[-380,2927],[-127,2645]],[[228090,539047],[-2,-6457]],[[228088,532590],[-2023,4]],[[226065,532594],[-78,4]],[[230050,527738],[35,-1611]],[[230085,526127],[-1851,-1]],[[228234,526126],[-88,1154],[-58,5310]],[[171468,527312],[-1044,34]],[[170424,527346],[-53,1]],[[170371,527347],[15,3855],[-349,1],[1,1630],[-349,-7],[35,5558]],[[170371,527347],[-2759,-17]],[[248571,532695],[0,-5698],[-321,-1]],[[248250,526996],[-719,-11]],[[247531,526985],[-3,3230],[-343,-15],[1,1609],[-352,7],[6,6374]],[[246840,538190],[370,-332]],[[304871,538016],[-24,-11134],[233,-2084],[-240,-2015],[-258,-234],[344,-2135],[-221,-1593],[445,-9902],[739,1165]],[[305889,510084],[248,-6544]],[[306137,503540],[-330,591],[-241,-1132]],[[305566,502999],[-450,139],[79,-2294],[-907,626]],[[304288,501470],[-183,1243],[-102,3478],[-276,-236],[62,1778],[-139,3374],[-376,-344],[-347,651],[-372,9721]],[[302555,521135],[466,1247],[-87,1101],[431,1781],[-152,2424],[219,2372],[-153,837],[286,2948],[372,1242],[93,2931]],[[307384,534699],[-16,-13241],[-374,-414],[-14,-2791],[297,275],[227,-5338],[-292,-285],[68,-1530],[-1391,-1291]],[[188025,524233],[-403,-667]],[[187622,523566],[-350,1101],[-489,-286],[-234,-1731]],[[186549,522650],[-588,1548],[-293,6521],[-197,-56]],[[185471,530663],[52,1620]],[[259893,531887],[-1,-5145]],[[259892,526742],[-133,-900],[-413,823],[-697,-828],[-157,-2486],[-258,-539]],[[258234,522812],[-83,2590],[0,4874],[-349,11]],[[257802,530287],[0,3250],[698,-25],[0,3238],[1393,-21]],[[198951,536301],[351,-3235],[408,-2428],[-126,-2500]],[[199584,528138],[-454,7],[0,-1079],[-345,0],[-232,-1614],[-462,6],[-62,-7075],[-689,-1094],[-914,67]],[[196426,517356],[-551,2735]],[[195875,520091],[-174,1330],[10,5160],[-63,3213]],[[201179,524914],[-661,1614],[-693,2],[0,1610],[-241,-2]],[[173379,527249],[-1569,44]],[[219085,526285],[-4142,17]],[[214943,526302],[0,2025]],[[253620,535144],[-3,-3220]],[[253617,531924],[2,-6051]],[[253619,525873],[-836,1000],[-325,853],[-417,-133]],[[252041,527593],[-695,1110]],[[251346,528703],[-162,441]],[[251183,535146],[869,8]],[[241984,535100],[-2,-4839]],[[241982,530261],[-158,-2624],[-996,-2261],[-371,-3070]],[[240457,522306],[-838,12]],[[239619,522318],[-4,4660],[246,5],[3,3293]],[[309532,520631],[304,-6937],[-325,-226]],[[309511,513468],[-690,-627],[93,-2259],[-353,-313],[61,-1593],[-355,-303],[157,-4051],[-1023,-1482]],[[307401,502840],[-7,-12]],[[307394,502828],[-61,340],[-964,-1079],[-232,1451]],[[160544,528153],[-247,-4],[10,-4864],[241,-1078]],[[160548,522207],[-795,-346],[-319,-1061]],[[159434,520800],[-910,-1887]],[[158524,518913],[9,9424]],[[158533,528337],[13,6170]],[[158533,528337],[-318,-1795],[-454,495],[-601,-1309],[-131,-1178]],[[157029,524550],[-330,4340],[-600,1895],[-259,-235]],[[155840,530550],[-2,54]],[[155838,530604],[-13,3898]],[[155838,530604],[-183,-462],[-495,2189],[-757,413]],[[235407,533824],[27,-10700]],[[235434,523124],[-1381,-10]],[[234053,523114],[-17,6206]],[[237756,530248],[0,-3231],[116,-9],[14,-2987]],[[237886,524021],[-1641,-2],[226,-871]],[[236471,523148],[-1037,-24]],[[251026,525384],[-2773,34]],[[248253,525418],[-3,1578]],[[251346,528703],[-2,-1704],[-318,2],[0,-1617]],[[155418,529962],[8,-6731]],[[155426,523231],[-1689,63]],[[153737,523294],[-68,3037],[184,1181],[-261,4092],[308,-881],[393,424],[493,-351],[259,901],[373,-1735]],[[214943,526302],[-5,-2]],[[214938,526300],[-2623,9]],[[212315,526309],[-150,2]],[[212165,526311],[1,6248]],[[228234,526126],[-2091,12]],[[226143,526138],[-82,1]],[[226061,526139],[4,6455]],[[223289,532599],[-7,-6388]],[[223282,526211],[-1985,21]],[[221297,526232],[-451,10]],[[226061,526139],[-1995,51]],[[224066,526190],[-784,21]],[[212165,526311],[-2924,3]],[[185471,530663],[-559,-802],[1,-1628],[-449,-1027],[40,-1092],[-450,-1375]],[[184054,524739],[-389,162],[-477,-1895],[-334,3184]],[[255011,527072],[0,-1623],[-222,0],[-1,-3274],[-416,-8]],[[254372,522167],[-588,1081]],[[253784,523248],[-213,795],[48,1830]],[[253617,531924],[1399,-8],[-5,-4844]],[[238811,530199],[-2,-3213],[-238,7],[12,-4605]],[[238583,522388],[8,-3269]],[[238591,519119],[-693,16]],[[237898,519135],[-12,4886]],[[194274,528161],[-17,-1569],[-232,-20],[-118,-1642],[-36,-4874],[-1029,-1611],[-3,-3240],[-365,3],[-1,-3288]],[[192473,511920],[-459,14],[21,11394],[-200,2],[30,7432]],[[190466,530915],[-30,-11210],[-346,-1209],[4,-3243],[-336,0],[-17,-6512]],[[189741,508741],[-31,-6238]],[[189710,502503],[18,-3578]],[[189728,498925],[-493,1904],[19,851],[-442,2414]],[[188812,504094],[13,11149],[64,-1],[-3,5424],[-517,4],[-342,1882],[-405,1014]],[[192473,511920],[740,-88]],[[193213,511832],[-2,-3076]],[[193211,508756],[-2525,-183],[-945,168]],[[186549,522650],[-756,-105],[-628,-2189]],[[185165,520356],[-202,2287],[-909,2096]],[[157029,524550],[64,-2277]],[[157093,522273],[-464,-110]],[[156629,522163],[-284,1059],[-919,9]],[[155418,529962],[422,588]],[[239619,522318],[-1036,70]],[[242662,530269],[51,-9640]],[[242713,520629],[-344,-3]],[[242369,520626],[1,1598],[-1042,58],[-2,-1612],[-997,38]],[[240329,520708],[128,1598]],[[241982,530261],[680,8]],[[245796,530213],[-1,-3233],[691,9]],[[246486,526989],[-5,-6391]],[[246481,520598],[-2400,-12]],[[244081,520586],[-31,9670]],[[244050,530256],[1746,-43]],[[244081,520586],[-1368,43]],[[242662,530269],[1388,-13]],[[209244,525134],[12,-12455]],[[209256,512679],[-50,-4012]],[[209206,508667],[-2732,55]],[[206474,508722],[1,6548],[142,-1],[14,8098]],[[195875,520091],[-795,-463],[-177,-2269],[-459,3],[-573,-1889],[-130,-1894],[-228,1],[-2,-1751],[-298,3]],[[232297,529334],[32,-6470]],[[232329,522864],[-1381,-3]],[[230948,522861],[-32,4873]],[[234053,523114],[0,-270]],[[234053,522844],[-1724,20]],[[252041,527593],[20,-5420],[699,-8]],[[252760,522165],[-8,-6431]],[[252752,515734],[-696,32]],[[252056,515766],[-691,-1],[8,1610],[-344,-7]],[[251029,517368],[-3,8016]],[[158524,518913],[-1068,1088],[-329,768],[-34,1504]],[[165157,524279],[-366,-437]],[[164791,523842],[-599,-1643],[-746,-545],[-477,787]],[[162969,522441],[-731,-1785]],[[162238,520656],[-782,-457],[-46,928],[-635,569]],[[160775,521696],[-227,511]],[[203016,512065],[45,-3465]],[[203061,508600],[-4592,140]],[[198469,508740],[-941,-34]],[[197528,508706],[338,4067],[-1592,0],[48,4005],[104,578]],[[230948,522861],[3,-3246]],[[230951,519615],[-1623,13]],[[229328,519628],[-7,1157],[513,1813],[251,3529]],[[253784,523248],[-5,-1257],[-1019,174]],[[170424,527346],[13,-2609]],[[170437,524737],[-401,-3204],[0,-4053],[-226,-812],[-642,33],[1,-1373],[-751,-187],[117,-2760],[304,1],[77,-3742]],[[168916,508640],[-1797,-1]],[[167119,508639],[49,1621],[3,8063],[-287,1],[0,1597],[-519,16],[8,5874]],[[174020,513700],[-270,-3513]],[[173750,510187],[-1345,33]],[[172405,510220],[-1,1583],[-585,-10],[-295,3221],[-170,-21],[-112,3313],[-288,3247],[112,3192],[-629,-8]],[[255706,527059],[117,-1614],[-5,-6481],[175,-7]],[[255993,518957],[-480,-4770],[-502,-3587]],[[255011,510600],[-348,1798],[263,2652],[-666,243],[263,2751],[-117,1109],[148,2102],[-182,912]],[[255011,527072],[695,-13]],[[248253,525418],[-2,-6368]],[[248251,519050],[4,-3225]],[[248255,515825],[-1774,-76]],[[246481,515749],[0,4849]],[[246486,526989],[1045,-4]],[[214938,526300],[2,-8789]],[[214940,517511],[-1,-8089]],[[214939,509422],[-2667,29]],[[212272,509451],[0,3227]],[[212272,512678],[43,13631]],[[219085,526285],[420,-1613],[146,-2779],[-338,-2104]],[[219313,519789],[-136,-1298],[382,-971]],[[219559,517520],[-3144,-9]],[[216415,517511],[-1475,0]],[[212272,512678],[-3016,1]],[[221297,526232],[2,-6469]],[[221299,519763],[-1986,26]],[[185165,520356],[-3,-2744],[506,-651],[135,-1730],[3,-4866],[343,-1],[-2,-1637],[399,-13],[67,-3224],[282,-800],[791,-49],[1,-1346],[860,8]],[[188547,503303],[-98,-3225],[-306,273]],[[188143,500351],[-567,-771],[-129,1023],[-637,-764],[-533,835],[-280,-2222],[-237,583],[-858,91],[-96,-2022]],[[184806,497104],[-526,1386],[-10,1397],[-347,4611],[-317,914],[-262,-705],[-322,1502],[12,3599],[-287,1010],[-383,2793],[-233,2701],[-86,3614],[-272,360],[-94,1382]],[[224066,526190],[-2,-6467]],[[224064,519723],[-2738,40]],[[221326,519763],[-27,0]],[[226143,526138],[-2,-6467]],[[226141,519671],[-6,-6480]],[[226135,513191],[-2051,62]],[[224084,513253],[-13,1]],[[224071,513254],[-7,6469]],[[229328,519628],[389,-3087],[628,-1746]],[[230345,514795],[-1456,6],[-40,-559],[-610,15]],[[228239,514257],[-2,4850]],[[228237,519107],[-3,7019]],[[228237,519107],[-408,549],[-1688,15]],[[167119,508639],[-1415,-27]],[[165704,508612],[-332,4],[2,1361]],[[165374,509977],[84,1859],[-342,38],[2,1607],[-347,28],[20,10333]],[[251029,517368],[-1054,47]],[[249975,517415],[-10,1640],[-1714,-5]],[[172405,510220],[-826,-23],[-2,-1616],[-596,-11],[-301,1240],[-304,-1199],[-378,943],[-318,-1615]],[[169680,507939],[-764,701]],[[188812,504094],[-265,-791]],[[237898,519135],[-1086,-11]],[[236812,519124],[-341,4024]],[[165374,509977],[-1964,15]],[[163410,509992],[-23,268]],[[163387,510260],[-111,2406],[144,4765],[366,767],[-140,1600],[-677,2643]],[[206474,508722],[-142,2]],[[206332,508724],[-2606,-128]],[[203726,508596],[-665,4]],[[264518,520374],[-131,-4],[15,-7955]],[[264402,512415],[-335,1]],[[264067,512416],[-1024,59]],[[263043,512475],[5,1628]],[[263048,514103],[0,9278]],[[263048,523381],[752,-2509],[718,-498]],[[155426,523231],[0,-1322],[-345,8],[171,-1358],[345,-537],[-450,-2154],[-11,-1076]],[[155136,516792],[5,-4050],[-895,-4],[-1,-2602],[169,-4]],[[154414,510132],[-4,-597]],[[154410,509535],[-766,17]],[[153644,509552],[191,6726],[-98,7016]],[[156629,522163],[0,-1647],[515,-2132],[2,-1597]],[[157146,516787],[-2,-1893],[-345,-270]],[[156799,514624],[-745,2158],[-918,10]],[[236812,519124],[284,-2536]],[[237096,516588],[-222,-1934],[-373,-651]],[[236501,514003],[-342,795],[-1058,-11]],[[235101,514787],[1,1614],[-1027,-8]],[[234075,516393],[-22,6451]],[[232329,522864],[30,-6471]],[[232359,516393],[-1029,-8]],[[231330,516385],[-347,13],[-32,3217]],[[234075,516393],[-344,3]],[[233731,516396],[-1372,-3]],[[163387,510260],[-630,17],[17,875],[-376,1202],[-462,444],[26,1607],[414,1539],[112,2268],[-250,2444]],[[239619,522318],[0,-3212],[334,-17],[7,-2705]],[[239960,516384],[-1367,54]],[[238593,516438],[-2,2681]],[[240329,520708],[8,-1269],[308,-145],[354,-2359],[-276,-2708]],[[240723,514227],[-762,15]],[[239961,514242],[-1,2142]],[[242369,520626],[-5,-8010]],[[242364,512616],[-1676,1]],[[240688,512617],[35,1610]],[[159434,520800],[0,-1906],[284,-1586]],[[159718,517308],[-2287,-7],[-285,-514]],[[160775,521696],[1,-3313],[-117,6],[3,-4865],[-600,-5]],[[160062,513519],[3,2150],[-347,1639]],[[163410,509992],[120,-3800],[224,-797]],[[163754,505395],[-3866,75]],[[159888,505470],[69,1165]],[[159957,506635],[-195,2311],[405,1008],[-240,2616],[135,949]],[[184806,497104],[3,-2717],[-506,66]],[[184303,494453],[-906,-18]],[[183397,494435],[-56,1597],[-348,2544],[-106,-613],[-845,1224],[-633,3399],[-141,1577],[-365,-188],[52,2182],[-181,-234],[-402,-2430],[55,-904],[-491,-1772],[-716,3193],[20,1138]],[[179240,505148],[217,1309],[9,5102],[104,825]],[[309697,499440],[-326,8144],[343,308],[-203,5576]],[[310206,521389],[551,-1389],[510,-516],[-213,-1646],[200,-1811],[-163,-2048],[408,-2916],[145,1141],[349,-534],[352,-3817],[161,-2744],[-592,-2997],[-880,-140],[-164,-1985],[-252,636],[-149,-1354],[-202,2089],[-181,-786],[-141,-3116],[-248,1984]],[[304288,501470],[-305,-405],[-9,-1933],[-241,39]],[[303733,499171],[-304,-528]],[[303429,498643],[-117,2061],[-640,1070],[-291,3136],[-430,-1180],[-152,5250],[122,81],[-151,4819]],[[301770,513880],[26,2282],[518,-340],[-235,2392],[476,2921]],[[266792,512571],[-1372,-94]],[[265420,512477],[-1018,-62]],[[264518,520374],[261,-2226],[563,-388],[377,-1459],[555,-1119],[218,171],[319,-1624],[-19,-1158]],[[244081,520586],[-3,-6434]],[[244078,514152],[-2,-1591],[-344,26]],[[243732,512587],[-1368,29]],[[246481,515749],[-686,31],[0,-1629]],[[245795,514151],[-1717,1]],[[197528,508706],[-1036,15]],[[196492,508721],[-3281,35]],[[221326,519763],[-4,-6470]],[[221322,513293],[-1536,18]],[[219786,513311],[-157,1348],[119,1153],[-189,1708]],[[224071,513254],[-2371,30]],[[221700,513284],[-378,9]],[[228239,514257],[0,-2714]],[[228239,511543],[-746,-4]],[[227493,511539],[-1359,31]],[[226134,511570],[1,1621]],[[231330,516385],[37,-4380]],[[231367,512005],[-502,1311],[-471,406]],[[230394,513722],[-49,1073]],[[238593,516438],[-6,-3148]],[[238587,513290],[-26,17]],[[238561,513307],[-163,844],[-522,100],[-780,2337]],[[249975,517415],[-3,-6481]],[[249972,510934],[-1721,19]],[[248251,510953],[4,4872]],[[219786,513311],[-178,-2703],[123,-1549],[-311,141],[-36,-2389]],[[219384,506811],[-387,-2543],[-370,1156],[-115,-973]],[[218512,504451],[-583,56],[-208,-833],[-377,334]],[[217344,504008],[2,4597],[-1015,-5],[84,8911]],[[217344,504008],[-269,-1031],[-620,-1015],[-211,-1237],[-503,175],[-264,-1034],[-539,-243]],[[214938,499623],[1,9799]],[[252056,515766],[-6,-3234],[108,-1632]],[[252158,510900],[-950,13],[2,-1662]],[[251210,509251],[-675,9]],[[250535,509260],[-1,1663],[-562,11]],[[159957,506635],[-1845,-55],[-388,968],[-168,1539],[-534,2059],[129,2389],[-301,15]],[[156850,513550],[-51,1074]],[[156850,513550],[-529,-701],[118,-1993],[-202,-742]],[[156237,510114],[-1823,18]],[[238561,513307],[-382,-977],[-277,-2080],[-23,-1945]],[[237879,508305],[-685,14]],[[237194,508319],[-679,14]],[[236515,508333],[-14,5670]],[[239961,514242],[-4,-3220]],[[239957,511022],[-577,12],[2,-1654]],[[239382,509380],[-216,1966],[-579,1944]],[[235101,514787],[15,-8081]],[[235116,506706],[-1366,-14]],[[233750,506692],[5,4861]],[[233755,511553],[-24,4843]],[[233755,511553],[-2201,-4]],[[231554,511549],[-187,456]],[[248251,510953],[-430,7],[1,-1618],[-327,3]],[[247495,509345],[-1695,-50]],[[245800,509295],[-5,4856]],[[252752,515734],[340,-45],[-9,-3220],[342,-69],[182,-3245],[999,-1069]],[[254606,508086],[-385,-2583],[-242,-2789]],[[253979,502714],[-8,-1]],[[253971,502713],[-10,-1]],[[253961,502712],[-693,39]],[[253268,502751],[-16,3220],[-670,52]],[[252582,506023],[15,4858],[-439,19]],[[303429,498643],[-266,-4622],[-406,-3326]],[[302757,490695],[-370,576],[-32,2012],[-647,-2107],[393,-1789],[-189,-2754]],[[301912,486633],[-573,-442]],[[301339,486191],[-59,9209]],[[301280,495400],[-86,9641],[-121,9365]],[[301073,514406],[217,752],[480,-1278]],[[230394,513722],[3,-5426]],[[230397,508296],[-1199,-11]],[[229198,508285],[-5,3227],[-954,31]],[[236515,508333],[-686,5],[-1,-1627]],[[235828,506711],[-712,-5]],[[301280,495400],[-90,-874],[-585,120],[-254,-2830],[-201,2231]],[[300150,494047],[-446,794],[-524,2812]],[[299180,497653],[520,1849],[112,1481],[-264,3053],[383,3039],[-19,1891]],[[299912,508966],[388,4731],[214,622],[386,-1122],[173,1209]],[[240688,512617],[-96,-2433],[64,-4037]],[[240656,506147],[-95,-2136]],[[240561,504011],[-562,444],[-37,2206]],[[239962,506661],[96,22],[-1,4337],[-100,2]],[[245800,509295],[3,-3236]],[[245803,506059],[-2029,-25]],[[243774,506034],[-42,6553]],[[159888,505470],[-166,-669],[69,-1969]],[[159791,502832],[-1153,206],[-233,1075],[-632,10],[-781,720],[-578,-1960],[-382,1172]],[[156032,504055],[-98,1558],[388,2207],[-85,2294]],[[175529,510717],[154,-826],[-339,-5246],[150,-1455],[0,-3804]],[[175494,499386],[-398,-1021]],[[175096,498365],[-633,867],[-4,2312],[-267,893],[-1,3275],[-764,34]],[[173427,505746],[188,1739],[-44,1635],[179,1067]],[[231554,511549],[662,-3749],[173,-272]],[[232389,507528],[-309,-828],[-3,-1608],[-1679,19]],[[230398,505111],[-1,3185]],[[307819,495119],[63,-2040],[-255,1149],[192,891]],[[308823,498240],[322,-1211],[-422,-2791],[-256,1683],[356,2319]],[[309697,499440],[-194,-3153],[-121,2655],[-613,1159],[154,-1191],[-465,-1334],[31,1761],[-402,-1706],[118,-3275],[-828,3345],[180,1912],[-237,2131],[81,1096]],[[221700,513284],[1,-6478]],[[221701,506806],[-279,1]],[[221422,506807],[-2038,4]],[[224084,513253],[3,-6454]],[[224087,506799],[-1658,3]],[[222429,506802],[-728,4]],[[239382,509380],[135,-2743]],[[239517,506637],[-427,-1841]],[[239090,504796],[-535,227]],[[238555,505023],[9,1658],[-687,3],[2,1621]],[[226134,511570],[12,-9713]],[[226146,501857],[-2029,50]],[[224117,501907],[-30,4892]],[[179240,505148],[-395,-1629],[-367,938],[-230,-2310],[-308,-656],[142,-3508],[-185,-1567]],[[177897,496416],[-297,-1464],[-332,-476],[-1622,26],[-30,-1654],[-276,67]],[[175340,492915],[1,3219],[164,2],[-11,3250]],[[212272,509451],[-19,-8102],[-1681,-4]],[[210572,501345],[-701,13],[-660,-630]],[[209211,500728],[-5,7939]],[[242364,512616],[56,-6547]],[[242420,506069],[-1764,78]],[[243774,506034],[0,-3209]],[[243774,502825],[-1350,15]],[[242424,502840],[-4,3229]],[[262700,510888],[28,-4815]],[[262728,506073],[-1239,26],[-114,-876]],[[261375,505223],[-304,883]],[[261071,506106],[219,4694],[-69,1784]],[[261221,512584],[458,-41],[1,-1633],[1020,-22]],[[264067,512416],[-14,-6399]],[[264053,506017],[-1325,56]],[[262700,510888],[342,-33],[1,1620]],[[265420,512477],[-20,-6430]],[[265400,506047],[-1347,-30]],[[227493,511539],[8,-6473]],[[227501,505066],[-3,-4842]],[[227498,500224],[-1005,10]],[[226493,500234],[-9,1613],[-338,10]],[[229198,508285],[-7,-3210]],[[229191,505075],[-1690,-9]],[[233750,506692],[-653,-11],[0,-2565]],[[233097,504116],[-708,3412]],[[239962,506661],[-445,-24]],[[250535,509260],[0,-6476]],[[250535,502784],[-1731,66]],[[248804,502850],[-1313,5]],[[247491,502855],[4,6490]],[[252582,506023],[-689,19],[2,1616],[-685,-6],[0,1599]],[[173427,505746],[-386,-1718],[-295,-3771],[-220,-1145],[9,-3402]],[[172535,495710],[-741,0],[0,1607],[-293,1056],[-1060,-21],[-726,-3486]],[[169715,494866],[-738,-18],[245,2193],[-29,1556],[315,2472],[-553,1434],[612,1573],[-28,2092],[141,1771]],[[156032,504055],[-16,-547]],[[156016,503508],[-1263,17]],[[154753,503525],[-341,333],[-2,5677]],[[165704,508612],[55,-3188],[-10,-9610]],[[165749,495814],[-676,-6],[-2,1543],[-342,912],[-1014,-18],[1,2353]],[[163716,500598],[38,4797]],[[154753,503525],[15,-5367],[-344,3],[15,-1349],[-284,-294],[116,-1140]],[[154271,495378],[-947,-136]],[[153324,495242],[89,2630],[44,7616],[170,1569],[17,2495]],[[214938,499623],[-2,-12]],[[214936,499611],[-312,-1407],[-473,240],[-223,-2776],[-71,-2959],[-2964,11]],[[210893,492720],[-2,2156],[-338,-10],[19,6479]],[[247491,502855],[1,-4850]],[[247492,498005],[-1354,-31],[0,1610],[-337,6],[2,1612]],[[245803,501202],[0,4857]],[[253268,502751],[-7,-1765]],[[253261,500986],[-1005,106]],[[252256,501092],[3,1635],[-1724,57]],[[296979,508865],[60,-3256],[-127,-959]],[[296912,504650],[-471,86],[-134,-1750],[-359,-1101]],[[295948,501885],[-849,1672]],[[295099,503557],[103,5409]],[[295202,508966],[1777,-101]],[[299180,497653],[-205,-1076]],[[298975,496577],[-266,1736],[213,1023],[-144,949],[183,1193],[-188,668],[204,1295],[-286,976]],[[298691,504417],[181,2000],[-64,2447]],[[298808,508864],[1104,102]],[[295099,503557],[-145,-2596],[-226,-367]],[[294728,500594],[-75,974],[149,3512],[-119,604],[97,3231]],[[294780,508915],[422,51]],[[294728,500594],[65,-314]],[[294793,500280],[-930,-2004],[-662,-178]],[[293201,498098],[-213,5177],[-115,5361]],[[292873,508636],[1907,279]],[[298691,504417],[-203,-1307],[-284,945],[-413,-3859],[-312,778]],[[297479,500974],[-299,925],[174,1659],[-442,1092]],[[296979,508865],[1829,-1]],[[196492,508721],[81,-2399],[-1,-6525],[53,-2],[-1,-6560],[65,0]],[[196689,493235],[-1,-1658]],[[196688,491577],[-673,4],[0,-1649],[-344,-3],[9,-1603],[-502,-5],[2,-1644],[-615,-56]],[[194565,486621],[-361,2183],[-278,575],[-277,-718],[-66,-1424],[-340,-839],[-198,2909],[-377,-147],[-166,1088]],[[192502,490248],[0,2320],[-270,2958],[-406,2562],[50,1662],[-272,1203],[-814,-4],[0,1554],[-1080,0]],[[290927,508616],[234,-782],[324,-14558],[-26,-1329]],[[291459,491947],[-889,-543]],[[290570,491404],[-581,-367],[-299,867]],[[289690,491904],[-769,2247]],[[288921,494151],[-1154,3457]],[[287767,497608],[260,2084],[1343,6200],[870,2516],[687,208]],[[198469,508740],[221,-3931],[453,-2059],[152,401],[684,-2630]],[[199979,500521],[185,-1796],[287,-397],[154,-2197],[-5,-2948]],[[200600,493183],[-3911,52]],[[206332,508724],[28,-15345]],[[206360,493379],[-1,-12617]],[[206359,480762],[-2612,-65]],[[203747,480697],[21,19901]],[[203768,500598],[-42,7998]],[[203768,500598],[-1168,-134],[-2621,57]],[[293201,498098],[-645,-417],[133,-5013],[-525,-325]],[[292164,492343],[-705,-396]],[[290927,508616],[1946,20]],[[209211,500728],[4,-7271]],[[209215,493457],[-2855,-78]],[[169715,494866],[13,-4023]],[[169728,490843],[-1637,144],[-4,-1633],[-2343,-17]],[[165744,489337],[5,6477]],[[237194,508319],[5,-4865]],[[237199,503454],[-679,9],[-2,-1632],[-677,-5],[-1,1626]],[[235840,503452],[-12,3259]],[[238555,505023],[-264,-1739],[-417,-1265]],[[237874,502019],[-336,1431],[-339,4]],[[230398,505111],[0,-3258]],[[230398,501853],[-1,-1635]],[[230397,500218],[-1208,4]],[[229189,500222],[2,4853]],[[233097,504116],[341,-1014]],[[233438,503102],[0,-2921],[-654,12]],[[232784,500193],[-25,1633],[-1362,10]],[[231397,501836],[-999,17]],[[240561,504011],[200,-613]],[[240761,503398],[-169,-3191],[-689,-567],[1,-756]],[[239904,498884],[-675,2],[1,1340]],[[239230,500226],[-140,4570]],[[221422,506807],[-9,-6504]],[[221413,500303],[-2368,-13]],[[219045,500290],[-317,676],[77,2216],[-293,1269]],[[222429,506802],[32,-13076]],[[222461,493726],[-769,-45]],[[221692,493681],[-243,467]],[[221449,494148],[-36,6155]],[[224117,501907],[15,-8146]],[[224132,493761],[-628,-3]],[[223504,493758],[-1043,-32]],[[235840,503452],[-367,-8],[16,-4852]],[[235489,498592],[-437,8]],[[235052,498600],[-236,772]],[[234816,499372],[-1378,3730]],[[183397,494435],[-268,4],[1,-3240],[-116,1],[10,-3445],[227,-983],[-559,-2336],[-480,-1253],[-145,-1113]],[[182067,482070],[-654,3911],[-113,-749],[-563,1718],[-9,880],[-657,321],[-39,-921],[-426,1522],[-370,1],[1,1213],[-439,-1021]],[[178798,488945],[-55,216]],[[178743,489161],[-155,1983],[62,1848],[-398,879],[-33,1744],[-322,801]],[[242424,502840],[-1,-2696],[-504,27]],[[241919,500171],[-648,507],[-510,2720]],[[266980,506081],[138,-2704],[-131,-3751]],[[266987,499626],[-1582,-52]],[[265405,499574],[-5,6473]],[[265400,506047],[1580,34]],[[262728,506073],[-10,-6460]],[[262718,499613],[-1346,23]],[[261372,499636],[3,5587]],[[264053,506017],[3,-6475]],[[264056,499542],[-1338,71]],[[245803,501202],[-678,11]],[[245125,501213],[-1013,-15]],[[244112,501198],[-337,9]],[[243775,501207],[-1,1618]],[[265405,499574],[-1349,-32]],[[175096,498365],[-132,-2276],[-32,-3180],[-260,7]],[[174672,492916],[-1240,21]],[[173432,492937],[-217,1683],[-680,1090]],[[163716,500598],[-1231,-21],[0,-1605],[-450,-3],[1,-1609],[-331,10]],[[161705,497370],[-2049,35]],[[159656,497405],[135,5427]],[[239230,500226],[-677,-7]],[[238553,500219],[-1077,-8]],[[237476,500211],[398,1808]],[[229189,500222],[-676,-12]],[[228513,500210],[-1015,14]],[[297479,500974],[-166,-1452]],[[297313,499522],[-157,-1497],[-296,998],[-160,-1509],[-420,995]],[[296280,498509],[-67,1872],[-265,1504]],[[159656,497405],[121,-2500]],[[159777,494905],[-1469,-90],[-166,-673],[-532,204],[-411,1118],[-498,-542],[-6,-1095],[-722,-4],[-54,1557]],[[155919,495380],[-124,1127],[44,2998],[-133,924],[521,1787],[-211,1292]],[[219045,500290],[-133,-1712],[523,-288],[58,-1572],[781,-877],[538,-1239],[-128,-869]],[[220684,493733],[-1196,-11],[0,-483]],[[219488,493239],[-1893,-6]],[[217595,493233],[-334,-5],[34,9715],[49,1065]],[[260032,504518],[-8,-4869]],[[260024,499649],[-1160,99]],[[258864,499748],[-13,3454],[458,1388]],[[259309,504590],[723,-72]],[[298975,496577],[-544,-513],[-20,-3063]],[[298411,493001],[-914,898]],[[297497,493899],[402,4107],[-586,1516]],[[307394,502828],[-140,-1574],[180,-625],[-64,-1886],[-523,-588],[177,-1427],[-225,-1985]],[[306799,494743],[-257,114],[-412,2029],[-390,-679]],[[305740,496207],[-272,275]],[[305468,496482],[309,2126],[8,3412],[-219,979]],[[189728,498925],[5,-9134]],[[189733,489791],[-389,-951],[-593,-175]],[[188751,488665],[-448,-740],[-1162,821]],[[187141,488746],[-1,810],[-499,8],[1,1607]],[[186642,491171],[339,-6],[1,1607],[490,821],[16,2405],[169,1558],[484,18],[2,2777]],[[217595,493233],[-48,-3229]],[[217547,490004],[-2628,-7]],[[214919,489997],[17,9614]],[[296280,498509],[-410,-5407]],[[295870,493102],[-68,2573],[-938,-658]],[[294864,495017],[48,3057],[-119,2206]],[[155919,495380],[-1648,-2]],[[305468,496482],[-133,-1504],[-266,328],[-18,-2929],[-277,308]],[[304774,492685],[-7,9]],[[304767,492694],[-269,530],[-119,-1120],[-300,314]],[[304079,492418],[-196,1521],[-150,5232]],[[237476,500211],[-51,-1567]],[[237425,498644],[-1936,-52]],[[241919,500171],[207,-1615]],[[242126,498556],[-525,16],[0,-1617],[-335,5],[-1,-3232]],[[241265,493728],[-356,11]],[[240909,493739],[-1010,22]],[[239899,493761],[5,5123]],[[234816,499372],[-1,-4019],[-672,-9],[-4,-1614]],[[234139,493730],[-985,19]],[[233154,493749],[-366,-7]],[[232788,493742],[-4,6451]],[[250535,502784],[-3,-8151]],[[250532,494633],[-1040,40]],[[249492,494673],[-353,36]],[[249139,494709],[-5,4907],[-328,5],[-2,3229]],[[249139,494709],[-495,34]],[[248644,494743],[-1141,-13]],[[247503,494730],[-11,3275]],[[253979,502714],[-8,-1]],[[253961,502712],[-152,-2104],[333,-531],[464,2017]],[[254606,502094],[-10,-5904]],[[254596,496190],[-339,8],[-1,-1624],[-432,11]],[[253824,494585],[-416,19]],[[253408,494604],[6,6419],[-153,-37]],[[243775,501207],[-1056,-193],[-152,-3332]],[[242567,497682],[-441,874]],[[252256,501092],[-364,1],[-9,-6465]],[[251883,494628],[-409,-12]],[[251474,494616],[-942,17]],[[255685,502676],[-302,-3068],[-168,-3412]],[[255215,496196],[-619,-6]],[[254606,502094],[73,609]],[[254679,502703],[1006,-27]],[[192502,490248],[5,-10113]],[[192507,480135],[-16,-1620],[-799,3],[0,-1615],[-653,8],[-1,-1050]],[[191038,475861],[-655,-1],[0,1503],[-643,-3]],[[189740,477360],[-2,3452]],[[189738,480812],[-5,8979]],[[226493,500234],[-2,-6499]],[[226491,493735],[-1332,24]],[[225159,493759],[-1027,2]],[[231397,501836],[40,-8076]],[[231437,493760],[-1042,2]],[[230395,493762],[2,6456]],[[232788,493742],[-1310,16]],[[231478,493758],[-41,2]],[[210893,492720],[-1678,5]],[[209215,492725],[0,732]],[[244112,501198],[-2,-6505],[-219,-856],[207,-1949],[-69,-1318]],[[244029,490570],[-834,3122]],[[243195,493692],[-279,3080],[-349,910]],[[245125,501213],[38,-9631]],[[245163,491582],[-433,-275],[-41,-1497],[-287,-3]],[[244402,489807],[-373,763]],[[247503,494730],[-1,-1740]],[[247502,492990],[-1654,56],[-187,-1630]],[[245661,491416],[-498,166]],[[253408,494604],[-589,39]],[[252819,494643],[-936,-15]],[[186642,491171],[-1501,14],[-1,-1610]],[[185140,489575],[-834,8],[-3,4870]],[[203747,480697],[-170,-9]],[[203577,480688],[-2877,-40],[0,148]],[[200700,480796],[-49,12388],[-51,-1]],[[165744,489337],[-329,0],[-5,-4859],[-337,5]],[[165073,484483],[-1002,8],[0,1610],[-336,-4],[-1,1618],[-1028,-13],[-1,1606],[-664,64],[-2,3219],[-321,78],[-13,4701]],[[221449,494148],[180,-1780],[-748,46],[-197,1319]],[[294864,495017],[-214,-1345],[-132,-2763],[162,-4374]],[[294680,486535],[-165,-91]],[[294515,486444],[-1724,-1102]],[[292791,485342],[28,960],[-465,275],[-343,2130],[225,833],[-72,2803]],[[238553,500219],[-2,-6472]],[[238551,493747],[-677,-2]],[[237874,493745],[1,808],[-680,-7]],[[237195,494546],[220,1969],[10,2129]],[[239899,493761],[-13,0]],[[239886,493761],[-1005,-6]],[[238881,493755],[-330,-8]],[[228513,500210],[-4,-6463]],[[228509,493747],[-670,-20]],[[227839,493727],[-1336,7]],[[226503,493734],[-12,1]],[[230395,493762],[0,-2]],[[230395,493760],[-1217,-20]],[[229178,493740],[-669,7]],[[260024,499649],[-9,-6493]],[[260015,493156],[-615,54]],[[259400,493210],[-964,215]],[[258436,493425],[364,3555],[64,2768]],[[261372,499636],[-2,-6463]],[[261370,493173],[-640,-4]],[[260730,493169],[-715,-13]],[[260024,499649],[1348,-13]],[[264056,499542],[8,-6454]],[[264064,493088],[-669,-1]],[[263395,493087],[-679,16]],[[262716,493103],[2,6510]],[[262716,493103],[-656,53]],[[262060,493156],[-690,17]],[[266987,499626],[-62,-3377],[-470,-1067],[-157,-2039]],[[266298,493143],[-886,-32]],[[265412,493111],[-7,6463]],[[265412,493111],[-786,0]],[[264626,493111],[-562,-23]],[[175340,492915],[-169,-3],[-24,-6409]],[[175147,486503],[-644,5]],[[174503,486508],[-557,-1]],[[173946,486507],[0,3241],[391,-17],[114,1595],[220,-13],[1,1603]],[[214919,489997],[-32,-5323],[-335,-145]],[[214552,484529],[-104,-245],[-1766,9]],[[212682,484293],[343,3116],[-3812,-38]],[[209213,487371],[2,5354]],[[297497,493899],[-208,-1404],[-408,589],[-262,-2738],[-163,304]],[[296456,490650],[-487,788],[-99,1664]],[[235052,498600],[862,-2186],[274,-1379]],[[236188,495035],[12,-2914]],[[236200,492121],[-1368,-13]],[[234832,492108],[-669,3],[-24,1619]],[[304079,492418],[-79,-2770]],[[304000,489648],[-229,-1286],[-556,2438],[-134,-953],[-324,848]],[[237195,494546],[-163,-1485],[-844,1974]],[[243195,493692],[-616,1],[0,-1602]],[[242579,492091],[-668,7],[-1,1620],[-645,10]],[[173432,492937],[-215,-1207],[111,-1864],[-135,-2003]],[[173193,487863],[-118,-728],[-1,-2989]],[[173074,484146],[-2,-31285]],[[173072,452861],[-3259,-52]],[[169813,452809],[-49,5195],[-6,11887],[-34,1],[4,20951]],[[300150,494047],[150,-5520],[-565,-418],[90,-2385]],[[299825,485724],[28,-1263],[-360,-305],[-213,-2183]],[[299280,481973],[-251,-348],[-88,1240],[-239,-1545]],[[298702,481320],[-1098,1335]],[[297604,482655],[73,1782],[273,1403]],[[297950,485840],[461,7161]],[[165073,484483],[-1,-1675]],[[165072,482808],[-3996,131]],[[161076,482939],[-1866,-3]],[[159210,482936],[77,4514],[296,1024],[283,3754],[-89,2677]],[[286590,490299],[-326,1891],[242,1614],[1261,3804]],[[288921,494151],[-278,-2135],[171,-536],[-163,-1981],[-600,-1657],[-229,97],[184,-3632]],[[288006,484307],[-698,337],[-439,-516]],[[286869,484128],[-30,11]],[[286839,484139],[-17,6]],[[286822,484145],[-93,2653],[-183,296],[473,1068],[-221,1527],[192,1727],[-400,-1117]],[[306799,494743],[-208,-3179],[83,-579],[-394,-2171],[-422,631],[40,936]],[[305898,490381],[198,677],[-157,2939],[-190,25],[-9,2185]],[[178743,489161],[-409,649],[-97,1866],[-374,-2892],[-523,-708],[-420,-3555],[-517,-1774],[-404,-258]],[[175999,482489],[-852,4014]],[[305898,490381],[-88,1150],[-333,-4457],[-153,1112],[-294,-802],[27,3033],[-191,-1421]],[[304866,488996],[63,1228],[-293,335]],[[304636,490559],[138,2126]],[[255215,496196],[9,-3137],[-283,-948],[-251,-4019]],[[254690,488092],[-862,-9]],[[253828,488083],[-4,6502]],[[296456,490650],[-138,-1260]],[[296318,489390],[-526,-1476],[60,-1043]],[[295852,486871],[-725,197],[35,-1310],[-435,-258]],[[294727,485500],[-47,1035]],[[159210,482936],[-357,-1082],[-3,-2172]],[[158850,479682],[-1696,-62],[-3,2000],[-1023,-131],[-80,4508],[-585,9],[-506,918],[-251,1688],[-580,464],[-273,-1479],[-649,-39]],[[153204,487558],[120,7684]],[[301339,486191],[74,-4637]],[[301413,481554],[-561,-31]],[[300852,481523],[-271,609],[-756,3592]],[[237874,493745],[1,-6477]],[[237875,487268],[-1335,-3]],[[236540,487265],[-340,-1]],[[236200,487264],[0,4857]],[[248644,494743],[4,-1204],[-356,-1774],[167,-1778],[17,-2211],[271,-1702],[222,-2658]],[[248969,483416],[-1467,-1]],[[247502,483415],[1,1682]],[[247503,485097],[-1,7893]],[[249492,494673],[0,-4905]],[[249492,489768],[-4,-6323]],[[249488,483445],[-519,-29]],[[251474,494616],[2,-4829]],[[251476,489787],[-786,-8]],[[250690,489779],[-1198,-11]],[[252819,494643],[-1,-5695]],[[252818,488948],[3,-842],[-1344,42]],[[251477,488148],[-1,1639]],[[253828,488083],[-336,-5]],[[253492,488078],[2,861],[-676,9]],[[185140,489575],[-7,-6496]],[[185133,483079],[-5,-1541],[-325,-81],[-2,-1617],[-333,6],[-31,-3058],[-167,-6]],[[184270,476782],[-988,-2],[0,1537],[-762,6],[71,2286],[-253,1758],[-271,-297]],[[297950,485840],[-1609,2938],[-23,612]],[[289857,482936],[-1180,-3639],[-620,942]],[[288057,480239],[-51,4068]],[[289690,491904],[167,-8968]],[[222306,488875],[139,-1400],[-276,-1472],[-156,-2285],[239,-7],[215,-2925]],[[222467,480786],[-656,1]],[[221811,480787],[-251,2],[-1,4521],[-205,287],[-400,-1289],[-686,-69],[-402,533]],[[219866,484772],[-299,60]],[[219567,484832],[-76,2418],[-3,5989]],[[221692,493681],[38,-1692],[582,-1978],[-6,-1136]],[[231478,493758],[-2,-6467]],[[231476,487291],[-1083,9]],[[230393,487300],[2,6460]],[[233154,493749],[0,-6482]],[[233154,487267],[-1644,21]],[[231510,487288],[-34,3]],[[225159,493759],[1,-4821]],[[225160,488938],[-1327,-45]],[[223833,488893],[-332,-2]],[[223501,488891],[3,4867]],[[239886,493761],[1,-6484]],[[239887,487277],[-11,1]],[[239876,487278],[-995,-7]],[[238881,487271],[0,6484]],[[240909,493739],[-31,-6461]],[[240878,487278],[-991,-1]],[[230393,487300],[-1213,-24]],[[229180,487276],[-2,6464]],[[238881,487271],[-673,1]],[[238208,487272],[-333,-4]],[[223501,488891],[-1195,-16]],[[226503,493734],[-5,-6430]],[[226498,487304],[-323,15]],[[226175,487319],[-997,7]],[[225178,487326],[-18,1612]],[[242579,492091],[0,-4839]],[[242579,487252],[-1030,-242]],[[241549,487010],[-671,268]],[[234832,492108],[2,-4840]],[[234834,487268],[-1653,0]],[[233181,487268],[-27,-1]],[[229180,487276],[-670,-7]],[[228510,487269],[-670,12]],[[227840,487281],[-1,6446]],[[227840,487281],[-666,8]],[[227174,487289],[-676,15]],[[244402,489807],[393,-2555]],[[244795,487252],[-1244,-3]],[[243551,487249],[-972,3]],[[209213,487371],[2,-6521]],[[209215,480850],[-2355,-66]],[[206860,480784],[-501,-22]],[[259400,493210],[11,-6544]],[[259411,486666],[-1095,71]],[[258316,486737],[-227,4106],[347,2582]],[[304000,489648],[314,-1030]],[[304314,488618],[155,528]],[[304469,489146],[-41,-1011]],[[304428,488135],[-42,-1969],[-144,1439],[-555,-1235],[-307,-2322],[164,-2049],[-435,-545]],[[303109,481454],[-386,1325],[-161,2029],[-306,-177],[3,1605],[-347,397]],[[219567,484832],[-690,962],[-260,-1067],[-280,233],[-415,1974],[-376,206]],[[217546,487140],[1,2864]],[[200700,480796],[-1181,20]],[[199519,480816],[-168,0]],[[199351,480816],[0,2700],[-338,-12],[1,1631],[-670,1],[0,1587],[-984,520],[1,1055],[-333,2],[5,1651],[-345,1626]],[[304469,489146],[96,425],[146,-4224],[-211,-752],[-72,3540]],[[304747,485718],[-31,9]],[[304716,485727],[-143,4024],[63,808]],[[304866,488996],[-144,-864],[186,-1284],[-161,-1130]],[[304767,492694],[-187,-2521],[-266,-1555]],[[260730,493169],[6,-6502]],[[260736,486667],[-1325,-1]],[[262060,493156],[-5,-6526]],[[262055,486630],[-1319,37]],[[263395,493087],[5,-6428]],[[263400,486659],[-1345,-29]],[[264626,493111],[-1,-3070]],[[264625,490041],[-2,-3183]],[[264623,486858],[-1223,-199]],[[247503,485097],[-1667,-111]],[[245836,484986],[-8,6442],[-167,-12]],[[173946,486507],[-392,-271],[-361,1627]],[[292791,485342],[-439,-294],[152,-6649]],[[292504,478399],[55,-2189]],[[292559,476210],[-1099,-481],[-496,1080]],[[290964,476809],[-432,1000],[257,2733],[-219,10862]],[[236200,487264],[-1352,5]],[[234848,487269],[-14,-1]],[[178798,488945],[-51,-1480]],[[178747,487465],[-147,-2107],[27,-2295],[-141,-364],[-9,-7525]],[[178477,475174],[-1,-5281],[148,-48]],[[178624,469845],[-11,-2675]],[[178613,467170],[-1161,-3],[35,3027],[-978,384],[-398,937],[-84,-1083],[-654,1956],[-183,1187]],[[175190,473575],[801,17],[8,8897]],[[290964,476809],[48,-2086],[-179,-2358]],[[290833,472365],[-10,-3429]],[[290823,468936],[-322,652],[-79,-1360],[-540,1548],[-309,-528]],[[289573,469248],[-20,3211],[418,3255],[-260,632],[239,1293],[-93,5297]],[[245836,484986],[-965,56]],[[244871,485042],[-76,2210]],[[199351,480816],[0,-542],[-2070,-233],[-331,808],[-664,542],[-332,1359],[-995,270],[0,1479],[-394,2122]],[[187141,488746],[-18,-3230],[156,229],[838,-2596]],[[188117,483149],[-2489,-4]],[[185628,483145],[-495,-66]],[[169813,452809],[-3140,-58]],[[166673,452751],[-101,8]],[[166572,452759],[-14,14051],[-1606,-147],[6,8084],[116,22],[-2,8039]],[[199519,480816],[49,-535],[44,-12867],[-117,0],[4,-6462],[53,0]],[[199552,460952],[1,-3214]],[[199553,457738],[-4238,36]],[[195315,457774],[-81,8002],[-208,644],[-621,4049],[-508,1246],[-388,4712],[-175,3678],[-827,30]],[[265345,488432],[-13,-4364],[594,-1608]],[[265926,482460],[2,-2061]],[[265928,480399],[-330,807],[-651,39],[-327,828]],[[264620,482073],[3,4785]],[[264625,490041],[337,2],[2,-1583],[381,-28]],[[217546,487140],[-458,-847],[2,-7566]],[[217090,478727],[-2459,14]],[[214631,478741],[-79,1615],[0,4173]],[[184270,476782],[-1,-3235]],[[184269,473547],[-657,2],[18,-6493],[161,-1616],[-161,-936]],[[183630,464504],[-371,1135],[-285,-316]],[[182974,465323],[1,3350],[164,1],[-2,6526],[-838,4]],[[182299,475204],[-1840,-8]],[[180459,475196],[-55,2307],[-389,209],[74,2719],[-486,2041],[-29,3598],[-455,236],[-372,1159]],[[189738,480812],[-430,715],[-125,1527],[-435,6]],[[188748,483060],[3,5605]],[[251477,488148],[-1,-4871]],[[251476,483277],[-337,-10]],[[251139,483267],[-664,187]],[[250475,483454],[215,6325]],[[250475,483454],[-987,-9]],[[297604,482655],[-139,-531],[-150,-6298]],[[297315,475826],[-695,-198],[-380,612]],[[296240,476240],[-135,842]],[[296105,477082],[40,1310],[261,-316],[91,2133],[-178,490],[251,3496],[-332,633],[104,1629],[-255,1002],[-235,-588]],[[158850,479682],[187,-616],[253,-2714],[-330,-3406],[-529,-206],[-3,-1355]],[[158428,471385],[-330,3],[-438,-1061],[-659,-2952],[-995,-473],[-214,-851]],[[155792,466051],[-606,-122],[-747,708],[-3,820]],[[154436,467457],[-267,105]],[[154169,467562],[-22,3823],[330,1722],[-6,3184],[-170,-24],[1,3264],[-310,1556],[0,1719],[-955,38]],[[153037,482844],[167,4714]],[[253492,488078],[4,-6488]],[[253496,481590],[-667,11]],[[252829,481601],[-2,1626],[-1351,50]],[[225178,487326],[13,-6561]],[[225191,480765],[-1074,13]],[[224117,480778],[-247,-3]],[[223870,480775],[-37,8118]],[[223870,480775],[-1403,11]],[[188748,483060],[-631,89]],[[254690,488092],[83,-3796],[-251,-2704]],[[254522,481592],[-691,-13]],[[253831,481579],[-335,11]],[[174503,486508],[-1,-3222],[108,-1],[2,-3245],[-111,-9],[5,-3147]],[[174506,476884],[-750,3480],[-170,2161],[-512,1621]],[[219866,484772],[49,-6023]],[[219915,478749],[-2825,-22]],[[180459,475196],[-612,-24]],[[179847,475172],[-1370,2]],[[296105,477082],[-1078,260]],[[295027,477342],[17,4427],[-151,1362],[-345,331],[179,2038]],[[212682,484293],[-532,-1540],[1,-2394]],[[212151,480359],[-2937,18]],[[209214,480377],[1,473]],[[231510,487288],[-2,-6495]],[[231508,480793],[-1114,5]],[[230394,480798],[-1,6502]],[[233181,487268],[-6,-6467]],[[233175,480801],[-1132,-10]],[[232043,480791],[-535,2]],[[239876,487278],[3,-6493]],[[239879,480785],[-1250,-4]],[[238629,480781],[-422,3]],[[238207,480784],[1,6488]],[[241549,487010],[0,-6211]],[[241549,480799],[-290,-4]],[[241259,480795],[-1313,-10]],[[239946,480785],[-67,0]],[[244871,485042],[-34,-2081],[143,-2160]],[[244980,480801],[-1096,2]],[[243884,480803],[-334,1]],[[243550,480804],[1,6445]],[[226175,487319],[6,-6546]],[[226181,480773],[-417,-14]],[[225764,480759],[-573,6]],[[227174,487289],[5,-6502]],[[227179,480787],[-998,-14]],[[243550,480804],[-972,-1]],[[242578,480803],[-1029,-4]],[[234848,487269],[-4,-6462]],[[234844,480807],[-164,-8]],[[234680,480799],[-1319,-4]],[[233361,480795],[-186,6]],[[238207,480784],[-898,-1]],[[237309,480783],[-772,10]],[[236537,480793],[3,6472]],[[230394,480798],[-407,0]],[[229987,480798],[-908,-8]],[[229079,480790],[-570,-5]],[[228509,480785],[1,6484]],[[236537,480793],[-543,5]],[[235994,480798],[-1150,9]],[[228509,480785],[-758,1]],[[227751,480786],[-572,1]],[[264620,482073],[-6,-1618],[-556,-297]],[[264058,480158],[-662,0]],[[263396,480158],[4,6501]],[[259411,486666],[-4,-6482]],[[259407,480184],[-1174,91]],[[258233,480275],[-220,3394],[303,3068]],[[263396,480158],[-664,2]],[[262732,480160],[-666,1]],[[262066,480161],[-11,6469]],[[303109,481454],[-237,-3363],[-309,-544],[-343,-4752],[-437,1186]],[[301783,473981],[56,1698],[-493,2897],[67,2978]],[[262066,480161],[-1330,38]],[[260736,480199],[0,6468]],[[260736,480199],[0,-3239]],[[260736,476960],[-635,-23]],[[260101,476937],[-693,9],[-1,3238]],[[295027,477342],[-54,-6909]],[[294973,470433],[-1008,-44]],[[293965,470389],[167,2590],[-54,4202]],[[294078,477181],[-94,3357],[378,3143],[153,2763]],[[175190,473575],[-334,1506],[-129,1914],[-221,-111]],[[294078,477181],[-671,-975],[-134,2686],[-769,-493]],[[221811,480787],[-2,-9342]],[[221809,471445],[-1850,12]],[[219959,471457],[-44,7292]],[[300852,481523],[10,-3416],[-211,-1324]],[[300651,476783],[-895,3174],[-123,-380],[-353,2396]],[[266573,485031],[2,-1115],[972,142]],[[267547,484058],[-7,-6487]],[[267540,477571],[-652,-90],[7,-1620],[-304,-225]],[[266591,475636],[-654,-32]],[[265937,475604],[-9,4795]],[[265926,482460],[509,2703],[138,-132]],[[247502,483415],[4,-1619]],[[247506,481796],[-1005,-19],[9,-2420]],[[246510,479357],[-1496,-1]],[[245014,479356],[-34,1445]],[[286839,484139],[-17,6]],[[288057,480239],[-163,246],[-201,-2952],[7,-3154]],[[287700,474379],[-304,517]],[[287396,474896],[-580,1634],[-156,-692],[-618,-120]],[[286042,475718],[-350,485],[-33,3060]],[[285659,479263],[575,1959],[489,118],[146,2788]],[[214631,478741],[5,-4840],[73,-2431]],[[214709,471470],[-1977,12]],[[212732,471482],[-581,14]],[[212151,471496],[0,8863]],[[269259,474627],[-1374,-276]],[[267885,474351],[-11,2435],[-334,785]],[[267547,484058],[1423,278]],[[268970,484336],[189,-4720],[100,-4989]],[[178613,467170],[-2,-14381]],[[178611,452789],[-3086,40],[-2428,19]],[[173097,452848],[-25,13]],[[251139,483267],[-6,-6482]],[[251133,476785],[-1981,150]],[[249152,476935],[333,1631],[5,3332],[-358,175],[-163,1343]],[[249152,476935],[-329,-1617]],[[248823,475318],[-991,-774]],[[247832,474544],[6,7270],[-332,-18]],[[252829,481601],[-49,-6494]],[[252780,475107],[-328,25]],[[252452,475132],[-1318,30]],[[251134,475162],[-1,1623]],[[189740,477360],[2,-5505]],[[189742,471855],[-1522,-1]],[[188220,471854],[5,4870],[-654,10],[0,1563],[-614,10],[-169,1087],[-1161,0],[1,3751]],[[188220,471854],[-1319,43]],[[186901,471897],[-1652,-8],[-264,-1313]],[[184985,470576],[-183,-1641],[-532,-4],[-1,4616]],[[161076,482939],[-46,-16196],[1299,-29],[10,-13964]],[[162339,452750],[-1582,69]],[[160757,452819],[-2346,191]],[[158411,453010],[17,18375]],[[166572,452759],[-1780,10]],[[164792,452769],[-2453,-19]],[[289573,469248],[-100,-158]],[[289473,469090],[-527,-153],[-20,1299],[-279,-24],[-23,1969],[-519,2369],[-405,-171]],[[300651,476783],[-32,-196]],[[300619,476587],[-404,-4938]],[[300215,471649],[-352,1116],[-342,-274],[-74,1832],[-524,-693],[-159,1711],[-267,-361]],[[298497,474980],[-226,2507],[101,2207],[330,1626]],[[154169,467562],[-316,-270],[-199,-1662],[-396,-276],[-61,3940],[-268,1346],[-619,4]],[[152310,470644],[269,5889],[458,6311]],[[298497,474980],[-81,-1100]],[[298416,473880],[-391,59],[61,931],[-822,-384]],[[297264,474486],[51,1340]],[[301783,473981],[-163,-963]],[[301620,473018],[-513,-14],[127,1049],[-615,2534]],[[265937,475604],[-658,-19],[7,-1631]],[[265286,473954],[-1222,-81]],[[264064,473873],[-6,6285]],[[247832,474544],[-657,679]],[[247175,475223],[-660,-543]],[[246515,474680],[-5,4677]],[[254522,481592],[-329,-5331],[50,-1204]],[[254243,475057],[-476,2]],[[253767,475059],[64,6520]],[[253767,475059],[-987,48]],[[238629,480781],[-1,-4543]],[[238628,476238],[-1318,-2]],[[237310,476236],[-1,4547]],[[237308,469765],[-1314,6]],[[235994,469771],[-1,6459]],[[235993,476230],[1,4568]],[[237310,476236],[-2,-6471]],[[234682,476230],[-1321,3]],[[233361,476233],[0,4562]],[[234680,480799],[2,-4569]],[[235993,476230],[-1311,0]],[[239947,476239],[-1319,-1]],[[239946,480785],[1,-4546]],[[233361,476233],[-1320,44]],[[232041,476277],[2,4514]],[[209214,480377],[5,-8812]],[[209219,471565],[1,-7313]],[[209220,464252],[-1680,-40]],[[207540,464212],[-659,-10]],[[206881,464202],[-21,16582]],[[241256,475444],[-1309,0]],[[239947,475444],[0,795]],[[241259,480795],[-3,-5351]],[[232041,476277],[-1932,42]],[[230109,476319],[87,2489],[-209,1990]],[[242578,480803],[-3,-5357]],[[242575,475446],[-1319,-2]],[[245014,479356],[4,-1361],[405,-1716],[-330,-3303]],[[245093,472976],[-1193,26]],[[243900,473002],[-16,7801]],[[243900,473002],[-1326,21]],[[242574,473023],[1,2423]],[[230109,476319],[223,-746],[59,-2539]],[[230391,473034],[-980,6]],[[229411,473040],[-330,1]],[[229081,473041],[-2,7749]],[[206881,464202],[-1098,-58],[7,-3247]],[[205790,460897],[-134,-6],[-121,-2624],[-664,266],[161,2348],[-1442,50]],[[203590,460931],[-6,-1]],[[203584,460930],[-7,19758]],[[224117,480778],[91,-2392],[1571,-3239]],[[225779,475147],[90,-531]],[[225869,474616],[-47,-3529],[-165,-2602]],[[225657,468485],[-436,812]],[[225221,469297],[-526,2161]],[[224695,471458],[-1032,3040],[-78,1581],[-470,1324],[-243,2027],[-405,1356]],[[229081,473041],[-659,-11]],[[228422,473030],[0,1617],[-664,-11]],[[227758,474636],[-7,6150]],[[227758,474636],[-664,-12]],[[227094,474624],[-1225,-8]],[[225779,475147],[-15,5612]],[[203584,460930],[-1530,-50],[-2502,72]],[[224695,471458],[-2109,-11]],[[222586,471447],[-777,-2]],[[212151,471496],[-1401,0]],[[210750,471496],[-1531,69]],[[260101,476937],[-1,-1635]],[[260100,475302],[-328,14],[3,-1620],[-1016,-10]],[[258759,473686],[-526,6589]],[[262732,480160],[25,-6465]],[[262757,473695],[-1325,-1]],[[261432,473694],[1,3265],[-697,1]],[[195315,457774],[-2814,132]],[[192501,457906],[-1359,125],[-5,3185],[-131,-2],[32,14647]],[[264064,473873],[0,-195]],[[264064,473678],[-1307,17]],[[246515,474680],[-1111,-3346],[-255,-72]],[[245149,471262],[-56,1714]],[[286042,475718],[-35,-4144],[115,-2963],[261,68],[229,-1455]],[[286612,467224],[26,-2750]],[[286638,464474],[-1117,-2]],[[285521,464472],[-187,1944],[55,5513]],[[285389,471929],[-24,5904]],[[285365,477833],[294,1430]],[[293965,470389],[-115,-2942]],[[293850,467447],[-369,-82]],[[293481,467365],[-266,2463],[-523,831]],[[292692,470659],[-14,505]],[[292678,471164],[-119,5046]],[[217090,478727],[0,-7284]],[[217090,471443],[-2381,27]],[[219959,471457],[-2869,-14]],[[281818,478275],[-5,-4317]],[[281813,473958],[-1305,-81]],[[280508,473877],[0,4511]],[[280508,478388],[1310,-113]],[[280508,473877],[4,-838]],[[280512,473039],[-752,7],[-414,-1124]],[[279346,471922],[-518,1158],[12,3306],[719,1122],[949,880]],[[283548,476628],[9,-4500]],[[283557,472128],[-318,-2],[8,-1702],[-272,15]],[[282975,470439],[-429,33],[9,794],[-498,-12]],[[282057,471254],[10,2715],[-254,-11]],[[281818,478275],[657,-443],[527,-1887],[546,683]],[[285389,471929],[-697,-202]],[[284692,471727],[-474,498],[-661,-97]],[[283548,476628],[635,251],[429,-526],[753,1480]],[[267885,474351],[36,-4847]],[[267921,469504],[-333,-95]],[[267588,469409],[-976,-153]],[[266612,469256],[-21,6380]],[[192501,457906],[16,-12908]],[[192517,444998],[-2780,45]],[[189737,445043],[-3,7851]],[[189734,452894],[0,9524]],[[189734,462418],[8,9437]],[[296240,476240],[-137,-2650],[-357,111],[-31,-3519],[241,-61],[-25,-3493]],[[295931,466628],[-259,36]],[[295672,466664],[-674,89]],[[294998,466753],[-25,3680]],[[251134,475162],[-13,-6522]],[[251121,468640],[-991,-43]],[[250130,468597],[-1307,230]],[[248823,468827],[0,6491]],[[261432,473694],[8,-6496]],[[261440,467198],[-657,-32]],[[260783,467166],[-660,1]],[[260123,467167],[-23,8135]],[[292678,471164],[-1239,48],[-606,1153]],[[301620,473018],[189,655],[301,-1389],[-295,-3187]],[[301815,469097],[-594,-238],[-94,-983],[-503,-1186]],[[300624,466690],[-506,1532],[-67,1970],[164,1457]],[[287396,474896],[215,-2006],[56,-5300]],[[287667,467590],[-1055,-366]],[[297264,474486],[21,-2797],[-310,-2919],[270,-2371]],[[297245,466399],[-1314,229]],[[232041,476277],[7,-6487]],[[232048,469790],[-1892,-6]],[[230156,469784],[78,2629],[157,621]],[[233361,476233],[0,-6428]],[[233361,469805],[-1313,-15]],[[239947,475444],[-3,-5679]],[[239944,469765],[-1319,14]],[[238625,469779],[3,6459]],[[238625,469779],[-1317,-14]],[[235994,469771],[-1312,30]],[[234682,469801],[0,6429]],[[234682,469801],[-1321,4]],[[266612,469256],[-658,-171],[8,-1638]],[[265962,467447],[-657,-45]],[[265305,467402],[-19,6552]],[[242574,473023],[-1,-3272]],[[242573,469751],[-1317,2]],[[241256,469753],[0,5691]],[[241256,469753],[-1312,12]],[[248823,468827],[0,-811]],[[248823,468016],[-1640,-18]],[[247183,467998],[-8,7225]],[[247183,467998],[1,-5693]],[[247184,462305],[-597,23]],[[246587,462328],[-172,2280],[-542,826]],[[245873,465434],[-469,1434],[-255,4394]],[[300624,466690],[-29,-121]],[[300595,466569],[-107,-728],[-1684,269]],[[298804,466110],[-84,15]],[[298720,466125],[-215,4313],[-157,17],[68,3425]],[[260123,467167],[-1187,-13]],[[258936,467154],[-49,4105],[-128,2427]],[[182299,475204],[2,-6521],[-141,-1591],[-469,14]],[[181691,467106],[-326,810],[-824,10],[-55,777],[-638,0]],[[179848,468703],[-1,6469]],[[179848,468703],[-62,-3750]],[[179786,464953],[-566,398],[-337,2246],[112,1245],[-371,1003]],[[252452,475132],[-16,-6574]],[[252436,468558],[-656,-5]],[[251780,468553],[-659,87]],[[182974,465323],[-238,-239],[-275,-2258],[-767,3]],[[181694,462829],[-3,4277]],[[253767,475059],[-17,-6494]],[[253750,468565],[-659,-23]],[[253091,468542],[-655,16]],[[254243,475057],[184,-6512]],[[254427,468545],[-677,20]],[[289473,469090],[-133,-2371]],[[289340,466719],[-1655,-378]],[[287685,466341],[-18,1249]],[[298720,466125],[-986,173]],[[297734,466298],[-489,101]],[[228422,473030],[1,-5277]],[[228423,467753],[-411,1263],[-493,-320]],[[227519,468696],[-420,21]],[[227099,468717],[-5,5907]],[[227099,468717],[-647,152],[-239,-1688],[-225,-92],[-331,1396]],[[265305,467402],[-657,-78]],[[264648,467324],[-572,-16]],[[264076,467308],[-12,6370]],[[282057,471254],[-126,-2331]],[[281931,468923],[-1419,92]],[[280512,469015],[0,4024]],[[262757,473695],[0,-6486]],[[262757,467209],[-661,6]],[[262096,467215],[-656,-17]],[[264076,467308],[-666,-110]],[[263410,467198],[-653,11]],[[184985,470576],[7,-702],[463,1104],[183,-1173],[54,-3295],[326,-1616],[-112,-2669]],[[185906,462225],[-218,4],[1,-1621],[-435,0],[0,-1622],[-966,-26]],[[184288,458960],[-6,4851],[-652,693]],[[279029,472686],[285,-724],[-124,-1251],[-161,1975]],[[280512,469015],[1,-6165]],[[280513,462850],[-1172,-1823],[-491,1851]],[[278850,462878],[-210,586]],[[278640,463464],[245,2249],[543,1799],[-163,3148],[81,1262]],[[230156,469784],[-257,-2656],[18,-1098],[319,-1424],[30,-1300]],[[230266,463306],[144,-1321]],[[230410,461985],[-513,642]],[[229897,462627],[-186,2346],[-301,1001]],[[229410,465974],[1,7066]],[[229410,465974],[-586,1070]],[[228824,467044],[-401,709]],[[243900,473002],[-5,-8148]],[[243895,464854],[-1322,-36]],[[242573,464818],[0,4933]],[[245873,465434],[-1,-548],[-656,3]],[[245216,464889],[-1321,-35]],[[292692,470659],[24,-1088],[-500,-1877]],[[292216,467694],[-534,-438],[-538,1053]],[[291144,468309],[-321,627]],[[284692,471727],[-24,-4635]],[[284668,467092],[-949,-54],[-149,-3443]],[[283570,463595],[-344,17]],[[283226,463612],[9,1732],[-312,31],[52,5064]],[[186901,471897],[-242,-2320],[147,-3747],[355,398],[147,-1874],[115,-3739]],[[187423,460615],[-60,-2973],[-639,534]],[[186724,458176],[-179,1226],[-381,-494],[84,2410],[-342,907]],[[189734,462418],[-524,-640],[35,1742],[-1063,342],[13,-3257]],[[188195,460605],[-772,10]],[[285521,464472],[224,-1369],[-308,-60]],[[285437,463043],[-555,-96]],[[284882,462947],[2,2099],[-216,2046]],[[210915,461007],[1,-6453],[118,-1623]],[[211034,452931],[-1814,-34]],[[209220,452897],[0,11355]],[[210750,471496],[65,-4017],[1,-6473],[99,1]],[[214922,454659],[-168,-1623]],[[214754,453036],[-1702,-72]],[[213052,452964],[-57,-9]],[[212995,452955],[-124,1630],[-20,6470],[-66,0]],[[212785,461055],[0,6429],[-53,3998]],[[214709,471470],[45,-10349],[76,-3],[-3,-6452],[95,-7]],[[212785,461055],[-1870,-48]],[[225221,469297],[13,-2261]],[[225234,467036],[-777,345],[-862,1235],[-166,869],[-848,-1646]],[[222581,467839],[5,3608]],[[222581,467839],[-781,-1614],[-391,247]],[[221409,466472],[-1271,1432],[-179,713]],[[219959,468617],[0,2840]],[[219959,468617],[86,-14154]],[[220045,454463],[-279,6]],[[219766,454469],[-1612,38]],[[218154,454507],[-1617,75]],[[216537,454582],[-1615,77]],[[158411,453010],[-2621,-73]],[[155790,452937],[2,13114]],[[283226,463612],[-473,57],[-176,-2031]],[[282577,461638],[-328,866],[-551,60]],[[281698,462564],[229,2735],[4,3624]],[[294998,466753],[-244,-4393]],[[294754,462360],[-1202,-852]],[[293552,461508],[61,2653],[237,3286]],[[293481,467365],[-1033,-910]],[[292448,466455],[-257,-292],[25,1531]],[[154436,467457],[-322,-2815],[-164,-2462],[-328,-79],[-48,-2548],[372,-119],[282,-2180],[-184,-1761],[99,-2712]],[[154143,452781],[-1087,56]],[[153056,452837],[-394,1953],[-208,4302],[105,4473],[-70,1599],[-265,1356],[-113,2029],[199,2095]],[[179786,464953],[673,-952],[683,-1920],[360,583]],[[181502,462664],[-185,-222],[-6,-1813],[-601,5],[9,-7878]],[[180719,452756],[-2108,33]],[[232048,469790],[-2,-6488]],[[232046,463302],[-1780,4]],[[234682,469801],[-2,-6505]],[[234680,463296],[-1319,27]],[[233361,463323],[0,6482]],[[233361,463323],[-985,-8]],[[232376,463315],[-330,-13]],[[235994,469771],[0,-4895]],[[235994,464876],[0,-1607]],[[235994,463269],[-1314,27]],[[238625,469779],[-2,-6531]],[[238623,463248],[-1316,8]],[[237307,463256],[0,1612]],[[237307,464868],[1,4897]],[[237307,464868],[-1313,8]],[[239944,469765],[-5,-6533]],[[239939,463232],[-1316,16]],[[241256,469753],[-1,-4930]],[[241255,464823],[1,-1610]],[[241256,463213],[-1317,19]],[[242573,464818],[-1318,5]],[[291144,468309],[49,-3777],[-226,-2027]],[[290967,462505],[-369,-97],[-984,-2871],[-607,-823]],[[289007,458714],[112,1789],[-67,1881],[190,1044],[98,3291]],[[225234,467036],[13,-6037]],[[225247,460999],[-1,-6480]],[[225246,454519],[-1283,-6]],[[223963,454513],[-1286,-11]],[[222677,454502],[-33,1]],[[222644,454503],[-31,12930],[-32,406]],[[268694,465642],[-316,-1304],[-142,-3077]],[[268236,461261],[-593,-71]],[[267643,461190],[-55,8219]],[[267921,469504],[681,71],[92,-3933]],[[267643,461190],[-1306,-223]],[[266337,460967],[-314,-75]],[[266023,460892],[-61,6555]],[[301815,469097],[129,-3011],[504,-1290],[-172,-917],[-656,-1010],[-20,-1435],[-247,-532]],[[301353,460902],[-115,246]],[[301238,461148],[-90,2974],[-550,962],[-3,1485]],[[227519,468696],[-1,-7666]],[[227518,461030],[-973,-16]],[[226545,461014],[-1298,-15]],[[228824,467044],[-4,-7638]],[[228820,459406],[-975,10]],[[227845,459416],[1,1618],[-328,-4]],[[281698,462564],[-753,1]],[[280945,462565],[-432,285]],[[250130,468597],[9,-6418]],[[250139,462179],[-99,3]],[[250040,462182],[-1215,98]],[[248825,462280],[-2,5736]],[[181694,462829],[-192,-165]],[[251780,468553],[1,-6528]],[[251781,462025],[-456,57]],[[251325,462082],[-1186,97]],[[221409,466472],[43,-12006]],[[221452,454466],[-68,2]],[[221384,454468],[-1339,-5]],[[253091,468542],[1,-4305]],[[253092,464237],[4,-2164]],[[253096,462073],[-1122,-19]],[[251974,462054],[-193,-29]],[[254427,468545],[185,-1078],[-127,-2165]],[[254485,465302],[-1064,31],[0,-1088],[-329,-8]],[[292448,466455],[-171,-3336],[-36,-2654]],[[292241,460465],[-527,-990]],[[291714,459475],[-489,1291],[-258,1739]],[[293552,461508],[-1311,-1043]],[[248825,462280],[-247,1]],[[248578,462281],[-1394,24]],[[222644,454503],[-1192,-37]],[[287015,460504],[-343,-51]],[[286672,460453],[-34,4021]],[[287685,466341],[71,-5737]],[[287756,460604],[-741,-100]],[[155790,452937],[-801,-56]],[[154989,452881],[-846,-100]],[[266023,460892],[-1299,-123]],[[264724,460769],[-27,1]],[[264697,460770],[-49,6554]],[[264697,460770],[-1281,-49]],[[263416,460721],[-6,6477]],[[263416,460721],[-329,-8]],[[263087,460713],[-984,-2]],[[262103,460711],[-7,6504]],[[262103,460711],[-633,-30]],[[261470,460681],[-681,30]],[[260789,460711],[-6,6455]],[[260789,460711],[-616,-16]],[[260173,460695],[-1419,-24]],[[258754,460671],[182,6483]],[[284882,462947],[17,-1461],[-606,385]],[[284293,461871],[-102,1734],[-621,-10]],[[229897,462627],[-253,6],[-13,-4592]],[[229631,458041],[-267,-255]],[[229364,457786],[-544,3],[0,1617]],[[295672,466664],[201,-1870],[-69,-1579]],[[295804,463215],[-69,-4535]],[[295735,458680],[-148,-5073]],[[295587,453607],[-1209,182]],[[294378,453789],[-26,1]],[[294352,453790],[-31,692],[433,7878]],[[289007,458714],[-9,-2218]],[[288998,456496],[-609,11],[-4,988],[-572,205],[-57,2904]],[[297645,459261],[-169,1431],[-916,-277],[9,882],[-474,582],[-291,1336]],[[297734,466298],[148,-1092],[-43,-2780],[-194,-3165]],[[301238,461148],[-70,-969]],[[301168,460179],[-67,-41]],[[301101,460138],[28,-398]],[[301129,459740],[-2,-4]],[[301127,459736],[-256,-725]],[[300871,459011],[-22,-492]],[[300849,458519],[-74,-391]],[[300775,458128],[-385,553],[-40,-2081],[-373,-818]],[[299977,455782],[-294,713],[-11,1199],[281,1311],[-388,367],[185,1163],[57,2437],[-390,-234],[39,1513],[-540,411],[-112,1448]],[[299977,455782],[-56,-2597]],[[299921,453185],[-840,-170]],[[299081,453015],[-844,386]],[[298237,453401],[-93,27]],[[298144,453428],[2,2448],[-359,413],[119,1141]],[[297906,457430],[27,1226],[-288,605]],[[184288,458960],[0,-6128]],[[184288,452832],[-2898,-85]],[[181390,452747],[-671,9]],[[246587,462328],[-37,-555],[502,-1806]],[[247052,459967],[-532,14],[-2,-1629],[-648,10]],[[245870,458362],[-646,9]],[[245224,458371],[-8,6518]],[[254485,465302],[3,-3266]],[[254488,462036],[-1092,47]],[[253396,462083],[-300,-10]],[[245224,458371],[-650,13]],[[244574,458384],[-651,-1]],[[243923,458383],[-28,6471]],[[237307,463256],[111,-1603],[1,-4876]],[[237419,456777],[-650,-3]],[[236769,456774],[-649,-15]],[[236120,456759],[0,4918],[-126,1592]],[[243923,458383],[-652,50]],[[243271,458433],[-651,-34]],[[242620,458399],[-47,6419]],[[242620,458399],[-652,5]],[[241968,458404],[-654,-10]],[[241314,458394],[-58,4819]],[[286672,460453],[9,-2065],[-460,404],[-3,-1031],[-340,350]],[[285878,458111],[-226,20]],[[285652,458131],[-201,28],[-14,4884]],[[209220,452897],[0,-5655]],[[209220,447242],[1,-2489]],[[209221,444753],[-1673,14],[-1,1638]],[[207547,446405],[-7,17807]],[[207547,446405],[-1741,68]],[[205806,446473],[-16,14424]],[[189734,452894],[-1285,-39]],[[188449,452855],[-244,1757],[-87,2140],[178,1265],[-101,2588]],[[284293,461871],[21,-3935],[375,114]],[[284689,458050],[-2,-5187]],[[284687,452863],[-1795,-1]],[[282892,452862],[-390,-48]],[[282502,452814],[75,8824]],[[278850,462878],[-1,-10015]],[[278849,452863],[-1529,-47]],[[277320,452816],[-423,27],[0,5035]],[[276897,457878],[602,1757],[633,2570],[508,1259]],[[232376,463315],[-1,-1600],[202,-13],[-4,-4901]],[[232573,456801],[-1911,67]],[[230662,456868],[1,1145]],[[230663,458013],[-154,1202],[39,2678],[-138,92]],[[234680,463296],[161,-1607],[-5,-4920]],[[234836,456769],[-647,13]],[[234189,456782],[-648,10]],[[233541,456792],[3,4901],[-183,1630]],[[233541,456792],[-968,9]],[[236120,456759],[-644,5]],[[235476,456764],[-640,5]],[[238623,463248],[102,-1618],[-1,-4868]],[[238724,456762],[-656,0]],[[238068,456762],[-649,15]],[[239939,463232],[70,-6471]],[[240009,456761],[-640,-7]],[[239369,456754],[-645,8]],[[241314,458394],[-654,-6],[2,-1611]],[[240662,456777],[-653,-16]],[[297906,457430],[-486,-1106],[-24,857],[-523,-375],[-118,1160],[-154,-1681],[-621,1035],[76,1737],[-321,-377]],[[285652,458131],[-963,-81]],[[280945,462565],[4,-9702]],[[280949,452863],[-1704,-50]],[[279245,452813],[-396,50]],[[230663,458013],[-1032,28]],[[282502,452814],[-1272,49]],[[281230,452863],[-281,0]],[[291714,459475],[-261,-2860],[238,-599]],[[291691,456016],[-916,-2846]],[[290775,453170],[-1003,-3057]],[[289772,450113],[-329,184],[-290,2566]],[[289153,452863],[-165,772],[10,2861]],[[248578,462281],[18,-5761]],[[248596,456520],[-1109,-47]],[[247487,456473],[-210,622],[-225,2872]],[[294352,453790],[-85,-1335],[-514,519],[-607,1358]],[[293146,454332],[53,903]],[[293199,455235],[352,2852],[1,3421]],[[249240,456572],[-644,-52]],[[250040,462182],[14,-5553]],[[250054,456629],[-814,-57]],[[251325,462082],[1,-6381]],[[251326,455701],[-650,-38],[2,1005],[-624,-39]],[[186724,458176],[47,-5357]],[[186771,452819],[-155,1]],[[186616,452820],[-2328,12]],[[253396,462083],[-8,-6346]],[[253388,455737],[-108,0]],[[253280,455737],[-976,-13]],[[252304,455724],[-325,0]],[[251979,455724],[-5,6330]],[[251979,455724],[-653,-23]],[[254614,455702],[-1226,35]],[[254488,462036],[-82,-3532],[208,-2802]],[[293199,455235],[-367,802],[-92,-1373],[-647,330],[-402,1022]],[[301127,459736],[69,-1616]],[[301196,458120],[-253,-1012],[-168,1020]],[[300849,458519],[22,492]],[[301101,460138],[28,-398]],[[301353,460902],[-185,-723]],[[227845,459416],[-2,-4858]],[[227843,454558],[-1298,-22]],[[226545,454536],[0,6478]],[[212995,452955],[-1853,-35]],[[211142,452920],[-108,11]],[[226545,454536],[3,-3229]],[[226548,451307],[-1288,-25]],[[225260,451282],[-14,3237]],[[266372,454459],[-653,-59]],[[265719,454400],[-998,-202]],[[264721,454198],[3,6571]],[[266337,460967],[35,-6508]],[[203590,460931],[14,-19328],[-709,2],[8,-7373]],[[202903,434232],[-1501,66]],[[201402,434298],[-1279,5]],[[200123,434303],[-1673,-17]],[[198450,434286],[-31,12242],[1170,-40],[16,8015],[-52,3235]],[[205806,446473],[3,-12257]],[[205809,434216],[-2545,-6]],[[203264,434210],[-361,22]],[[188449,452855],[-1678,-36]],[[264721,454198],[-644,35]],[[264077,454233],[-965,-58]],[[263112,454175],[-25,6538]],[[263112,454175],[-326,39]],[[262786,454214],[-1301,-17]],[[261485,454197],[-15,6484]],[[260173,460695],[4,-6539]],[[260177,454156],[-1281,40]],[[258896,454196],[-2,3200],[-392,-4]],[[258502,457392],[252,3279]],[[261485,454197],[-1308,-41]],[[289153,452863],[-334,-23]],[[288819,452840],[-1736,23]],[[287083,452863],[-29,3458],[98,835],[-137,3348]],[[287083,452863],[-115,0]],[[286968,452863],[-1145,0]],[[285823,452863],[55,5248]],[[247487,456473],[432,-1435],[26,-1576]],[[247945,453462],[-2076,28]],[[245869,453490],[1,4872]],[[229364,457786],[-3,-3238]],[[229361,454548],[-545,3]],[[228816,454551],[-973,7]],[[301793,457791],[116,-445]],[[301909,457346],[-116,445]],[[301196,458120],[304,-647]],[[301500,457473],[18,-1676],[-433,-1150]],[[301085,454647],[-839,-2061]],[[300246,452586],[-325,599]],[[298144,453428],[-1041,73]],[[297103,453501],[-1391,88]],[[295712,453589],[-125,18]],[[301909,457346],[403,-2846],[-196,-1500],[475,-1515],[5,-2151]],[[302596,449334],[-234,-1189]],[[302362,448145],[-280,-204],[-97,-1527],[-241,-453]],[[301744,445961],[-218,3011],[-291,-217],[146,1519],[-214,1940],[-82,2433]],[[301500,457473],[293,318]],[[243271,458433],[-1,-8140]],[[243270,450293],[-1301,18]],[[241969,450311],[-1,8093]],[[244574,458384],[-7,-6498]],[[244567,451886],[-1,-1626]],[[244566,450260],[-1296,33]],[[241969,450311],[-1303,-7]],[[240666,450304],[-4,6473]],[[245869,453490],[0,-1629]],[[245869,451861],[-1302,25]],[[285823,452863],[-1027,0]],[[284796,452863],[-109,0]],[[230662,456868],[249,-1941],[-15,-1182]],[[230896,453745],[-107,-597],[-679,13]],[[230110,453161],[-2,1379],[-747,8]],[[198450,434286],[-3153,-25]],[[195297,434261],[-2648,-56]],[[192649,434205],[-133,0]],[[192516,434205],[1,10793]],[[277320,452816],[-6,-2742]],[[277314,450074],[-2528,-14]],[[274786,450060],[0,2383]],[[274786,452443],[526,1090],[1585,4345]],[[258896,454196],[-9,-5791]],[[258887,448405],[-831,-14]],[[258056,448391],[-837,12]],[[257219,448403],[579,2539],[358,3965],[346,2485]],[[232573,456801],[-5,-6484]],[[232568,450317],[-1299,59]],[[231269,450376],[19,2011],[-392,1358]],[[240666,450304],[-1298,5]],[[239368,450309],[1,6445]],[[235476,456764],[0,-6454]],[[235476,450310],[-323,-7]],[[235153,450303],[-970,19]],[[234183,450322],[6,6460]],[[234183,450322],[-1294,1]],[[232889,450323],[-321,-6]],[[238068,456762],[1,-6440]],[[238069,450322],[-327,1]],[[237742,450323],[-971,-4]],[[236771,450319],[-2,6455]],[[236771,450319],[-324,-4]],[[236447,450315],[-971,-5]],[[239368,450309],[-323,8]],[[239045,450317],[-976,5]],[[251326,455701],[-4,-4851]],[[251322,450850],[-1168,-69],[-750,255]],[[249404,451036],[-159,538]],[[249245,451574],[-5,4998]],[[249245,451574],[-1296,-32]],[[247949,451542],[-4,1920]],[[293146,454332],[-66,-9100]],[[293080,445232],[-482,-138],[-384,934],[-288,-777]],[[291926,445251],[-77,1003],[-501,1869],[338,2426],[-911,2621]],[[254614,455702],[218,-1446],[203,-4308],[236,-2529]],[[255271,447419],[-5,-4485]],[[255266,442934],[-738,66],[-9,1648],[-322,-35],[-7,1623],[-327,775]],[[253863,447011],[320,581],[-17,5162],[-954,-145]],[[253212,452609],[68,3128]],[[252304,455724],[-36,-8080]],[[252268,447644],[-1,-1641]],[[252267,446003],[-938,-57]],[[251329,445946],[-7,4904]],[[253212,452609],[3,-4870]],[[253215,447739],[-947,-95]],[[216537,454582],[2,-6500]],[[216539,448082],[-1559,-14]],[[214980,448068],[-230,-4],[4,4972]],[[301744,445961],[-236,-1674],[-533,-780]],[[300975,443507],[-36,3035],[-176,276]],[[300763,446818],[-87,707]],[[300676,447525],[-253,1172]],[[300423,448697],[-177,3889]],[[218154,454507],[10,-6475]],[[218164,448032],[-1571,52]],[[216593,448084],[-54,-2]],[[230110,453161],[-1,-5099]],[[230109,448062],[-977,14]],[[229132,448076],[-318,4]],[[228814,448080],[2,6471]],[[227843,454558],[1,-6473]],[[227844,448085],[-1288,-18]],[[226556,448067],[-8,3240]],[[228814,448080],[-648,2]],[[228166,448082],[-322,3]],[[219766,454469],[6,-6447]],[[219772,448022],[-1243,-2]],[[218529,448020],[-365,12]],[[225260,451282],[0,-3235]],[[225260,448047],[-1272,-15]],[[223988,448032],[-19,0]],[[223969,448032],[-6,6481]],[[221384,454468],[0,-6437]],[[221384,448031],[-1572,-10]],[[219812,448021],[-40,1]],[[222677,454502],[3,-6462]],[[222680,448040],[-1296,-9]],[[223969,448032],[-1262,4]],[[222707,448036],[-27,4]],[[265719,454400],[30,-6681]],[[265749,447719],[-327,-65]],[[265422,447654],[-1337,-251]],[[264085,447403],[-8,6830]],[[294378,453789],[-86,-7126]],[[294292,446663],[-33,-2600]],[[294259,444063],[-1125,-724],[-133,-918]],[[293001,442421],[79,2811]],[[302989,445850],[-269,-1329],[-502,-404],[144,4028]],[[302596,449334],[321,-1189],[408,-370],[764,1511],[-341,4569],[401,-2137],[107,-3239],[-210,-1768],[-1057,-861]],[[262786,454214],[4,-5817]],[[262790,448397],[-1035,-6]],[[261755,448391],[-266,4]],[[261489,448395],[-4,5802]],[[264085,447403],[-109,-15]],[[263976,447388],[-1133,-180]],[[262843,447208],[-53,1189]],[[261489,448395],[-1024,-15]],[[260465,448380],[-348,0]],[[260117,448380],[68,740],[-8,5036]],[[260117,448380],[-775,27]],[[259342,448407],[-455,-2]],[[295712,453589],[-58,-1345],[397,121],[-173,-3103],[-189,-154],[94,-2952]],[[295783,446156],[-481,-2335],[-431,-845]],[[294871,442976],[-365,419],[-214,3268]],[[231269,450376],[205,-1282],[-160,-2130]],[[231314,446964],[-897,11]],[[230417,446975],[1,1083],[-309,4]],[[297103,453501],[5,-4179],[88,62],[182,-3932]],[[297378,445452],[-156,-333]],[[297222,445119],[-108,1174],[-581,-359],[-107,-908]],[[296426,445026],[-541,-412],[-102,1542]],[[298237,453401],[8,-1300],[-328,-15],[-2,-2289],[170,-1516],[-230,-737]],[[297855,447544],[-477,-2092]],[[247949,451542],[-252,-2717]],[[247697,448825],[-205,-1009]],[[247492,447816],[-439,832],[-669,-719],[-515,680]],[[245869,448609],[0,3252]],[[299081,453015],[27,-5278]],[[299108,447737],[6,-1575]],[[299114,446162],[-459,-103],[-560,582],[-240,903]],[[300423,448697],[-95,-429]],[[300328,448268],[-110,206]],[[300218,448474],[-189,-592],[-921,-145]],[[291926,445251],[-301,-1608],[-771,-203],[-6,-1281]],[[290848,442159],[-634,1016],[-245,2280]],[[289969,445455],[47,2798],[-244,1860]],[[160757,452819],[3,-15155]],[[160760,437664],[-2931,-15]],[[157829,437649],[51,2566],[-254,887],[-643,-3073],[-236,35],[-241,-1729],[155,-2198],[-334,193],[-289,1393],[-274,-64],[-470,1939]],[[155294,437598],[-187,3492],[-518,272]],[[154589,441362],[110,941],[-260,2964],[146,1892],[-112,2512],[386,1397],[130,1813]],[[214980,448068],[2,-6471]],[[214982,441597],[-197,8],[0,-3232]],[[214785,438373],[-1546,1]],[[213239,438374],[-74,4048]],[[213165,442422],[1,5661],[-117,1089],[3,3792]],[[213165,442422],[-2040,-26]],[[211125,442396],[-4,4869]],[[211121,447265],[21,5655]],[[188449,452855],[1,-2742],[226,-3238],[-46,-2736],[-189,-2015]],[[188441,442124],[-433,106],[-167,-982],[-443,939]],[[187398,442187],[-88,2092],[-182,-89],[-193,3079],[-3,3037],[-316,2514]],[[211121,447265],[-1901,-23]],[[189737,445043],[-2,-6108]],[[189735,438935],[-608,-2001]],[[189127,436934],[120,1271],[-305,2684],[-249,88]],[[188693,440977],[-252,1147]],[[286968,452863],[88,-6473]],[[287056,446390],[-234,-101],[-57,-1949]],[[286765,444340],[-1655,896]],[[285110,445236],[-170,121]],[[284940,445357],[-144,7506]],[[284940,445357],[-238,-844],[-1781,-169]],[[282921,444344],[-29,8518]],[[187398,442187],[-205,138],[-194,-1813],[-583,5],[-712,-4840]],[[185704,435677],[-852,-1424],[-3463,-5]],[[181389,434248],[1,18499]],[[173097,434253],[-1,-6650],[-636,-11],[-149,-2165]],[[172311,425427],[-25,2930],[-933,15],[0,3230],[-3183,-36],[0,1954],[-1457,-9]],[[166713,433511],[-56,0],[16,19240]],[[173097,452848],[0,-18595]],[[181389,434248],[-13,-16439]],[[181376,417809],[-3391,104],[-1591,99]],[[176394,418012],[-462,-9],[-438,10035],[-1,6209],[-1194,9]],[[174299,434256],[-1202,-3]],[[281230,452863],[9,-7106]],[[281239,445757],[-601,-298]],[[280638,445459],[-179,516],[-1317,-112]],[[279142,445863],[103,6950]],[[282921,444344],[4,-1186]],[[282925,443158],[-1089,-70]],[[281836,443088],[-171,12],[-426,2657]],[[154589,441362],[-304,-18],[-3,1572],[-819,-16]],[[153463,442900],[-268,5082],[-262,812],[123,4043]],[[289969,445455],[-804,-6747]],[[289165,438708],[-411,-131]],[[288754,438577],[180,478],[-59,7138]],[[288875,446193],[-56,6647]],[[288875,446193],[-721,12]],[[288154,446205],[-1098,185]],[[279142,445863],[-1549,13]],[[277593,445876],[-281,-13]],[[277312,445863],[2,4211]],[[164792,452769],[-2,-15096]],[[164790,437673],[-3709,-1]],[[161081,437672],[-321,-8]],[[166713,433511],[-9,-8230],[-71,-9],[-2,-9666],[318,14]],[[166949,415620],[46,-3526],[105,-404],[-51,-2899]],[[167049,408791],[-263,-205]],[[166786,408586],[-506,-709],[-437,-1338],[-205,182],[103,-3534],[-151,-1502]],[[165590,401685],[-468,-1589],[-344,2]],[[164778,400098],[-9,2800]],[[164769,402898],[12,2458]],[[164781,405356],[6,5108]],[[164787,410464],[3,27209]],[[253863,447011],[-4,808],[-643,-82]],[[253216,447737],[-1,2]],[[274786,450060],[0,-6513]],[[274786,443547],[-1348,41]],[[273438,443588],[-1,3974]],[[273437,447562],[3,2573]],[[273440,450135],[1346,2308]],[[245869,448609],[-4,-3244]],[[245865,445365],[-1300,23]],[[244565,445388],[1,4872]],[[249404,451036],[-5,-5897]],[[249399,445139],[-643,-17]],[[248756,445122],[-901,11]],[[247855,445133],[-158,3692]],[[226556,448067],[2,-4023]],[[226558,444044],[-753,-845],[-533,-1]],[[225272,443198],[-12,4849]],[[251329,445946],[-636,7],[1,-813]],[[250694,445140],[-1295,-1]],[[232889,450323],[0,-4862],[165,-1791]],[[233054,443670],[-1388,16]],[[231666,443686],[-257,439],[-95,2839]],[[243270,450293],[6,-6523]],[[243276,443770],[-325,-13]],[[242951,443757],[-980,-18]],[[241971,443739],[-2,6572]],[[237742,450323],[69,-6544]],[[237811,443779],[-1256,-154]],[[236555,443625],[-107,1818],[-1,4872]],[[234183,450322],[1,-4860],[144,-1818]],[[234328,443644],[-317,10]],[[234011,443654],[-957,16]],[[235153,450303],[2,-4797],[121,-1872]],[[235276,443634],[-948,10]],[[241971,443739],[-319,-4]],[[241652,443735],[-960,-14]],[[240692,443721],[-26,6583]],[[239045,450317],[54,-6615]],[[239099,443702],[0,-314]],[[239099,443388],[-1288,391]],[[240692,443721],[-320,-6]],[[240372,443715],[-1273,-13]],[[236555,443625],[-1279,9]],[[244565,445388],[-5,-3262]],[[244560,442126],[-322,13]],[[244238,442139],[-104,1623],[-858,8]],[[273437,447562],[-275,-15],[3,-1357],[-539,-11],[-3,-1320],[-265,-4]],[[272358,444855],[-272,1147]],[[272086,446002],[570,2451],[784,1682]],[[277312,445863],[-602,-34],[-476,-2457]],[[276234,443372],[-1448,-20]],[[274786,443352],[0,195]],[[247855,445133],[-29,-825],[-657,-1555],[-5,-2417]],[[247164,440336],[-1778,119]],[[245386,440455],[0,4]],[[245386,440459],[89,1581],[707,640]],[[246182,442680],[363,172],[871,2340],[76,2624]],[[300676,447525],[-94,-1308],[-254,2051]],[[246182,442680],[7,2674],[-324,11]],[[300218,448474],[-108,-2110]],[[300110,446364],[-154,-894],[-847,-110]],[[299109,445360],[5,802]],[[260465,448380],[14,-4389]],[[260479,443991],[3,-1610]],[[260482,442381],[-1130,-19]],[[259352,442362],[-1,806]],[[259351,443168],[-9,5239]],[[259351,443168],[-1136,-49],[2,-810]],[[258217,442309],[-162,-2]],[[258055,442307],[103,4030],[-102,2054]],[[258055,442307],[-491,-569],[-231,-2173],[-408,-905]],[[256925,438660],[-7,8809]],[[256918,447469],[301,934]],[[262847,444118],[-1085,-69]],[[261762,444049],[-7,4342]],[[262843,447208],[4,-3090]],[[261762,444049],[-1283,-58]],[[229132,448076],[0,-5330]],[[229132,442746],[-759,-1482],[-210,137]],[[228163,441401],[3,6681]],[[228163,441401],[-319,227]],[[227844,441628],[-642,-1180]],[[227202,440448],[0,1151],[-293,1]],[[226909,441600],[0,2456],[-351,-12]],[[230417,446975],[2,-2973],[320,-2435]],[[230739,441567],[-395,2]],[[230344,441569],[-276,747],[-936,373]],[[229132,442689],[0,57]],[[218529,448020],[-5,-6446]],[[218524,441574],[-1550,27]],[[216974,441601],[-383,9]],[[216591,441610],[2,6474]],[[216591,441610],[-1609,-13]],[[225272,443198],[0,-1617]],[[225272,441581],[-1262,6]],[[224010,441587],[-20,-2]],[[223990,441585],[-2,6447]],[[222707,448036],[24,-6450]],[[222731,441586],[-12,-6463]],[[222719,435123],[-608,-9]],[[222111,435114],[-2224,2]],[[219887,435116],[-75,6454]],[[219812,441570],[0,6451]],[[219812,441570],[-1288,4]],[[223990,441585],[-1259,1]],[[267414,445851],[-697,-80]],[[266717,445771],[-430,-33],[-394,-2264],[-479,-1509]],[[265414,441965],[1,1360]],[[265415,443325],[7,4329]],[[265749,447719],[781,160]],[[266530,447879],[6,1]],[[266536,447880],[30,6]],[[266566,447886],[16,1]],[[266582,447887],[26,6]],[[266608,447893],[806,-2042]],[[255266,442934],[-3,-3134]],[[255263,439800],[-1357,-110],[6,-1614],[-648,-79]],[[253264,437997],[-22,4868]],[[253242,442865],[-26,4872]],[[267414,445851],[546,-1925],[381,1255],[294,-1579],[-868,-836]],[[267767,442766],[-50,-4]],[[267717,442762],[-15,-1]],[[267702,442761],[-769,5],[-213,795]],[[266720,443561],[-3,2210]],[[253242,442865],[-957,-108]],[[252285,442757],[-18,3246]],[[265415,443325],[-1279,-38]],[[264136,443287],[-108,529],[-52,3572]],[[273438,443588],[0,-2864]],[[273438,440724],[-1081,8]],[[272357,440732],[1,4123]],[[299109,445360],[-23,-3357],[-140,-1618]],[[298946,440385],[-436,-421],[-482,288],[-416,-839],[-291,2723]],[[297321,442136],[350,235],[-50,1823],[-299,-180],[-100,1105]],[[256110,445861],[11,-7107]],[[256121,438754],[-158,-435]],[[255963,438319],[-386,-1051],[-314,72]],[[255263,437340],[0,2460]],[[255271,447419],[248,-1015],[591,-543]],[[256925,438660],[-559,918],[-245,-824]],[[256110,445861],[808,1608]],[[264136,443287],[1,-1079]],[[264137,442208],[-1288,-28]],[[262849,442180],[-2,1938]],[[211125,442396],[-24,-801]],[[211101,441595],[-1879,-26]],[[209222,441569],[-1,3184]],[[231666,443686],[163,-2163]],[[231829,441523],[-1090,44]],[[300704,446133],[-85,-2792],[-210,504],[295,2288]],[[300975,443507],[-208,-748],[-4,4059]],[[294871,442976],[348,-1153],[336,-2034],[-121,-1229]],[[295434,438560],[-200,-1187],[-705,-2090],[-626,-1184]],[[293903,434099],[-195,2024],[683,2086],[-173,2860]],[[294218,441069],[41,2994]],[[209222,441569],[-4,-7295]],[[209218,434274],[-2479,-59]],[[206739,434215],[-930,1]],[[300110,446364],[27,-3366],[-179,-1833],[-1042,-1242],[30,462]],[[288154,446205],[-316,-4003]],[[287838,442202],[-487,-794],[-761,-135]],[[286590,441273],[175,3067]],[[297027,438996],[-21,79]],[[297006,439075],[21,-79]],[[297321,442136],[207,-2885],[-531,-113]],[[296997,439138],[-298,3270],[-256,-279],[-17,2897]],[[296997,439138],[9,-63]],[[297027,438996],[-1112,58],[-473,-1600],[-8,1106]],[[288754,438577],[-264,-1321]],[[288490,437256],[-133,486],[-104,2828],[-415,1632]],[[293001,442421],[-13,-2191]],[[292988,440230],[-683,-3302]],[[292305,436928],[-373,1054]],[[291932,437982],[-934,2867]],[[290998,440849],[-150,1310]],[[252285,442757],[26,-6500]],[[252311,436257],[-960,-39],[-1,-3314]],[[251350,432904],[-324,-34]],[[251026,432870],[0,3329],[-319,-13]],[[250707,436186],[-5,3837]],[[250702,440023],[-8,5117]],[[272357,440732],[-1,0]],[[272356,440732],[-569,48],[73,-1359],[-321,-13]],[[271539,439408],[-538,-38]],[[271001,439370],[3,1405],[-263,14],[8,2860]],[[270749,443649],[623,-331],[714,2684]],[[280638,445459],[-3,-3672],[463,-4],[46,-3243]],[[281144,438540],[-1140,433],[-177,-967]],[[279827,438006],[-70,1137],[-624,1848],[-379,-397]],[[278754,440594],[88,2092],[292,-301],[8,3478]],[[278754,440594],[-317,-166]],[[278437,440428],[5,1839],[-538,104],[-216,-927]],[[277688,441444],[-95,4432]],[[277688,441444],[-1,-946],[-351,-263],[-253,-2768]],[[277083,437467],[-849,-19]],[[276234,437448],[0,5924]],[[269692,442196],[16,-2669]],[[269708,439527],[-1391,121]],[[268317,439648],[-18,2604],[-150,10]],[[268149,442262],[561,621],[551,-1528],[431,841]],[[266720,443561],[-15,-4584]],[[266705,438977],[-3,-1624]],[[266702,437353],[-1282,19]],[[265420,437372],[-6,4593]],[[281836,443088],[1,-1968],[-292,-2833]],[[281545,438287],[-401,253]],[[290998,440849],[-384,-1438],[-427,-3445]],[[290187,435966],[-461,1078],[82,1910],[-643,-246]],[[245386,440459],[-829,46],[3,1621]],[[285110,445236],[175,-3416],[441,-1905],[405,-535]],[[286131,439380],[-539,-2233]],[[285592,437147],[-256,304]],[[285336,437451],[-455,-614]],[[284881,436837],[-228,-952],[-463,-355]],[[284190,435530],[-1068,5402],[-197,2226]],[[286590,441273],[-78,-1278]],[[286512,439995],[-381,-615]],[[250702,440023],[-480,-175],[-57,-1257],[-306,11],[0,-1588]],[[249859,437014],[-479,1]],[[249380,437015],[-1,1587],[-610,12]],[[248769,438614],[-13,6508]],[[248769,438614],[-31,-1592],[-323,7]],[[248415,437029],[-1262,35]],[[247153,437064],[11,3272]],[[192516,434205],[-2782,5],[1,4725]],[[262849,442180],[1,-2881]],[[262850,439299],[-1082,-132]],[[261768,439167],[-6,4882]],[[261768,439167],[-323,-1]],[[261445,439166],[-639,37],[-320,533]],[[260486,439736],[-4,2645]],[[226909,441600],[-5,-1596],[-968,-299],[-620,-535],[-31,2412]],[[225285,441582],[-13,-1]],[[293006,440243],[-18,-18]],[[292988,440225],[0,5]],[[294218,441069],[-1212,-826]],[[302428,442673],[372,-1939],[-773,-264],[113,1841],[288,362]],[[271001,439370],[-263,5],[-6,-1404],[-273,-2],[-271,-1169],[-5,-1369]],[[270183,435431],[-459,42]],[[269724,435473],[-16,4054]],[[269692,442196],[936,1652],[121,-199]],[[244238,442139],[-5,-4867]],[[244233,437272],[-641,8]],[[243592,437280],[-640,14]],[[242952,437294],[-1,6463]],[[239099,443388],[2,-6149]],[[239101,437239],[-640,12]],[[238461,437251],[-646,14]],[[237815,437265],[-4,6514]],[[242952,437294],[-653,-17]],[[242299,437277],[-642,-14]],[[241657,437263],[-5,6472]],[[237815,437265],[-627,-98]],[[237188,437167],[-633,7]],[[236555,437174],[0,6451]],[[241657,437263],[-641,-12]],[[241016,437251],[-639,-8]],[[240377,437243],[-5,6472]],[[240377,437243],[-639,-2]],[[239738,437241],[-637,-2]],[[234011,443654],[-3,-6441]],[[234008,437213],[-637,14]],[[233371,437227],[-1382,-2]],[[231989,437225],[-120,576]],[[231869,437801],[88,2412],[-128,1310]],[[235276,443634],[0,-6443]],[[235276,437191],[-632,8]],[[234644,437199],[-636,14]],[[236555,437174],[-638,8]],[[235917,437182],[-641,9]],[[274786,443352],[1,-6623]],[[274787,436729],[-1339,18]],[[273448,436747],[-10,3977]],[[267767,442766],[-50,-4]],[[267702,442761],[447,-499]],[[268317,439648],[3,-646]],[[268320,439002],[-1615,-25]],[[276234,437448],[-271,-1900]],[[275963,435548],[-140,819],[-1037,201]],[[274786,436568],[1,161]],[[265420,437372],[-967,-36]],[[264453,437336],[-1,4877],[-315,-5]],[[259352,442362],[14,-4890],[-65,-5]],[[259301,437467],[-1086,-27]],[[258215,437440],[2,4869]],[[284190,435530],[0,-456]],[[284190,435074],[-1015,-1516],[-180,1961],[-470,731],[-132,1393],[-289,-76],[-14,1365],[-389,-1859]],[[281701,437073],[-156,1214]],[[155294,437598],[-138,-2080],[141,-716],[-208,-2139],[-386,312],[219,-3715],[-7,-13593]],[[154915,415667],[-1334,-12]],[[153581,415655],[-243,1900],[-703,2915],[-128,3411],[386,4770],[132,-280],[309,6363],[-132,874],[261,7292]],[[253264,437997],[-21,-1627]],[[253243,436370],[-932,-113]],[[229132,442689],[-7,-7583]],[[229125,435106],[-7,1]],[[229118,435107],[-1274,15]],[[227844,435122],[0,6506]],[[230344,441569],[400,-3783]],[[230744,437786],[23,-2700]],[[230767,435086],[-404,-539]],[[230363,434547],[0,541],[-1238,18]],[[260486,439736],[-89,-4618]],[[260397,435118],[-729,-71]],[[259668,435047],[-363,782],[-4,1638]],[[213239,438374],[-30,-4080]],[[213209,434294],[-90,-2]],[[213119,434292],[-2031,-1]],[[211088,434291],[13,7304]],[[278437,440428],[-17,-5242]],[[278420,435186],[-185,-771],[-669,-629],[-401,1395],[-70,2246]],[[277095,437427],[-12,40]],[[188693,440977],[-212,-2505],[-448,-762],[-414,553],[-140,-1426]],[[187479,436837],[-928,257],[-847,-1417]],[[258215,437440],[-1290,17]],[[256925,437457],[0,1203]],[[288490,437256],[-135,-732]],[[288355,436524],[-329,-580],[98,-1544],[-739,-1772]],[[287385,432628],[-586,685]],[[286799,433313],[-59,3513],[-251,1238],[23,1931]],[[264453,437336],[-316,-6]],[[264137,437330],[-325,1637],[-962,-17]],[[262850,438950],[0,349]],[[245386,440455],[-115,-1739],[344,-1569],[113,-1545]],[[245728,435602],[-1175,26]],[[244553,435628],[2,1613],[-322,31]],[[216974,441601],[-2,-6470],[57,-809]],[[217029,434322],[-2234,-1]],[[214795,434321],[-10,4052]],[[219887,435116],[2,-6448]],[[219889,428668],[-1545,-2]],[[218344,428666],[-1309,-44]],[[217035,428622],[-6,5700]],[[304069,440803],[121,-1847],[-424,-198],[303,2045]],[[227844,435122],[-1276,-6]],[[226568,435116],[-6,2365]],[[226562,437481],[640,2967]],[[211088,434291],[-535,-10]],[[210553,434281],[-1335,-7]],[[226562,437481],[-534,-2004],[-732,-3606]],[[225296,431871],[-12,3241]],[[225284,435112],[1,6470]],[[225284,435112],[-1212,5]],[[224072,435117],[-74,0]],[[223998,435117],[12,6470]],[[223998,435117],[-1279,6]],[[231869,437801],[-1125,-15]],[[189127,436934],[-680,-1517],[-235,-1306],[316,-2475],[-193,-1470],[-256,342]],[[188079,430508],[-272,1154]],[[187807,431662],[-214,1796],[-114,3379]],[[279827,438006],[-264,-1318],[-1,-4190]],[[279562,432498],[-1143,100]],[[278419,432598],[1,2588]],[[157829,437649],[146,-436],[-429,-4820],[-421,-4012],[146,-2151],[-836,-2814],[-186,-2443]],[[156249,420973],[222,-710],[144,-5038]],[[156615,415225],[-1701,-21],[1,463]],[[293900,433789],[-376,-1707]],[[293524,432082],[-325,588]],[[293199,432670],[123,2992],[-28,2071],[-288,2510]],[[293903,434099],[-3,-310]],[[291932,437982],[-383,-2133]],[[291549,435849],[-417,-3054],[-322,-187]],[[290810,432608],[-554,3400]],[[290256,436008],[-69,-42]],[[272356,440732],[-5,-6697]],[[272351,434035],[-74,-1524],[-634,144]],[[271643,432655],[-114,1381]],[[271529,434036],[10,5372]],[[273448,436747],[0,-2714],[-243,-5]],[[273205,434028],[-854,7]],[[247153,437064],[-5,-1624]],[[247148,435440],[-963,88]],[[246185,435528],[-457,35]],[[245728,435563],[0,39]],[[250707,436186],[-548,-8],[-300,836]],[[293219,434221],[-863,2534]],[[292356,436755],[-51,173]],[[292988,440225],[220,-3323],[11,-2681]],[[286799,433313],[-277,-2754],[-204,-490]],[[286318,430069],[-412,1992]],[[285906,432061],[44,1187],[-292,2181],[-66,1718]],[[255263,437340],[2,-2899]],[[255265,434441],[-1688,-234]],[[253577,434207],[-321,-56]],[[253256,434151],[-13,2219]],[[261445,439166],[-77,-4820]],[[261368,434346],[-859,-55]],[[260509,434291],[-112,827]],[[294555,427007],[-1,231]],[[294554,427238],[1,-231]],[[294554,427873],[-111,3760]],[[294443,431633],[-93,1192],[769,-305],[174,1129],[1072,-46],[376,297],[903,2986],[-441,-2839],[-371,-1388],[292,-372],[379,1926],[532,898],[205,-1022],[552,1761],[82,-626],[-1554,-3633],[-192,472],[-405,-1606],[-231,346],[-428,-1335],[-389,98],[-351,-945],[-238,353],[-532,-1101]],[[269724,435473],[-278,-7],[9,-1349]],[[269455,434117],[-813,50]],[[268642,434167],[-292,19]],[[268350,434186],[-30,4816]],[[256925,437457],[-1,-4839]],[[256924,432618],[-156,-1387],[-315,-5],[3,-1882]],[[256456,429344],[-470,-1]],[[255986,429343],[-23,8976]],[[271529,434036],[-1228,60]],[[270301,434096],[-118,1335]],[[262850,438950],[1,-4899]],[[262851,434051],[1,-1244]],[[262852,432807],[-755,-88]],[[262097,432719],[-730,-14]],[[261367,432705],[1,1641]],[[268350,434186],[-789,-60]],[[267561,434126],[-857,-29]],[[266704,434097],[-2,3256]],[[281701,437073],[-194,-835],[74,-2824],[-363,-706],[-462,-3036],[47,-402]],[[280803,429270],[26,-144]],[[280829,429126],[-1257,-6]],[[279572,429120],[-10,3378]],[[192649,434205],[0,-3424]],[[192649,430781],[-728,405],[-315,-879],[-787,-888],[-261,131],[-420,-1293]],[[190138,428257],[-340,-1865],[-343,-530],[-618,1479],[-69,1143],[-267,-1698],[-177,190]],[[188324,426976],[-245,3532]],[[284190,435074],[-613,-3675]],[[283577,431399],[-884,-2182]],[[282693,429217],[-399,260],[-333,-971],[-479,890]],[[281482,429396],[-679,-126]],[[290256,436008],[-428,-2350]],[[289828,433658],[-466,-1941],[-519,-917]],[[288843,430800],[-280,2542],[218,671],[-426,2511]],[[264137,437330],[-1,-3255],[-160,-7]],[[263976,434068],[-1125,-17]],[[249380,437015],[-1,-3256]],[[249379,433759],[-966,19]],[[248413,433778],[2,3251]],[[214795,434321],[-2,-29]],[[214793,434292],[-1584,2]],[[255986,429343],[-721,8]],[[255265,429351],[0,5090]],[[292587,430897],[-395,1442]],[[292192,432339],[-104,1880],[-539,1630]],[[292356,436755],[-165,-2166],[367,-970],[29,-2722]],[[231989,437225],[3,-1984]],[[231992,435241],[-431,236],[-484,-1310],[-310,919]],[[164787,410464],[-407,-275]],[[164380,410189],[102,1082],[5,3235],[-258,1376],[-23,1351],[-367,544],[-472,2489],[-707,1249],[-457,-2310],[-371,1200],[1,3540],[-743,-22]],[[161090,423923],[22,8573],[-31,5176]],[[161090,423923],[-473,4]],[[160617,423927],[-1427,-350],[-831,-1031],[-549,335],[-407,-1138],[-270,689],[-884,-1459]],[[285336,437451],[-167,-4196],[366,398],[371,-1592]],[[286318,430069],[-895,-2187]],[[285423,427882],[-525,-888],[-165,332]],[[284733,427326],[26,188]],[[284759,427514],[265,1448],[124,3090]],[[285148,432052],[-223,1660],[-44,3125]],[[259668,435047],[-2,-813]],[[259666,434234],[-619,-58],[-1,-1610]],[[259046,432566],[-835,5]],[[258211,432571],[4,4869]],[[226568,435116],[1,-6480]],[[226569,428636],[-1260,-4]],[[225309,428632],[-13,-1]],[[225296,428631],[0,3240]],[[258211,432571],[-314,19]],[[257897,432590],[-973,28]],[[277095,437427],[-6,-9327]],[[277089,428100],[-1270,89]],[[275819,428189],[-27,3363]],[[275792,431552],[-19,2705],[190,1291]],[[278419,432598],[1,-2521],[-655,-4576]],[[277765,425501],[-675,2581]],[[277090,428082],[-1,18]],[[266704,434097],[-266,-3234]],[[266438,430863],[-1015,33]],[[265423,430896],[-1,1871]],[[265422,432767],[-2,4605]],[[265422,432767],[-638,-292],[0,-814],[-644,-36]],[[264140,431625],[-164,2443]],[[243592,437280],[-7,-4878]],[[243585,432402],[-1284,-20]],[[242301,432382],[-2,4895]],[[244553,435628],[-3,-4868],[-104,1]],[[244446,430761],[-862,16]],[[243584,430777],[1,1625]],[[242301,432382],[-1281,-16]],[[241020,432366],[-4,4885]],[[238461,437251],[3,-4896]],[[238464,432355],[-1277,-24]],[[237187,432331],[1,4836]],[[239738,437241],[4,-4883]],[[239742,432358],[-1278,-3]],[[241020,432366],[-1278,-8]],[[233371,437227],[-2,-4811]],[[233369,432416],[-1193,2]],[[232176,432418],[-184,2823]],[[234644,437199],[-3,-4800]],[[234641,432399],[-1272,17]],[[235917,437182],[-1,-4805]],[[235916,432377],[-1275,22]],[[237187,432331],[-1271,46]],[[187807,431662],[-565,-723],[-182,1854],[-716,-2763]],[[186344,430030],[-640,5647]],[[248413,433778],[-2,-4886]],[[248411,428892],[-1277,44]],[[247134,428936],[14,6504]],[[285148,432052],[-398,108],[-1152,-1498]],[[283598,430662],[-21,737]],[[251026,432870],[-1183,-88]],[[249843,432782],[68,968],[-532,9]],[[292970,430541],[-431,-269]],[[292539,430272],[48,625]],[[293219,434221],[-249,-3680]],[[274786,436568],[0,-4189]],[[274786,432379],[-1580,38]],[[273206,432417],[-1,1611]],[[288843,430800],[-376,-510]],[[288467,430290],[-415,-967]],[[288052,429323],[-667,3305]],[[275792,431552],[-1004,-69]],[[274788,431483],[-2,896]],[[253256,434151],[34,-7012],[-625,-15]],[[252665,427124],[-321,-16],[-27,2627],[-962,-80]],[[251355,429655],[-5,3249]],[[290810,432608],[-339,-2310]],[[290471,430298],[-220,-1439],[-616,-2210]],[[289635,426649],[-20,312]],[[289615,426961],[68,3154],[341,1723],[-196,1820]],[[292192,432339],[-162,208],[-116,-3152]],[[291914,429395],[-248,-1212]],[[291666,428183],[-265,1560],[-471,-722]],[[290930,429021],[-459,1277]],[[186344,430030],[155,-1074],[97,-4631]],[[186596,424325],[-109,-153],[38,-2825],[149,-2529],[-96,-2970]],[[186578,415848],[-452,-1999],[-1271,-19],[-3480,52]],[[181375,413882],[1,3927]],[[245728,435563],[-37,-2695],[-356,-1884],[-62,-2389]],[[245273,428595],[-827,2166]],[[246185,435528],[-13,-8061]],[[246172,427467],[-319,64]],[[245853,427531],[-783,-26]],[[245070,427505],[203,1090]],[[247134,428936],[-3,-1616]],[[247131,427320],[-959,147]],[[270301,434096],[8,-6024]],[[270309,428072],[-263,-12],[2,-1850]],[[270048,426210],[-326,-246]],[[269722,425964],[-6,3196],[-215,1864],[-46,3093]],[[232176,432418],[-58,-2195]],[[232118,430223],[-1754,3]],[[230364,430226],[-1,4321]],[[227844,435122],[-1,-6484]],[[227843,428638],[-1271,-2]],[[226572,428636],[-3,0]],[[225296,428631],[-1222,12]],[[224074,428643],[-2,6474]],[[222111,435114],[24,-6994]],[[222135,428120],[-630,262]],[[221505,428382],[-943,281]],[[220562,428663],[-673,5]],[[224074,428643],[-9,-170]],[[224065,428473],[-506,-690],[-761,186]],[[222798,427969],[-663,151]],[[229118,435107],[-7,-6481]],[[229111,428626],[-1268,12]],[[230364,430226],[0,-4858]],[[230364,425368],[-1251,12]],[[229113,425380],[-2,3246]],[[260509,434291],[15,-6500]],[[260524,427791],[-631,-27]],[[259893,427764],[-208,-13],[-19,6483]],[[255265,429351],[-1,-4574]],[[255264,424777],[-1140,-99]],[[254124,424678],[-508,40],[-39,9489]],[[261367,432705],[3,-4890],[-318,-15]],[[261052,427800],[-528,-9]],[[206643,416247],[0,0]],[[210532,425375],[-1577,18],[-7,-9745]],[[208948,415648],[-2259,-10]],[[206689,415638],[-255,-1]],[[206434,415637],[-6,4869]],[[206428,420506],[-5,1628],[310,-3],[6,12084]],[[210553,434281],[-21,-8906]],[[217035,428622],[-272,6]],[[216763,428628],[-1967,-10]],[[214796,428618],[0,969]],[[214796,429587],[-3,4705]],[[201978,423908],[55,-1918],[16,-7759]],[[202049,414231],[-1129,-109]],[[200920,414122],[-15,3214]],[[200905,417336],[-2,2480],[-1118,-26]],[[199785,419790],[29,5936],[320,1081],[-11,7496]],[[201402,434298],[27,-1460],[436,-670],[158,-1783],[-185,-3986],[140,-2491]],[[204199,424684],[-292,-2571],[-164,468],[-589,-929],[-98,691],[-519,-163],[-107,1114],[-340,-603],[-112,1217]],[[203264,434210],[15,-1191],[530,-3757],[390,-4578]],[[199785,419790],[-1878,-32],[-2612,19]],[[195295,419777],[7,8023]],[[195302,427800],[-5,6461]],[[213087,423794],[-321,0]],[[212766,423794],[-1909,-39]],[[210857,423755],[-6,1618],[-319,2]],[[213119,434292],[6,-4708]],[[213125,429584],[-38,-5790]],[[214796,429587],[-1671,-3]],[[195302,427800],[-314,562],[-118,3255],[-532,-11],[-250,-2208],[-269,1599],[-807,-1145],[-299,856]],[[192713,430708],[-64,73]],[[176394,418012],[112,-5115],[-101,-2198],[49,-2635],[-263,-2403],[2,-5633]],[[176193,400028],[-1933,-7]],[[174260,400021],[29,16934],[-18,16297],[28,1004]],[[174260,400021],[-2037,36],[-1236,-1303]],[[170987,398754],[153,2224],[-118,2607],[482,3074],[266,297],[87,1838],[-89,2169],[75,3206],[-204,1482]],[[171639,415651],[672,9776]],[[206428,420506],[-1667,-25]],[[204761,420481],[-562,4203]],[[259893,427764],[3,-1603]],[[259896,426161],[-839,-54]],[[259057,426107],[-11,6459]],[[268642,434167],[-7,-5295]],[[268635,428872],[-365,-115]],[[268270,428757],[-705,-40]],[[267565,428717],[-4,5409]],[[254124,424678],[8,-1608]],[[254132,423070],[-1470,-13]],[[252662,423057],[3,4067]],[[269722,425964],[-798,-95]],[[268924,425869],[-10,2970],[-279,33]],[[267565,428717],[-860,-299]],[[266705,428418],[-211,275],[-56,2170]],[[271643,432655],[-7,-4585]],[[271636,428070],[-1327,2]],[[264140,431625],[-158,-803],[1,-2439]],[[263983,428383],[-165,-1]],[[263818,428382],[0,807],[-964,-2]],[[262854,429187],[-2,3620]],[[273206,432417],[-2,-3235]],[[273204,429182],[-418,-80],[-224,-1340]],[[272562,427762],[-923,-316]],[[271639,427446],[-3,624]],[[249843,432782],[-231,-3232]],[[249612,429550],[-356,-3635],[-528,-731]],[[248728,425184],[-3,2060],[-321,38],[7,1610]],[[289615,426961],[-382,-1336]],[[289233,425625],[-268,2542],[-150,-335],[-348,2458]],[[171639,415651],[-2673,-44],[-2017,13]],[[288052,429323],[-372,-1083]],[[287680,428240],[-781,-2649],[-748,-726]],[[286151,424865],[-265,1102]],[[285886,425967],[-463,1915]],[[274788,431483],[-1,-3962]],[[274787,427521],[-415,-1046]],[[274372,426475],[-540,316]],[[273832,426791],[-154,2374],[-474,17]],[[251355,429655],[-154,-1644],[-415,-1270],[-378,-46]],[[250408,426695],[-162,393],[-9,2476],[-625,-14]],[[262854,429187],[-1,-2903]],[[262853,426284],[-741,-74]],[[262112,426210],[-15,6509]],[[265423,430896],[0,-3262]],[[265423,427634],[-634,-31],[-321,799],[-485,-19]],[[188324,426976],[-114,-601]],[[188210,426375],[-465,-891],[-618,-2134],[-142,895],[-389,80]],[[293524,432082],[-12,-1259],[-325,-351]],[[293187,430472],[-27,1525]],[[293160,431997],[39,673]],[[262112,426210],[-370,-18]],[[261742,426192],[-685,-6]],[[261057,426186],[-5,1614]],[[294555,427007],[-1,231]],[[294554,427873],[-875,-1167]],[[293679,426706],[-79,470]],[[293600,427176],[63,322]],[[293663,427498],[119,2121],[-126,536]],[[293656,430155],[90,1669],[410,855],[287,-1046]],[[257897,432590],[-1,-3261]],[[257896,429329],[-463,33],[-65,-3266]],[[257368,426096],[-903,15]],[[256465,426111],[-9,3233]],[[259057,426107],[-583,-24]],[[258474,426083],[2,2440],[-422,-1],[-158,807]],[[279572,429120],[-482,-6117]],[[279090,423003],[-351,-509],[-158,819],[-661,807],[-155,1381]],[[292539,430272],[83,-963]],[[292622,429309],[-118,-889]],[[292504,428420],[-590,975]],[[233369,432416],[31,-5977]],[[233400,426439],[-1091,87]],[[232309,426526],[-329,3028],[138,669]],[[234641,432399],[38,-6061]],[[234679,426338],[-801,63]],[[233878,426401],[-478,38]],[[235916,432377],[-1,-6118]],[[235915,426259],[-448,10]],[[235467,426269],[-788,69]],[[243584,430777],[5,-4003]],[[243589,426774],[-630,136]],[[242959,426910],[-660,-98]],[[242299,426812],[2,5570]],[[242299,426812],[-476,-61]],[[241823,426751],[-800,-120]],[[241023,426631],[-3,5735]],[[237187,432331],[-2,-6015]],[[237185,426316],[-604,-37]],[[236581,426279],[-666,-20]],[[238464,432355],[-1,-5921]],[[238463,426434],[-606,-50]],[[237857,426384],[-672,-68]],[[239742,432358],[1,-5854]],[[239743,426504],[-772,-68]],[[238971,426436],[-508,-2]],[[241023,426631],[-213,-26]],[[240810,426605],[-1067,-101]],[[284759,427514],[-273,768],[-697,262]],[[283789,428544],[-185,150],[-6,1968]],[[293187,430472],[-271,-1807],[244,3332]],[[195295,419777],[-1,-10466]],[[195294,409311],[0,-3031]],[[195294,406280],[-155,-665],[-2556,-5]],[[192583,405610],[1,136]],[[192584,405746],[-39,1805],[122,2542],[304,1922],[-230,5]],[[192741,412020],[-28,18688]],[[275819,428189],[-81,-1216],[-510,-2451]],[[275228,424522],[-441,-3]],[[274787,424519],[0,3002]],[[282497,422685],[-451,360],[271,1887],[-71,939],[431,2360],[16,986]],[[283789,428544],[-584,-2082],[-708,-3777]],[[192741,412020],[-2481,120]],[[190260,412140],[-94,1614]],[[190166,413754],[-28,14503]],[[266705,428418],[4,-3381]],[[266709,425037],[-283,-21]],[[266426,425016],[-1003,645]],[[265423,425661],[0,1973]],[[292970,430541],[-211,-2552],[-210,-377],[73,1697]],[[245070,427505],[-470,-581],[-146,-1017],[115,-2813],[-151,-420]],[[244418,422674],[-829,4100]],[[293679,426706],[-79,470]],[[293412,427055],[2,251]],[[293414,427306],[-2,-251]],[[293663,427498],[-315,245]],[[293348,427743],[-288,1615]],[[293060,429358],[139,999],[457,-202]],[[290930,429021],[33,-2516],[-174,-1500],[76,-1477]],[[290865,423528],[-301,-1442],[-241,-93]],[[290323,421993],[-332,1497],[-16,2219],[-340,940]],[[289233,425625],[-418,-2203]],[[288815,423422],[-127,533]],[[288688,423955],[-1008,4285]],[[232309,426526],[155,-1143]],[[232464,425383],[-995,-12]],[[231469,425371],[-1105,-3]],[[291666,428183],[-6,-1402]],[[291660,426781],[-177,-792],[187,-1218],[-446,-2170]],[[291224,422601],[-359,927]],[[252662,423057],[2,-2183]],[[252664,420874],[-322,-1]],[[252342,420873],[-1598,9]],[[250744,420882],[-320,803]],[[250424,421685],[-16,5010]],[[214796,428618],[0,-4795]],[[214796,423823],[-1709,-29]],[[250424,421685],[-946,-94]],[[249478,421591],[-311,-17],[-6,2160],[-579,13]],[[248582,423747],[146,1437]],[[282497,422685],[136,-2142]],[[282633,420543],[-450,-3765]],[[282183,416778],[-753,1928]],[[281430,418706],[-282,1105],[-60,1366]],[[281088,421177],[343,3265],[-239,3445],[290,1509]],[[281088,421177],[-85,-1011],[-408,1093],[-38,-1088],[-480,1549]],[[280077,421720],[157,811],[68,2529],[527,4066]],[[293412,427055],[2,251]],[[293348,427743],[-53,-1236],[-451,496],[216,2355]],[[256465,426111],[5,-1623]],[[256470,424488],[-1206,27]],[[255264,424515],[0,262]],[[292504,428420],[-134,-1704]],[[292370,426716],[-710,65]],[[258474,426083],[-4,-2412]],[[258470,423671],[-889,5]],[[257581,423676],[-2,2416],[-211,4]],[[273832,426791],[-165,-803],[-57,-2450]],[[273610,423538],[-915,164]],[[272695,423702],[15,2449],[-148,1611]],[[263818,428382],[2,-6070],[59,-80]],[[263879,422232],[-1,-5]],[[263878,422227],[-1029,-28]],[[262849,422199],[4,4085]],[[280077,421720],[-106,-1566]],[[279971,420154],[-653,121],[-455,659]],[[278863,420934],[227,2069]],[[248582,423747],[-330,-1227],[-73,-1405],[-363,-2059]],[[247816,419056],[-702,92],[4,1628]],[[247118,420776],[13,6544]],[[268924,425869],[-64,-3813],[-274,84]],[[268586,422140],[-515,159],[-254,1409]],[[267817,423708],[174,1058],[2,2876],[277,1115]],[[267817,423708],[-635,189]],[[267182,423897],[2,1174],[-475,-34]],[[220562,428663],[2,-4872],[-317,7],[0,-1627]],[[220247,422171],[-287,-23]],[[219960,422148],[-1562,-10]],[[218398,422138],[-52,0]],[[218346,422138],[-2,6528]],[[225309,428632],[0,-6478]],[[225309,422154],[-1242,1]],[[224067,422155],[-2,6318]],[[221505,428382],[3,-6212]],[[221508,422170],[-1261,1]],[[218346,422138],[-1521,20]],[[216825,422158],[-55,-2]],[[216770,422156],[-7,6472]],[[216770,422156],[-1974,-23]],[[214796,422133],[0,1690]],[[227843,428638],[0,-6483]],[[227843,422155],[-1260,3]],[[226583,422158],[-9,0]],[[226574,422158],[-2,6478]],[[229113,425380],[-7,-3237]],[[229106,422143],[-1263,12]],[[226574,422158],[-1253,-4]],[[225321,422154],[-12,0]],[[284733,427326],[-651,-855],[-995,-3407],[-367,-2037]],[[282720,421027],[-87,-484]],[[190166,413754],[-531,19],[-2,801],[-456,2164],[59,2009],[-250,2239],[-310,-13],[-356,1411],[-184,1615],[74,2376]],[[224067,422155],[-8,0]],[[224059,422155],[-1261,5]],[[222798,422160],[0,5809]],[[265423,425661],[-341,-1035]],[[265082,424626],[-938,-41],[1,-1903],[-266,-450]],[[222798,422160],[-1257,8]],[[221541,422168],[-33,2]],[[278863,420934],[-360,-3257],[-302,-1293]],[[278201,416384],[-562,1959],[-401,-1171],[-297,845],[-366,-25]],[[276575,417992],[19,1315]],[[276594,419307],[179,811],[290,5227],[-200,1083],[227,1654]],[[288688,423955],[-466,-3821]],[[288222,420134],[-493,-1945]],[[287729,418189],[-773,3320]],[[286956,421509],[-805,3356]],[[276594,419307],[-123,1028],[-749,1490],[-494,2697]],[[271639,427446],[-58,-3551],[-110,17]],[[271471,423912],[-1324,214]],[[270147,424126],[-99,2084]],[[272695,423702],[-15,-2421],[-175,-1661]],[[272505,419620],[-793,132]],[[271712,419752],[19,2733],[-268,50],[8,1377]],[[285886,425967],[-397,-1498],[304,-5190]],[[285793,419279],[-424,-1382]],[[285369,417897],[-35,1119],[-345,857]],[[284989,419873],[-161,1874]],[[284828,421747],[-318,1084],[208,1376],[-107,1919],[122,1200]],[[261057,426186],[8,-3496]],[[261065,422690],[-374,7]],[[260691,422697],[-791,-21]],[[259900,422676],[1,530]],[[259901,423206],[-5,2955]],[[292731,427719],[-75,-1794],[-401,-1051],[179,2726],[297,119]],[[245853,427531],[-15,-6605]],[[245838,420926],[-7,-1699]],[[245831,419227],[-1653,137]],[[244178,419364],[23,899]],[[244201,420263],[217,2411]],[[274787,424519],[3,-1461]],[[274790,423058],[-309,-65]],[[274481,422993],[90,1246],[-199,2236]],[[247118,420776],[-1280,150]],[[284828,421747],[-989,-927],[-267,651],[-693,-2143]],[[282879,419328],[-159,1699]],[[244201,420263],[-1264,157]],[[242937,420420],[3,812]],[[242940,421232],[19,5678]],[[290323,421993],[609,-3619]],[[290932,418374],[-696,-1838]],[[290236,416536],[-114,1663]],[[290122,418199],[-1307,5223]],[[292370,426716],[-184,-1986],[143,-700]],[[292329,424030],[-474,-3179],[-254,-504]],[[291601,420347],[-383,1322],[6,932]],[[242940,421232],[-1109,36]],[[241831,421268],[-6,814]],[[241825,422082],[-2,4669]],[[274481,422993],[82,-1389],[-230,-2518]],[[274333,419086],[-58,-528]],[[274275,418558],[-502,43]],[[273773,418601],[49,4904],[-212,33]],[[241825,422082],[-931,-56]],[[240894,422026],[22,2915],[-106,1664]],[[240894,422026],[-478,-14]],[[240416,422012],[1,804],[-1425,-54]],[[238992,422762],[-21,3674]],[[233878,426401],[61,-5897]],[[233939,420504],[-1043,57]],[[232896,420561],[-163,921],[-269,3901]],[[238992,422762],[-1,-2172]],[[238991,420590],[-1104,-44]],[[237887,420546],[-30,5838]],[[235467,426269],[94,-3438]],[[235561,422831],[-21,-4851]],[[235540,417980],[-1218,74]],[[234322,418054],[-79,2429],[-304,21]],[[190260,412140],[-1085,15]],[[189175,412155],[-1095,-4]],[[188080,412151],[-519,2498],[-397,-2608],[-336,-472],[101,2059],[-351,2220]],[[237887,420546],[-2,-2440]],[[237885,418106],[-1261,69]],[[236624,418175],[7,4610]],[[236631,422785],[-50,3494]],[[270147,424126],[-32,-4044]],[[270115,420082],[-784,126],[-762,580]],[[268569,420788],[17,1352]],[[262849,422199],[1,-795]],[[262850,421404],[-1157,-63]],[[261693,421341],[-3,1350]],[[261690,422691],[52,3501]],[[236631,422785],[-1070,46]],[[261690,422691],[-625,-1]],[[259901,423206],[-1061,-73],[0,-541]],[[258840,422592],[-370,1079]],[[257581,423676],[-3,-4054]],[[257578,419622],[-1105,9]],[[256473,419631],[1,2822]],[[256474,422453],[-4,2035]],[[232896,420561],[7,-54]],[[232903,420507],[-1276,-7]],[[231627,420500],[-157,10]],[[231470,420510],[-1,4861]],[[286956,421509],[-435,-1129],[-728,-1101]],[[266426,425016],[-87,-5114]],[[266339,419902],[-1291,821]],[[265048,420723],[34,3903]],[[210857,423755],[-19,-8095],[-651,-3]],[[210187,415657],[-1239,-9]],[[230364,425368],[0,-4856]],[[230364,420512],[0,-4861]],[[230364,415651],[-953,8]],[[229411,415659],[-308,1]],[[229103,415660],[3,6483]],[[231470,420510],[-1106,2]],[[267401,418295],[-101,-658]],[[267300,417637],[-828,70]],[[266472,417707],[25,2122],[-158,73]],[[267182,423897],[1,-3714],[212,-4],[6,-1884]],[[255264,424515],[-14,-6128]],[[255250,418387],[-5,-4934]],[[255245,413453],[-1127,-59]],[[254118,413394],[14,9676]],[[204761,420481],[44,-3996],[-107,-2111]],[[204698,414374],[-40,-1498]],[[204658,412876],[-515,-983],[-138,-1866]],[[204005,410027],[-269,-196],[-186,2235],[-440,1922],[-526,246]],[[202584,414234],[-535,-3]],[[265048,420723],[-23,-1667]],[[265025,419056],[-1141,244]],[[263884,419300],[-6,2927]],[[276575,417992],[2,-1689],[-338,-982]],[[276239,415321],[-807,668],[-645,-1056]],[[274787,414933],[1,1005]],[[274788,415938],[-1,2668]],[[274787,418606],[3,4452]],[[256474,422453],[-391,-1025],[-571,-2641],[86,-782]],[[255598,418005],[-348,382]],[[292681,417934],[-413,-100],[-60,940],[-392,66],[-407,-1735]],[[291409,417105],[-97,1100]],[[291312,418205],[289,2142]],[[292329,424030],[617,-741],[71,-1974],[-164,-3756],[-172,375]],[[271712,419752],[-260,-1288]],[[271452,418464],[-1312,274]],[[270140,418738],[-25,1344]],[[290122,418199],[-264,-1711],[-318,871],[49,-1505],[-196,-651]],[[289393,415203],[-235,1649]],[[289158,416852],[-265,414],[-399,2752],[-272,116]],[[160617,423927],[82,-1784],[340,-681],[-60,-1870],[-213,-1153]],[[160766,418439],[-393,-946],[-168,-2180],[-363,-1739],[-738,-95],[-16,-1615]],[[159088,411864],[-2483,10]],[[156605,411874],[10,3351]],[[164380,410189],[-1412,-44],[-74,-532],[-534,1856],[-382,-2585]],[[161978,408884],[-186,-741]],[[161792,408143],[-928,5114],[-31,2656],[120,1726],[-187,800]],[[268569,420788],[-30,-2813]],[[268539,417975],[-1138,320]],[[214796,422133],[-1,-6444]],[[214795,415689],[6,-7987]],[[214801,407702],[-2099,-115]],[[212702,407587],[-2,8092],[66,8115]],[[212702,407587],[-979,-41]],[[211723,407546],[-1540,11]],[[210183,407557],[2,3232]],[[210185,410789],[2,4868]],[[249478,421591],[4,-3680]],[[249482,417911],[-1008,385],[-87,-647]],[[248387,417649],[-390,-863],[-421,-126],[-193,1285]],[[247383,417945],[433,1111]],[[273773,418601],[-955,197]],[[272818,418798],[-313,822]],[[258840,422592],[0,-2942]],[[258840,419650],[1,-653]],[[258841,418997],[-1263,-39]],[[257578,418958],[0,664]],[[291312,418205],[-380,169]],[[259900,422676],[1,-2968]],[[259901,419708],[-1061,-58]],[[274787,418606],[-454,480]],[[254118,413394],[-1462,-13]],[[252656,413381],[8,7493]],[[240416,422012],[-1,-5687]],[[240415,416325],[-1409,-77]],[[239006,416248],[-15,4342]],[[236624,418175],[-6,-1894]],[[236618,416281],[-1077,81]],[[235541,416362],[-1,1618]],[[260691,422697],[7,-5629]],[[260698,417068],[-1,-2446]],[[260697,414622],[-797,-40]],[[259900,414582],[1,5126]],[[261693,421341],[13,-4278]],[[261706,417063],[-1008,5]],[[256473,419631],[4,-4874]],[[256477,414757],[-917,-11]],[[255560,414746],[38,3259]],[[263884,419300],[17,-5165]],[[263901,414135],[-164,-21]],[[263737,414114],[-912,-29]],[[262825,414085],[5,1641]],[[262830,415726],[20,5678]],[[225321,422154],[-1,-6473]],[[225320,415681],[-642,-7]],[[224678,415674],[-618,2]],[[224060,415676],[-1,6479]],[[224060,415676],[-949,-1]],[[223111,415675],[-312,-4]],[[222799,415671],[-1,6489]],[[226583,422158],[-2,-6488]],[[226581,415670],[-307,0]],[[226274,415670],[-954,11]],[[216825,422158],[-7,-6476]],[[216818,415682],[-238,-3]],[[216580,415679],[-1785,10]],[[221541,422168],[6,-6502]],[[221547,415666],[-1531,-5]],[[220016,415661],[-44,2]],[[219972,415663],[-12,6485]],[[227843,422155],[-2,-6487]],[[227841,415668],[-1260,2]],[[229103,415660],[-1262,8]],[[219972,415663],[-1519,8]],[[218453,415671],[-56,3]],[[218397,415674],[1,6464]],[[218397,415674],[-1579,8]],[[222799,415671],[-1244,-5]],[[221555,415666],[-8,0]],[[241831,421268],[8,-4944]],[[241839,416324],[-1400,2]],[[240439,416326],[-24,-1]],[[284989,419873],[-135,-1158],[-365,-437],[-280,-1344]],[[284209,416934],[-135,-749],[-607,-656],[-188,-939]],[[283279,414590],[-400,4738]],[[250744,420882],[11,-4341]],[[250755,416541],[4,-2438],[-208,-17]],[[250551,414086],[-739,301],[-265,800]],[[249547,415187],[-65,2724]],[[281430,418706],[-459,-6339],[-226,-1900]],[[280745,410467],[-1192,6]],[[279553,410473],[143,1890],[-17,4331],[292,3460]],[[287729,418189],[-193,-523],[-134,-4476],[-402,-2750]],[[287000,410440],[-272,4]],[[286728,410444],[-19,0]],[[286709,410444],[-635,4132],[-197,2069],[-508,1252]],[[262830,415726],[-1089,-10]],[[261741,415716],[-35,1347]],[[242937,420420],[-5,-5730]],[[242932,414690],[-935,53]],[[241997,414743],[-158,1581]],[[283279,414590],[33,-4171]],[[283312,410419],[-28,0]],[[283284,410419],[-1756,39]],[[281528,410458],[270,1945],[385,4375]],[[279553,410473],[-342,-2]],[[279211,410471],[-1285,-23]],[[277926,410448],[-70,2461],[345,3475]],[[252342,420873],[-474,-4214]],[[251868,416659],[-1113,-118]],[[247383,417945],[-442,-2539]],[[246941,415406],[-262,-107],[-245,2264],[-607,13]],[[245827,417576],[4,1651]],[[252656,413381],[-30,-1626]],[[252626,411755],[-760,5]],[[251866,411760],[2,4899]],[[270140,418738],[-33,-4033],[-98,-688]],[[270009,414017],[-637,318]],[[269372,414335],[-891,173]],[[268481,414508],[58,3467]],[[266472,417707],[-35,-1886]],[[266437,415821],[-1449,559]],[[264988,416380],[37,2676]],[[239006,416248],[0,-1219]],[[239006,415029],[-1115,-150]],[[237891,414879],[-6,3227]],[[206434,415637],[-263,-1609]],[[206171,414028],[-698,-18]],[[205473,414010],[-2,411],[-773,-47]],[[231627,420500],[-1,-4856]],[[231626,415644],[-637,4]],[[230989,415648],[-625,3]],[[232903,420507],[433,-2548],[-67,-1277],[314,-1050]],[[233583,415632],[-88,0]],[[233495,415632],[-1249,8]],[[232246,415640],[-620,4]],[[234322,418054],[138,-4323]],[[234460,413731],[-255,-671],[-622,2572]],[[244178,419364],[30,-3036],[165,-1716]],[[244373,414612],[-1128,56]],[[243245,414668],[-313,22]],[[288510,412605],[38,24]],[[288548,412629],[-38,-24]],[[289158,416852],[-454,-2262],[-196,-1986]],[[288508,412604],[-541,-2144]],[[287967,410460],[-967,-20]],[[286709,410444],[-918,-3]],[[285791,410441],[-606,-7]],[[285185,410434],[-592,-13]],[[284593,410421],[124,2546],[-179,2927],[-329,1040]],[[200905,417336],[-779,-15],[-5,-3272],[-315,51],[0,-1663],[-1412,-63],[0,-2430],[-1717,-16],[1,-809],[-1384,192]],[[272795,414724],[-423,-10],[-218,-1046],[-319,59],[-5,-1091],[-317,66]],[[271513,412702],[-96,1662],[35,4100]],[[272818,418798],[-23,-4074]],[[259900,414582],[-209,-307]],[[259691,414275],[-842,-20]],[[258849,414255],[-8,4742]],[[257578,418958],[2,-4763]],[[257580,414195],[0,-1080]],[[257580,413115],[-876,35]],[[256704,413150],[-228,10],[1,1597]],[[245827,417576],[-8,-4829]],[[245819,412747],[1,-1636],[-1253,29]],[[244567,411140],[-193,1581],[-1,1891]],[[264988,416380],[-41,-2986]],[[264947,413394],[-298,808],[-748,-67]],[[274788,415938],[-600,318]],[[274188,416256],[87,2302]],[[291409,417105],[453,-5673],[-67,-3974]],[[291795,407458],[-8,-68]],[[291787,407390],[-674,1374],[-215,1839]],[[290898,410603],[-464,1138],[-81,1797],[-341,1920]],[[290012,415458],[-13,49]],[[289999,415507],[237,1029]],[[258849,414255],[-241,-37]],[[258608,414218],[-1028,-23]],[[274188,416256],[-53,-2244],[-194,-1196]],[[273941,412816],[-1155,355]],[[272786,413171],[9,1553]],[[292681,417934],[179,-419],[-159,-5398],[-46,2228],[-255,-3465],[103,-762],[-505,-2576],[-203,-84]],[[271513,412702],[-9,-1621]],[[271504,411081],[-1056,287]],[[270448,411368],[-261,927],[22,1648],[-200,74]],[[281528,410458],[-678,3]],[[280850,410461],[-105,6]],[[249547,415187],[-334,-14],[-10,-1089],[-815,-275]],[[248388,413809],[-1,3840]],[[161792,408143],[-202,-1300],[-433,-150],[-282,-3312],[-609,-862]],[[160266,402519],[-794,178]],[[159472,402697],[52,1462]],[[159524,404159],[106,2863],[-397,-48],[-8,2486],[153,591],[-290,1813]],[[255560,414746],[183,-1654],[-78,-4759]],[[255665,408333],[-417,-12]],[[255248,408321],[-3,5132]],[[277926,410448],[-234,-9]],[[277692,410439],[-800,-6]],[[276892,410433],[-426,0]],[[276466,410433],[-22,3775],[-205,1113]],[[268481,414508],[-116,-2693]],[[268365,411815],[-1169,326]],[[267196,412141],[-43,3579],[147,1917]],[[289999,415507],[-235,-1942],[-206,-351]],[[289558,413214],[-165,1989]],[[237891,414879],[8,-3256]],[[237899,411623],[-1244,80]],[[236655,411703],[-37,4578]],[[235541,416362],[8,-4089]],[[235549,412273],[-763,19]],[[234786,412292],[-326,1439]],[[181375,413882],[-2,-6763]],[[181373,407119],[-6,-16111]],[[181367,391008],[-2649,0]],[[178718,391008],[-2525,9020]],[[248388,413809],[0,-545]],[[248388,413264],[-1641,73]],[[246747,413337],[194,2069]],[[267196,412141],[-24,-2179]],[[267172,409962],[-1117,399]],[[266055,410361],[17,1045]],[[266072,411406],[162,-81],[203,4496]],[[246747,413337],[32,-697]],[[246779,412640],[-960,107]],[[200920,414122],[-228,4],[1,-10296]],[[200693,403830],[-885,2]],[[199808,403832],[-4513,9]],[[195295,403841],[-1,2439]],[[261741,415716],[-54,-4022]],[[261687,411694],[-224,-18]],[[261463,411676],[-823,-18]],[[260640,411658],[57,2964]],[[284593,410421],[-604,-5]],[[283989,410416],[-677,3]],[[289558,413214],[-576,-1173]],[[288982,412041],[-434,588]],[[288510,412605],[-2,-1]],[[251866,411760],[-181,-2586]],[[251685,409174],[-598,19]],[[251087,409193],[-316,20],[-8,2709],[-211,269]],[[250552,412191],[-1,1895]],[[206689,415638],[-255,-1611]],[[206434,414027],[-263,1]],[[241997,414743],[-31,-6459]],[[241966,408284],[-4,0]],[[241962,408284],[-1090,93]],[[240872,408377],[8,1624],[-470,45]],[[240410,410046],[29,6280]],[[266072,411406],[-502,924],[-631,518]],[[264939,412848],[8,546]],[[240410,410046],[-1140,67]],[[239270,410113],[-272,4],[8,4912]],[[236655,411703],[-1,-803]],[[236654,410900],[-1104,25]],[[235550,410925],[-1,1348]],[[274786,410447],[-874,-18]],[[273912,410429],[29,2387]],[[274787,414933],[-1,-4486]],[[276466,410433],[-1406,8]],[[275060,410441],[-274,6]],[[188080,412151],[154,-1357],[-372,-848],[15,-4365],[-94,-1439],[-489,-26],[-259,-1247]],[[187035,402869],[-501,282],[-67,4178],[-5094,-210]],[[262825,414085],[-5,-3543]],[[262820,410542],[-613,-224]],[[262207,410318],[-419,17],[-101,1359]],[[216580,415679],[-8,-8083]],[[216572,407596],[-1771,-3]],[[214801,407593],[0,109]],[[218453,415671],[-6,-8081]],[[218447,407590],[-1806,6]],[[216641,407596],[-69,0]],[[156605,411874],[135,-4012]],[[156740,407862],[1,-963],[-483,-512],[-37,-1772],[224,-3191],[-238,-1152],[-36,-1898],[296,-1404],[110,-1804],[361,-938]],[[156938,394228],[-728,41],[-164,-808],[-645,-63],[-455,-694]],[[154946,392704],[-541,2801],[109,2093],[-386,5926],[150,3428],[-35,2229],[-193,3367],[-469,3107]],[[226274,415670],[1,-6484]],[[226275,409186],[-1,-1617]],[[226274,407569],[-1548,10]],[[224726,407579],[-50,3]],[[224676,407582],[2,8092]],[[224676,407582],[-1502,8]],[[223174,407590],[-61,1]],[[223113,407591],[-2,8084]],[[227841,415668],[2,-6477]],[[227843,409191],[-1568,-5]],[[229411,415659],[-2,-8099]],[[229409,407560],[-425,-1]],[[228984,407559],[-1141,12]],[[227843,407571],[0,1620]],[[220016,415661],[-7,-8093]],[[220009,407568],[-1504,22]],[[218505,407590],[-58,0]],[[170987,398754],[-249,-368]],[[170738,398386],[-2476,18]],[[168262,398404],[-1,696],[-1014,5148],[112,6216],[-310,-1673]],[[221555,415666],[-7,-8087]],[[221548,407579],[-1487,-3]],[[220061,407576],[-52,-8]],[[223113,407591],[-1493,-14]],[[221620,407577],[-72,2]],[[230989,415648],[1,-8092]],[[230990,407556],[-955,2]],[[230035,407558],[-626,2]],[[210185,410789],[-3283,6]],[[206902,410795],[415,544],[-67,968],[390,100],[54,1353],[-365,-16],[-165,-1868],[-731,-134]],[[206433,411742],[1,2285]],[[233495,415632],[0,-6461]],[[233495,409171],[-625,0]],[[232870,409171],[-626,1]],[[232244,409172],[2,6468]],[[232244,409172],[-1,-1611],[-687,-7]],[[231556,407554],[-566,2]],[[234786,412292],[-21,-1747],[-200,349],[-276,-2271]],[[234289,408623],[-168,540],[-626,8]],[[290898,410603],[-392,-2264]],[[290506,408339],[-409,2275],[-302,2865]],[[289795,413479],[217,1979]],[[250552,412191],[-498,-1356],[-386,-1884],[4,-2170]],[[249672,406781],[-468,-23]],[[249204,406758],[-626,-24]],[[248578,406734],[-164,3649],[-26,2881]],[[239270,410113],[-31,-1637]],[[239239,408476],[-1338,-79]],[[237901,408397],[-2,3226]],[[243245,414668],[-18,-5394]],[[243227,409274],[-942,82],[-5,-1095],[-314,23]],[[256704,413150],[-10,-4876]],[[256694,408274],[-518,44]],[[256176,408318],[-511,15]],[[272786,413171],[-213,31],[-15,-3018],[100,-1891]],[[272658,408293],[-850,-351]],[[271808,407942],[-304,3139]],[[244567,411140],[169,-1392]],[[244736,409748],[-1143,47],[1,-537]],[[243594,409258],[-367,16]],[[260640,411658],[-101,-1640]],[[260539,410018],[-888,-25]],[[259651,409993],[40,4282]],[[269372,414335],[-25,-1734],[207,-77],[64,-3316]],[[269618,409208],[-318,123],[-21,-1093],[-338,107],[-318,-1008]],[[268623,407337],[-310,141],[52,4337]],[[205473,414010],[-1,-3087]],[[205472,410923],[-407,495],[-407,1458]],[[270448,411368],[146,-862],[-65,-3237]],[[270529,407269],[-311,107]],[[270218,407376],[-615,748],[15,1084]],[[259651,409993],[-1,-1094]],[[259650,408899],[-829,-88]],[[258821,408811],[-213,-27]],[[258608,408784],[0,5434]],[[202584,414234],[137,-2937],[562,-2151],[-209,-2932],[145,-2132]],[[203219,404082],[-612,-330]],[[202607,403752],[-1914,78]],[[204005,410027],[367,-1170],[-102,-1326]],[[204270,407531],[-525,-3718],[-328,268]],[[203417,404081],[-198,1]],[[258608,408784],[-389,-585],[-532,8]],[[257687,408207],[-86,542],[-21,4366]],[[264939,412848],[-109,-226],[-59,-4847]],[[264771,407775],[-700,214]],[[264071,407989],[-317,29]],[[263754,408018],[-17,6096]],[[263754,408018],[1,-414],[-936,-23]],[[262819,407581],[1,2961]],[[206433,411742],[-1,-3288]],[[206432,408454],[13,-902]],[[206445,407552],[-516,-5958],[-264,-2165]],[[205665,399429],[-5,0]],[[205660,399429],[-187,-2],[-3,8128]],[[205470,407555],[2,3368]],[[206902,410795],[-80,-2162],[-167,812],[-223,-991]],[[290506,408339],[-301,-1740]],[[290205,406599],[-214,1002]],[[289991,407601],[-385,1832],[-352,446],[-281,1742]],[[288973,411621],[822,1858]],[[255248,408321],[1,-2424]],[[255249,405897],[-1194,78]],[[254055,405975],[-17,3810]],[[254038,409785],[80,3609]],[[254038,409785],[-270,-622],[-1139,-18]],[[252629,409145],[-3,2610]],[[248578,406734],[-634,-33]],[[247944,406701],[-412,-2]],[[247532,406699],[3,2166],[-202,555],[5,1620],[-635,683]],[[246703,411723],[76,917]],[[273912,410429],[-309,-2118]],[[273603,408311],[-264,-1244]],[[273339,407067],[6,599],[-687,627]],[[257687,408207],[-84,-2441]],[[257603,405766],[-705,61]],[[256898,405827],[-209,8],[5,2439]],[[205470,407555],[-1200,-24]],[[266055,410361],[-47,-3102]],[[266008,407259],[-855,352]],[[265153,407611],[-382,164]],[[246703,411723],[-131,-1726],[182,-3269]],[[246754,406728],[-91,-2345]],[[246663,404383],[-901,73]],[[245762,404456],[-253,772],[-419,2923]],[[245090,408151],[-354,1597]],[[288652,406259],[12,7]],[[288664,406266],[-12,-7]],[[288613,408309],[-8,431]],[[288605,408740],[8,-431]],[[288982,412041],[-534,-3559],[147,-1016],[-73,-1880],[204,-1804]],[[288726,403782],[-376,-1333],[-304,88]],[[288046,402537],[-18,1506]],[[288028,404043],[-61,6417]],[[251087,409193],[1,-5735],[-319,52]],[[250769,403510],[-1089,3],[-8,3268]],[[235550,410925],[3,-4036]],[[235553,406889],[-1398,55]],[[234155,406944],[134,1679]],[[192584,405746],[-2935,11],[-153,2687],[-321,1687]],[[189175,410131],[0,2024]],[[189175,410131],[1,-4419],[-151,2],[5,-8099]],[[189030,397615],[-1544,14],[-447,229]],[[187039,397858],[-4,5011]],[[268623,407337],[-26,-1603]],[[268597,405734],[-717,218],[40,495],[-788,179]],[[267132,406626],[40,3336]],[[159524,404159],[-693,578],[2,-532],[-1674,-52]],[[157159,404153],[10,3692],[-429,17]],[[252629,409145],[5,-3808]],[[252634,405337],[-317,9],[-372,2453],[-255,17],[-5,1358]],[[262207,410318],[-5,-3507]],[[262202,406811],[-729,-13]],[[261473,406798],[-10,4878]],[[261473,406798],[1,-1345]],[[261474,405453],[-925,-9]],[[260549,405444],[-10,4574]],[[247532,406699],[-778,29]],[[237901,408397],[-2,-1616]],[[237899,406781],[-1252,49]],[[236647,406830],[7,4070]],[[289029,404123],[-16,44]],[[289013,404167],[16,-44]],[[289991,407601],[-61,-2065],[-434,1889],[-494,-3193]],[[289002,404232],[-350,2027]],[[288664,406266],[-51,2043]],[[288605,408740],[368,2881]],[[164781,405356],[-1401,-15],[-152,1284],[-273,169],[-234,-1182],[-772,-1245]],[[161949,404367],[29,4517]],[[271808,407942],[-352,-103],[16,-1873],[-379,-570]],[[271093,405396],[-587,184],[23,1689]],[[236647,406830],[-6,-1350]],[[236641,405480],[-1087,27]],[[235554,405507],[-1,1382]],[[210183,407557],[-20,1]],[[210163,407558],[-2636,-8]],[[207527,407550],[-1082,2]],[[168262,398404],[-491,-22],[-237,-2422],[1,-1714],[314,16],[-14,-8144],[-697,1]],[[167138,386119],[-481,2256]],[[166657,388375],[-56,3727],[-157,-51],[-41,4121],[307,670],[-3,1729],[-666,41]],[[166041,398612],[-32,2040],[-419,1033]],[[165590,401685],[652,1617],[544,5284]],[[291787,407390],[22,-3591],[-686,-1401]],[[291123,402398],[-572,644]],[[290551,403042],[14,1877],[-360,1680]],[[262819,407581],[-2,-849]],[[262817,406732],[-615,79]],[[283284,410419],[-281,-1862],[-298,-5500]],[[282705,403057],[-115,-74]],[[282590,402983],[-298,3218]],[[282292,406201],[-167,1077],[132,1025],[-513,247]],[[281744,408550],[-440,1436],[-237,-1383],[-192,274]],[[280875,408877],[-25,1584]],[[280875,408877],[-278,-307],[-96,-1936]],[[280501,406634],[-526,344]],[[279975,406978],[-386,1376],[-475,-3170],[-283,768]],[[278831,405952],[380,4519]],[[278831,405952],[-568,-2832]],[[278263,403120],[-600,-2269]],[[277663,400851],[29,9588]],[[288028,404043],[-186,-68]],[[287842,403975],[-578,293],[198,3147],[-296,-282]],[[287166,407133],[-438,3311]],[[276892,410433],[-365,-5307]],[[276527,405126],[-123,314]],[[276404,405440],[-598,2940],[-675,502]],[[275131,408882],[-71,1559]],[[277663,400851],[0,-206]],[[277663,400645],[-557,1423],[-342,-760]],[[276764,401308],[-239,1284]],[[276525,402592],[2,2534]],[[287166,407133],[-74,-2015],[-419,-1266],[83,1880],[-379,-1384]],[[286377,404348],[-194,1933],[-292,888],[-100,3272]],[[275131,408882],[-285,-1378],[11,-1745]],[[274857,405759],[-140,-754]],[[274717,405005],[-207,396]],[[274510,405401],[-283,562],[-368,2260],[-256,88]],[[286377,404348],[69,-1032],[-251,-2456],[-293,639]],[[285902,401499],[0,2434],[-506,6],[2,-1756],[256,-748]],[[285654,401435],[-221,-442]],[[285433,400993],[-513,2542]],[[284920,403535],[-27,1753],[292,5146]],[[284920,403535],[-797,69]],[[284123,403604],[171,2562],[-558,2377],[253,1873]],[[284123,403604],[-2,-8]],[[284121,403596],[-805,-2420]],[[283316,401176],[-301,1527],[-310,354]],[[267132,406626],[-296,-2586]],[[266836,404040],[-607,26]],[[266229,404066],[40,3087],[-261,106]],[[192583,405610],[37,-824],[-294,-4892],[-129,-5401],[227,-2018],[156,-4769]],[[192580,387706],[-3568,0],[0,190]],[[189012,387896],[18,9719]],[[240872,408377],[-43,-5375]],[[240829,403002],[-231,409],[-164,-2185]],[[240434,401226],[-711,2931]],[[239723,404157],[-346,980],[-138,3339]],[[260549,405444],[-153,-1912]],[[260396,403532],[-746,-50]],[[259650,403482],[0,5417]],[[281744,408550],[-325,-479],[-251,-3773]],[[281168,404298],[-330,1392]],[[280838,405690],[-337,944]],[[245090,408151],[-786,-2744],[61,-2459]],[[244365,402948],[-779,380]],[[243586,403328],[8,5930]],[[254055,405975],[-144,-1905]],[[253911,404070],[-1278,-83]],[[252633,403987],[1,1350]],[[243586,403328],[-1650,162]],[[241936,403490],[26,4794]],[[270218,407376],[-23,-1680],[-315,123],[-26,-1652]],[[269854,404167],[-626,216],[-9,-490],[-644,-24]],[[268575,403869],[22,1865]],[[227843,407571],[-4,-4859]],[[227839,402712],[-1559,8]],[[226280,402720],[-6,4849]],[[232870,409171],[-17,-4355]],[[232853,404816],[-53,-3775]],[[232800,401041],[-1243,5]],[[231557,401046],[-1,6508]],[[234155,406944],[373,-2131]],[[234528,404813],[-590,6]],[[233938,404819],[-1085,-3]],[[252633,403987],[0,-2969]],[[252633,401018],[-933,23]],[[251700,401041],[-932,31]],[[250768,401072],[1,2438]],[[279975,406978],[-539,-3797],[-360,-1740]],[[279076,401441],[-431,1393],[-382,286]],[[273339,407067],[-230,-1522]],[[273109,405545],[-695,-2160]],[[272414,403385],[-195,1248],[-314,-1222],[-55,-1446],[-293,114],[-131,-1044]],[[271426,401035],[10,998],[-367,905],[24,2458]],[[276404,405440],[-240,-950],[-479,-155]],[[275685,404335],[-828,1424]],[[259650,403482],[-372,-60]],[[259278,403422],[-462,-46]],[[258816,403376],[5,5435]],[[161949,404367],[-299,-279],[-425,-2782],[0,-3647]],[[161225,397659],[-377,-706]],[[160848,396953],[-363,-449],[-90,-982],[-129,6997]],[[258816,403376],[-362,-36]],[[258454,403340],[-695,149]],[[257759,403489],[-4,2280],[-152,-3]],[[282292,406201],[-394,-2354],[-185,-1907]],[[281713,401940],[-545,2358]],[[239723,404157],[-203,523],[-147,-1561],[-300,-405],[-194,-1442],[-195,1194]],[[238684,402466],[-29,-1190],[-453,360],[-301,-767]],[[237901,400869],[-2,5912]],[[241936,403490],[-12,-1887]],[[241924,401603],[-322,44]],[[241602,401647],[-773,1355]],[[256176,408318],[-109,-1613],[-6,-4868]],[[256061,401837],[-1014,8]],[[255047,401845],[202,1649],[0,2403]],[[256898,405827],[-7,-2442],[-310,8],[-2,-3249]],[[256579,400144],[-519,69]],[[256060,400213],[1,1624]],[[274510,405401],[-752,-2906]],[[273758,402495],[-136,1718],[-196,-664]],[[273426,403549],[-49,2100],[-268,-104]],[[246361,401187],[-1284,58],[-7,-1638],[-209,11]],[[244861,399618],[-414,50]],[[244447,399668],[-82,3280]],[[245762,404456],[468,-1904],[131,-1365]],[[264071,407989],[93,-711],[-59,-4822]],[[264105,402456],[-1299,241]],[[262806,402697],[11,4035]],[[265153,407611],[-83,-5848]],[[265070,401763],[-704,293]],[[264366,402056],[-261,400]],[[157159,404153],[-102,-1293],[315,-1811],[516,-796],[-42,-2231],[424,-2417]],[[158270,395605],[-153,-1117]],[[158117,394488],[-192,-2961],[-455,-702]],[[157470,390825],[-532,3403]],[[214801,407593],[7,-8099]],[[214808,399494],[2,-1604]],[[214810,397890],[-3111,-175]],[[211699,397715],[24,9831]],[[289145,403438],[-116,685]],[[289013,404167],[-11,65]],[[290551,403042],[-150,-2724]],[[290401,400318],[-713,1073],[-543,2047]],[[223174,407590],[-9,-8099]],[[223165,399491],[-1516,-10]],[[221649,399481],[-38,0]],[[221611,399481],[9,8096]],[[265463,401619],[-346,126]],[[265117,401745],[-47,18]],[[266229,404066],[-541,-2162],[-225,-285]],[[216641,407596],[-7,-8066]],[[216634,399530],[-241,-6]],[[216393,399524],[-1585,-30]],[[218505,407590],[-3,-8092]],[[218502,399498],[-252,0]],[[218250,399498],[-1616,32]],[[224726,407579],[-8,-6473]],[[224718,401106],[0,-1621]],[[224718,399485],[-1525,8]],[[223193,399493],[-28,-2]],[[220061,407576],[-7,-8104]],[[220054,399472],[-1552,26]],[[228984,407559],[-7,-6447]],[[228977,401112],[-5,-1634]],[[228972,399478],[-1136,-7]],[[227836,399471],[3,3241]],[[226280,402720],[-2,-1625]],[[226278,401095],[-1560,11]],[[221611,399481],[-1512,-5]],[[220099,399476],[-45,-4]],[[230035,407558],[-404,-3029],[469,-4113],[472,-186]],[[230572,400230],[-5,-2410],[-307,8]],[[230260,397828],[0,542],[-974,290],[4,2434],[-313,18]],[[207527,407550],[-7,-8123]],[[207520,399427],[-1032,5]],[[206488,399432],[-823,-3]],[[210163,407558],[-15,-13022],[-936,36]],[[209212,394572],[12,4831],[-1704,24]],[[231557,401046],[-18,-1625]],[[231539,399421],[-119,1201],[-442,280],[-406,-672]],[[211699,397715],[-28,-7915]],[[211671,389800],[1,-1622],[-925,-166]],[[210747,388012],[-1530,111]],[[209217,388123],[-5,6449]],[[205660,399429],[4,-8051]],[[205664,391378],[-1785,-68]],[[203879,391310],[176,2110],[-338,2531],[-236,-28],[-218,2136]],[[203263,398059],[20,4732],[134,1290]],[[271426,401035],[-87,-662]],[[271339,400373],[-1426,435]],[[269913,400808],[-59,3359]],[[187039,397858],[-587,-3354],[17,-2559],[-254,-958],[-384,92],[-188,-2026]],[[185643,389053],[-4276,11]],[[181367,389064],[0,1944]],[[280838,405690],[20,-2092],[-220,-1798],[43,-1668],[-291,-1467]],[[280390,398665],[-1091,2059],[-223,717]],[[235554,405507],[-2,-5521]],[[235552,399986],[-482,732]],[[235070,400718],[-348,1876],[57,1415],[-251,804]],[[262806,402697],[-686,37]],[[262120,402734],[-425,21],[-218,-745]],[[261477,402010],[-3,3443]],[[237901,400869],[-183,146],[-782,-1329]],[[236936,399686],[-300,1184]],[[236636,400870],[5,4610]],[[250768,401072],[-307,-8],[0,-3525]],[[250461,397539],[-935,2],[-151,-542]],[[249375,396999],[-163,-3]],[[249212,396996],[-8,9762]],[[249212,396996],[-1246,20]],[[247966,397016],[-6,4876]],[[247960,401892],[-16,4809]],[[164769,402898],[-1778,-16],[-231,-508],[-693,-3277],[-169,-1785],[-673,347]],[[247960,401892],[-463,-687],[1,-945],[-743,197],[-64,-1253]],[[246691,399204],[-28,5179]],[[268575,403869],[-39,-2983]],[[268536,400886],[-64,-729]],[[268472,400157],[-1582,536]],[[266890,400693],[-54,3347]],[[282590,402983],[-306,-3505]],[[282284,399478],[-571,2462]],[[195295,403841],[-25,-16135]],[[195270,387706],[-2690,0]],[[255047,401845],[-68,-1899]],[[254979,399946],[-896,325]],[[254083,400271],[-160,-18]],[[253923,400253],[-12,3817]],[[273426,403549],[-646,-1537]],[[272780,402012],[-366,1373]],[[257759,403489],[-152,-219],[7,-3170]],[[257614,400100],[-1035,44]],[[275685,404335],[85,-2808]],[[275770,401527],[-169,-2411]],[[275601,399116],[-203,-165]],[[275398,398951],[-825,1176]],[[274573,400127],[195,794],[-216,1514],[165,2570]],[[281713,401940],[-331,-4250]],[[281382,397690],[-453,-529]],[[280929,397161],[-54,1696],[-336,-1326],[-249,538]],[[280290,398069],[100,596]],[[281262,400644],[0,0]],[[236636,400870],[-257,453],[-437,-2067],[-406,-129]],[[235536,399127],[16,859]],[[261477,402010],[-400,-1353]],[[261077,400657],[-346,-1150],[-342,-60]],[[260389,399447],[7,4085]],[[276525,402592],[-354,-957],[-401,-108]],[[274573,400127],[-368,-1329]],[[274205,398798],[-236,255]],[[273969,399053],[-281,3045],[70,397]],[[233938,404819],[-17,-6983]],[[233921,397836],[-873,234]],[[233048,398070],[-272,169],[24,2802]],[[235070,400718],[-349,74],[-25,-3937]],[[234696,396855],[-411,-173]],[[234285,396682],[-361,-327],[-3,1481]],[[159472,402697],[-103,-2316],[298,-2192],[10,-2579]],[[159677,395610],[-1407,-5]],[[240434,401226],[-265,-1911],[27,-1099]],[[240196,398216],[-322,-2537]],[[239874,395679],[-1243,272]],[[238631,395951],[53,6515]],[[272780,402012],[-162,-1544]],[[272618,400468],[-357,-942],[-433,-2022]],[[271828,397504],[-465,1316]],[[271363,398820],[-24,1553]],[[246691,399204],[104,-2426],[318,-370]],[[247113,396408],[-268,-1766],[-327,1147]],[[246518,395789],[-157,5398]],[[269913,400808],[-106,32],[-45,-3310]],[[269762,397530],[-314,138]],[[269448,397668],[30,1912],[-311,800],[-631,506]],[[273969,399053],[-615,-1858]],[[273354,397195],[-359,391]],[[272995,397586],[-242,75],[-135,2807]],[[288046,402537],[11,-941]],[[288057,401596],[-639,-5]],[[287418,401591],[-372,-2533],[-163,577],[-139,-1618],[-138,1747],[461,4160],[775,51]],[[203263,398059],[-1078,34]],[[202185,398093],[231,2578],[24,2142],[167,939]],[[253923,400253],[-983,-52]],[[252940,400201],[-307,817]],[[266890,400693],[-89,-2651]],[[266801,398042],[-801,-647]],[[266000,397395],[-555,16],[18,4208]],[[285892,400881],[-123,-38]],[[285769,400843],[123,38]],[[285902,401499],[-150,-621]],[[285752,400878],[-98,557]],[[285433,400993],[-397,-2058]],[[285036,398935],[-134,518]],[[284902,399453],[-167,-26],[-614,4169]],[[199808,403832],[102,-2045]],[[199910,401787],[-296,-715]],[[199614,401072],[-738,-3251],[-270,662],[-334,-353],[-440,-2814],[-665,-1481],[1,-2999]],[[197168,390836],[-2,-3130],[-1896,0]],[[202185,398093],[-59,-1116]],[[202126,396977],[-415,808],[-150,-1174],[-541,1040],[-198,1576],[-600,-2],[-312,2562]],[[289289,396094],[-480,-882],[-190,-1250],[-469,-136]],[[288150,393826],[-70,5854]],[[288080,399680],[-23,1916]],[[288726,403782],[311,-1994],[21,-3695],[231,-1999]],[[258454,403340],[32,-5425],[144,8]],[[258630,397923],[2,-1078]],[[258632,396845],[-1016,28]],[[257616,396873],[-2,3227]],[[260389,399447],[-315,-33],[3,-1128]],[[260077,398286],[-173,-511],[-611,176]],[[259293,397951],[-15,5471]],[[244447,399668],[-615,99],[-12,-1653]],[[243820,398114],[-1327,96]],[[242493,398210],[15,3270],[-584,123]],[[284902,399453],[-317,-3074]],[[284585,396379],[-327,-587]],[[284258,395792],[-575,2284]],[[283683,398076],[-376,336],[-185,1328],[194,1436]],[[259293,397951],[-663,-28]],[[241246,396474],[-791,165],[-259,1577]],[[241602,401647],[-356,-5173]],[[279076,401441],[-306,-3713],[122,-2044],[-246,-2142]],[[278646,393542],[-601,2684]],[[278045,396226],[-22,130]],[[278023,396356],[164,2029],[-157,242],[174,1961],[-541,57]],[[291123,402398],[69,-688],[-433,-4686],[-217,-1125],[-299,-2],[197,2821],[-39,1600]],[[283683,398076],[-584,-3904]],[[283099,394172],[-333,1775]],[[282766,395947],[-855,1322]],[[281911,397269],[373,2209]],[[164778,400098],[2,-985]],[[164780,399113],[3,-842]],[[164783,398271],[-389,10],[-272,-829],[-495,64],[-430,-2100],[-547,1773],[-795,-2035],[-254,-3501]],[[161601,391653],[-949,430]],[[160652,392083],[47,3550],[149,1320]],[[264366,402056],[-167,-4639]],[[264199,397417],[-520,1367]],[[263679,398784],[-327,-385]],[[263352,398399],[-336,1361],[-212,-782]],[[262804,398978],[2,3719]],[[262120,402734],[-186,-6686]],[[261934,396048],[-10,-344]],[[261924,395704],[-187,-294]],[[261737,395410],[-674,-15]],[[261063,395395],[14,5262]],[[262804,398978],[-161,-1387]],[[262643,397591],[-709,-1543]],[[227836,399471],[-2,-3241]],[[227834,396230],[-1551,10]],[[226283,396240],[-5,4855]],[[160652,392083],[-329,7]],[[160323,392090],[-249,594],[-92,1588],[-305,1338]],[[276764,401308],[-43,-2135]],[[276721,399173],[-197,-2651],[-527,-488]],[[275997,396034],[78,1670],[-474,1412]],[[238631,395951],[-2,-269]],[[238629,395682],[-940,177],[-5,-541],[-774,157]],[[236910,395475],[26,4211]],[[278023,396356],[-421,199],[-771,1158],[-110,1460]],[[264942,392757],[-495,1045]],[[264447,393802],[-4,881]],[[264443,394683],[-244,2734]],[[265117,401745],[-175,-8988]],[[281911,397269],[-117,-636]],[[281794,396633],[-412,1057]],[[247966,397016],[-356,-13],[-6,-1412]],[[247604,395591],[-491,817]],[[286601,396690],[-270,-2438],[180,3428],[90,-990]],[[288080,399680],[-243,-644],[-318,-3542]],[[287519,395494],[-452,119]],[[287067,395613],[-237,-813],[-18,1649],[166,2200],[440,2942]],[[256060,400213],[0,-4914]],[[256060,395299],[-786,-99]],[[255274,395200],[-295,4746]],[[202126,396977],[391,-1636],[384,-265],[-241,-1240],[-125,-2429],[202,-1122],[71,-1957],[299,-2059]],[[203107,386269],[-2100,53],[-1,-5186]],[[201006,381136],[-1581,2]],[[199425,381138],[-185,2880]],[[199240,384018],[376,1],[-2,6825]],[[199614,390844],[0,10228]],[[266000,397395],[-90,-7128]],[[265910,390267],[-554,2429]],[[265356,392696],[-414,61]],[[242493,398210],[-120,-2235],[32,-2529],[-220,-3074]],[[242185,390372],[-394,741],[-91,1045]],[[241700,392158],[-288,3406]],[[241412,395564],[-166,910]],[[166041,398612],[-586,525],[-675,-24]],[[280290,398069],[-483,-2836],[-192,-318],[-231,-2313]],[[279384,392602],[-347,1620],[-177,-1640]],[[278860,392582],[-214,960]],[[286072,395357],[-240,-2629],[72,-1051]],[[285904,391677],[-438,652]],[[285466,392329],[40,2579],[-96,1946],[-374,2081]],[[285752,400878],[17,-35]],[[285892,400881],[302,-2196],[-274,-289],[359,-1162],[-228,-562],[21,-1315]],[[236910,395475],[-14,-1566]],[[236896,393909],[-1362,261]],[[235534,394170],[2,3666]],[[235536,397836],[0,1291]],[[246518,395789],[-459,-1048],[-357,-130]],[[245702,394611],[-426,37],[-426,2232]],[[244850,396880],[11,2738]],[[230260,397828],[-2,-3239]],[[230258,394589],[-1082,7]],[[229176,394596],[-204,1767],[0,3115]],[[199614,390844],[-2446,-8]],[[226283,396240],[-1,-1625]],[[226282,394615],[-1549,-6]],[[224733,394609],[-15,4876]],[[233048,398070],[-1,-3479]],[[233047,394591],[-1242,3]],[[231805,394594],[-1,4258],[-265,569]],[[251700,401041],[-3,-5671],[315,56]],[[252012,395426],[-4,-1645]],[[252008,393781],[-1234,-54],[-1,-1621]],[[250773,392106],[-323,106]],[[250450,392212],[11,5327]],[[252940,400201],[-3,-4846]],[[252937,395355],[-925,71]],[[231805,394594],[2,-2436]],[[231807,392158],[-1134,-2]],[[230673,392156],[-105,1611],[-310,822]],[[269448,397668],[-55,-3547],[-336,-2]],[[269057,394119],[-207,86]],[[268850,394205],[-308,108],[-130,1735]],[[268412,396048],[60,4109]],[[270916,394754],[20,1086],[-281,1702],[-269,-1234]],[[270386,396308],[9,758],[-633,464]],[[271363,398820],[-98,-3219],[-349,-847]],[[235536,397836],[-739,279],[-101,-1260]],[[268412,396048],[-1128,225],[-164,1036]],[[267120,397309],[-319,733]],[[261063,395395],[-665,-1817]],[[260398,393578],[-311,-147]],[[260087,393431],[-10,4855]],[[272995,397586],[-322,-2153]],[[272673,395433],[-626,51]],[[272047,395484],[-219,2020]],[[255274,395200],[-23,-931]],[[255251,394269],[-1050,-45]],[[254201,394224],[-106,0]],[[254095,394224],[-12,6047]],[[254095,394224],[-871,-48]],[[253224,394176],[-287,83],[0,1096]],[[257616,396873],[-2,-1632]],[[257614,395241],[-617,-10]],[[256997,395231],[-542,-4]],[[256455,395227],[-395,72]],[[275398,398951],[-49,-2704],[-193,-1561],[-17,-2747]],[[275139,391939],[-179,223]],[[274960,392162],[-177,2250],[-237,820]],[[274546,395232],[-309,1941],[-32,1625]],[[178718,391008],[0,-11659],[-2488,-10],[-9,-22497]],[[176221,356842],[9,-15661]],[[176230,341181],[-3544,18062]],[[172686,359243],[1,19187],[-1464,8789]],[[171223,387219],[-1407,8282],[-1,1512],[923,1373]],[[244850,396880],[-2,-2773],[-420,28],[-8,-2523]],[[244420,391612],[-356,-627],[-262,515]],[[243802,391500],[-19,-7]],[[243783,391493],[37,6621]],[[263352,398399],[22,-5065]],[[263374,393334],[-124,-465]],[[263250,392869],[-377,1491]],[[262873,394360],[-210,810]],[[262663,395170],[-20,2421]],[[288150,393826],[43,-3599]],[[288193,390227],[-151,927],[-491,-152]],[[287551,391002],[-166,1427],[180,785]],[[287565,393214],[-46,2280]],[[218250,399498],[-19,-8071]],[[218231,391427],[-863,14]],[[217368,391441],[-992,-7]],[[216376,391434],[17,8090]],[[216376,391434],[-231,-10]],[[216145,391424],[-1332,-40]],[[214813,391384],[-3,6506]],[[223193,399493],[-14,-8123]],[[223179,391370],[-1512,-4]],[[221667,391366],[-36,-1]],[[221631,391365],[18,8116]],[[220099,399476],[-16,-8096]],[[220083,391380],[-260,16]],[[219823,391396],[-1228,34]],[[218595,391430],[-364,-3]],[[229176,394596],[-104,-1078],[-8,-3776]],[[229064,389742],[-1230,0]],[[227834,389742],[0,6488]],[[224733,394609],[-4,-3237]],[[224729,391372],[-1523,-3]],[[223206,391369],[-27,1]],[[221631,391365],[-1548,15]],[[285466,392329],[-31,-1557]],[[285435,390772],[-7,-2276]],[[285428,388496],[-134,1401],[-606,810],[-230,-838],[-81,1376]],[[284377,391245],[169,2220]],[[284546,393465],[298,1555],[-259,1359]],[[206488,399432],[13,-4858],[-121,-1294],[371,-47],[-10,-2736]],[[206741,390497],[-830,-43],[-247,924]],[[209217,388123],[-2473,-52]],[[206744,388071],[-3,2426]],[[264443,394683],[-524,-1279]],[[263919,393404],[-110,900],[24,2588],[-154,1892]],[[278045,396226],[-294,-514],[-232,-1659],[80,-1120],[-325,-2167]],[[277274,390766],[-416,1407],[-243,-3512],[-463,-1712],[-244,275],[-358,-1592]],[[275550,385632],[171,2537],[-267,3167]],[[275454,391336],[428,949],[115,3749]],[[166657,388375],[-699,3239]],[[165958,391614],[-902,4160]],[[165056,395774],[-269,1239],[-4,1258]],[[275454,391336],[-315,603]],[[274546,395232],[-268,-471],[-788,-2996]],[[273490,391765],[-200,1393],[150,994],[-86,3043]],[[280929,397161],[39,-1198],[-240,-2110]],[[280728,393853],[-452,-1704],[-259,-2493]],[[280017,389656],[-633,2946]],[[263919,393404],[-308,-258]],[[263611,393146],[-237,188]],[[272047,395484],[-122,-4567],[68,-1110]],[[271993,389807],[-308,-1085],[-178,1340]],[[271507,390062],[-213,1013]],[[271294,391075],[-378,3679]],[[171223,387219],[-1840,-10800],[-214,47]],[[169169,376466],[-2031,9653]],[[260087,393431],[-260,-1361]],[[259827,392070],[-266,627],[-811,-80]],[[258750,392617],[-16,4231],[-102,-3]],[[234285,396682],[0,-4534]],[[234285,392148],[-1238,4]],[[233047,392152],[0,2439]],[[165056,395774],[67,-1277],[-535,-3016]],[[164588,391481],[-682,-2964],[-866,-752],[-371,962],[-735,-860]],[[161934,387867],[-256,3852],[-77,-66]],[[243783,391493],[-601,-672],[-237,-1354]],[[242945,389467],[-301,-675],[-459,1580]],[[241412,395564],[-331,-4506],[-621,73]],[[240460,391131],[-613,170]],[[239847,391301],[27,4378]],[[284258,395792],[-146,-763]],[[284112,395029],[62,-330]],[[284174,394699],[109,-599]],[[284283,394100],[166,-999]],[[284449,393101],[22,-1315],[-319,-1468],[-235,541]],[[283917,390859],[-818,3313]],[[283747,394572],[0,0]],[[283737,394153],[0,0]],[[203879,391310],[232,-1351],[-62,-2148],[-284,-1101]],[[203765,386710],[-658,-441]],[[235534,394170],[-3,-2031]],[[235531,392139],[-1246,9]],[[267120,397309],[10,-7398]],[[267130,389911],[-689,1521],[-363,-1183]],[[266078,390249],[-168,18]],[[189012,387896],[-1284,-153]],[[187728,387743],[-2094,147]],[[185634,387890],[9,1163]],[[214813,391384],[1,-1534]],[[214814,389850],[-3143,-50]],[[273490,391765],[-131,-932]],[[273359,390833],[-145,-1040]],[[273214,389793],[-209,605],[-29,1581],[-303,3454]],[[281794,396633],[-352,-2134]],[[281442,394499],[-181,-1569],[-249,-398]],[[281012,392532],[-284,1321]],[[270386,396308],[-126,-2312],[-210,-922],[124,-3421],[-112,-240]],[[270062,389413],[-201,-166]],[[269861,389247],[-208,45],[20,1692],[-328,120],[-31,1671],[-276,127],[19,1217]],[[262663,395170],[-751,43],[12,491]],[[271294,391075],[-509,-437],[-303,-920],[26,-2484]],[[270508,387234],[-446,2179]],[[250450,392212],[-956,23]],[[249494,392235],[-119,4764]],[[268850,394205],[-252,-4690],[-210,-491]],[[268388,389024],[-201,3414],[-398,-532]],[[267789,391906],[-350,-1992],[-309,-3]],[[282766,395947],[-168,-2056],[513,-5136]],[[283111,388755],[-288,-2729]],[[282823,386026],[-280,59],[-558,5268]],[[281985,391353],[-543,3146]],[[249494,392235],[6,-1626],[-311,-14]],[[249189,390595],[-1322,102]],[[247867,390697],[42,2085]],[[247909,392782],[135,616]],[[248044,393398],[7,862],[-447,1331]],[[245702,394611],[-17,-6019]],[[245685,388592],[-299,1157],[-435,348],[-393,1320]],[[244558,391417],[-138,195]],[[284546,393465],[-223,1828]],[[284323,395293],[-65,499]],[[258750,392617],[-93,-1409]],[[258657,391208],[-1043,-28]],[[257614,391180],[0,4061]],[[248044,393398],[-494,1528],[-406,-1104],[-259,-2536],[-553,-983]],[[246332,390303],[-514,-1949],[-133,238]],[[289289,396094],[351,-2735],[270,-53],[-16,-3322],[132,-3185]],[[290026,386799],[-57,0]],[[289969,386799],[-44,0]],[[289925,386799],[-712,13]],[[289213,386812],[-982,152],[-22,1873]],[[288209,388837],[-16,1390]],[[227834,389742],[-1541,10]],[[226293,389752],[-11,4863]],[[278860,392582],[-466,-5288]],[[278394,387294],[-254,-1223]],[[278140,386071],[-449,839],[-166,1724],[-310,767]],[[277215,389401],[59,1365]],[[239847,391301],[-20,-3034]],[[239827,388267],[-622,98],[-4,-541],[-615,115]],[[238586,387939],[4,814]],[[238590,388753],[39,6929]],[[287565,393214],[-230,-1317],[167,-933],[-195,-1782],[-402,1980],[-480,1174],[215,1899],[256,-1835],[-68,1744],[239,1469]],[[283917,390859],[-162,-3064]],[[283755,387795],[-414,1424],[-230,-464]],[[283312,392781],[-46,161]],[[283266,392942],[46,-161]],[[273214,389793],[-307,-1570]],[[272907,388223],[-755,414],[-159,1170]],[[238590,388753],[-1541,208]],[[237049,388961],[-152,25],[-1,4923]],[[284458,394060],[-175,40]],[[284174,394699],[-62,330]],[[284323,395293],[135,-1233]],[[165958,391614],[-111,-1811],[216,-2119],[-268,-3201]],[[165795,384483],[-318,1674],[-351,-1063],[-391,1452]],[[164735,386546],[-147,1346]],[[164588,387892],[0,3589]],[[262873,394360],[-58,-1367],[-578,-417]],[[262237,392576],[-496,-1309]],[[261741,391267],[-4,4143]],[[241700,392158],[-279,-5795]],[[241421,386363],[-363,55]],[[241058,386418],[-603,3090],[5,1623]],[[160323,392090],[-77,-1063],[333,-1451],[-117,-1633],[136,-803],[-47,-1999],[-216,-907]],[[160335,384234],[-267,44],[0,3908],[-681,142],[-457,-373]],[[158930,387955],[-515,6080],[-298,453]],[[261741,391267],[-366,838]],[[261375,392105],[-331,-490],[66,-2302]],[[261110,389313],[-395,370]],[[260715,389683],[-3,1350],[-313,1081],[-1,1464]],[[253224,394176],[-108,-3961],[133,-657]],[[253249,389558],[-1250,127]],[[251999,389685],[9,4096]],[[256455,395227],[-410,-1812],[-93,-2129],[104,-2737]],[[256056,388549],[-615,-232]],[[255441,388317],[-373,-1489],[-409,-715]],[[254659,386113],[-35,905],[308,954],[-17,1004]],[[254915,388976],[435,3214],[-99,2079]],[[257614,391180],[2,-2983]],[[257616,388197],[-674,-392]],[[256942,387805],[55,7426]],[[256942,387805],[-413,172]],[[256529,387977],[-473,572]],[[274960,392162],[-143,-1744],[-396,-2215]],[[274421,388203],[-376,576],[-266,-940]],[[273779,387839],[-420,2994]],[[247909,392782],[-238,-999],[-192,-2324],[176,-1160]],[[247655,388299],[-25,-168]],[[247630,388131],[-211,-2528]],[[247419,385603],[-176,2125],[-512,9],[-410,-651]],[[246321,387086],[11,3217]],[[264517,389239],[-755,-736]],[[263762,388503],[-151,4643]],[[264447,393802],[70,-4563]],[[230673,392156],[-3,-4046]],[[230670,388110],[-1298,15]],[[229372,388125],[-307,-1],[-1,1618]],[[226293,389752],[2,-1622]],[[226295,388130],[-1550,-17]],[[224745,388113],[-16,3259]],[[233047,392152],[-21,-5676]],[[233026,386476],[-1232,4]],[[231794,386480],[13,5678]],[[158930,387955],[-62,-1644],[169,-2032],[-400,3],[-19,-2538],[-216,-435]],[[158402,381309],[-24,5]],[[158378,381314],[-291,-20]],[[158087,381294],[-3,44]],[[158084,381338],[140,477],[-107,2272],[-521,4539],[-126,2199]],[[281985,391353],[-669,-3258],[-157,217]],[[281159,388312],[-296,1764]],[[280863,390076],[149,2456]],[[263250,392869],[-350,-1063]],[[262900,391806],[-413,-1089]],[[262487,390717],[-250,1859]],[[158084,381338],[-237,-852],[-152,858]],[[157695,381344],[-539,908],[-435,1971],[-295,-310]],[[156426,383913],[-354,2862],[-569,2151],[-557,3778]],[[254915,388976],[-726,35]],[[254189,389011],[12,5213]],[[280017,389656],[-138,-1763],[572,-1648]],[[280451,386245],[-493,-2673]],[[279958,383572],[-240,-1326]],[[279718,382246],[-1324,5048]],[[279335,387009],[0,0]],[[254189,389011],[-119,3]],[[254070,389014],[-537,-21]],[[253533,388993],[-284,565]],[[269861,389247],[-95,-2476],[-512,-708]],[[269254,386063],[-253,186]],[[269001,386249],[-194,1565]],[[268807,387814],[-419,1210]],[[237049,388961],[-4,-2241]],[[237045,386720],[-1525,569]],[[235520,387289],[11,4850]],[[284458,394060],[-9,-959]],[[280863,390076],[-319,-2826]],[[280544,387250],[-93,-1005]],[[260715,389683],[-623,-25],[-151,-811]],[[259941,388847],[-106,263],[-8,2960]],[[265356,392696],[-244,-3246]],[[265112,389450],[-475,-738]],[[264637,388712],[-120,527]],[[251999,389685],[-2,-2446]],[[251997,387239],[-1241,-18]],[[250756,387221],[2,543]],[[250758,387764],[15,4342]],[[263762,388503],[-226,-927]],[[263536,387576],[-64,-376]],[[263472,387200],[-541,2720],[-31,1886]],[[283312,392781],[-46,161]],[[259941,388847],[-406,-1354],[-3,-1307]],[[259532,386186],[-107,-8]],[[259425,386178],[-618,89]],[[258807,386267],[-150,4]],[[258657,386271],[0,4937]],[[247867,390697],[-212,-2398]],[[285904,391677],[33,-3346],[377,-2749],[-110,-1238],[-607,2727],[-162,3701]],[[266078,390249],[8,-2072]],[[266086,388177],[-605,-1280],[-198,665]],[[265283,387562],[-171,1888]],[[262487,390717],[-392,-1208]],[[262095,389509],[-261,-210]],[[261834,389299],[-459,2806]],[[268807,387814],[-424,-2456]],[[268383,385358],[-426,367],[-546,2055]],[[267411,387780],[397,3341],[-19,785]],[[250758,387764],[-585,209],[-360,-847],[-369,60],[-248,-998]],[[249196,386188],[-7,4407]],[[277215,389401],[-117,-2971],[-334,-2300],[41,-776]],[[276805,383354],[-350,-1566],[-111,-2182]],[[276344,379606],[-848,-338],[-274,1261],[9,2076]],[[275231,382605],[22,2224],[297,803]],[[242945,389467],[-266,-2385],[-345,60],[127,-1459],[-203,-1038]],[[242258,384645],[-596,108],[14,1563],[-255,47]],[[231794,386480],[-23,-4900]],[[231771,381580],[-1110,51]],[[230661,381631],[9,6479]],[[234285,392148],[-26,-6490]],[[234259,385658],[-1233,6]],[[233026,385664],[0,812]],[[235520,387289],[0,-1654]],[[235520,385635],[-1261,23]],[[275231,382605],[-213,772]],[[275018,383377],[-482,1699],[-115,3127]],[[161934,387867],[6,-3922]],[[161940,383945],[-1040,-1302],[-210,590],[-282,-2812]],[[160408,380421],[-311,-431],[76,1800]],[[160173,381790],[162,2444]],[[261834,389299],[-406,-1730]],[[261428,387569],[-332,583]],[[261096,388152],[14,1161]],[[263472,387200],[-446,-2245]],[[263026,384955],[-363,79]],[[262663,385034],[-258,1507],[-110,2136],[-200,832]],[[267411,387780],[-197,-1448],[23,-1522],[-313,-464]],[[266924,384346],[-311,1158]],[[266613,385504],[-354,897],[-173,1776]],[[244558,391417],[4,-9114]],[[244562,382303],[-467,23],[2,-1087]],[[244097,381239],[-267,17]],[[243830,381256],[-40,2513]],[[243790,383769],[12,7731]],[[169169,376466],[1659,-8029]],[[170828,368437],[-2625,-35]],[[168203,368402],[-689,2284]],[[167514,370686],[-267,2691],[-421,166]],[[166826,373543],[187,2774],[-291,1077],[-121,2597],[-312,216],[-475,1906],[-19,2370]],[[246321,387086],[-122,-4888]],[[246199,382198],[-880,-1]],[[245319,382197],[-757,106]],[[243790,383769],[-1535,69]],[[242255,383838],[3,807]],[[164588,387892],[-720,-833],[-260,132],[-515,-1390],[-61,-1061],[-1009,-2142]],[[162023,382598],[-83,1347]],[[286908,384389],[-189,474],[-264,2363],[218,1227],[-54,1256],[301,244],[335,-1095],[296,2144]],[[288209,388837],[-340,-1484],[-14,-1069]],[[287855,386284],[-365,-3226],[-582,1331]],[[218595,391430],[0,-8114]],[[218595,383316],[-1157,7]],[[217438,383323],[-62,-1]],[[217376,383322],[-8,8119]],[[219823,391396],[8,-8115]],[[219831,383281],[-1236,35]],[[216145,391424],[-1,-8127]],[[216144,383297],[-1330,-14]],[[214814,383283],[1,118]],[[214815,383401],[-1,6449]],[[217376,383322],[-1162,-24]],[[216214,383298],[-70,-1]],[[282823,386026],[45,-780]],[[282868,385246],[-236,-135]],[[282632,385111],[-191,603],[-899,-1519]],[[281542,384195],[7,1474],[-390,2643]],[[221667,391366],[1,-6464]],[[221668,384902],[2,-1623]],[[221670,383279],[-1791,1]],[[219879,383280],[-48,1]],[[241058,386418],[-180,-1533],[-17,-2354]],[[240861,382531],[-192,-605],[-545,142],[-327,1182]],[[239797,383250],[30,5017]],[[224745,388113],[2,-4860]],[[224747,383253],[-1206,6]],[[223541,383259],[-334,2],[0,1622]],[[223207,384883],[-1,6486]],[[206744,388071],[3,-4865],[-303,-5]],[[206444,383201],[-2083,131]],[[204361,383332],[-596,3378]],[[223207,384883],[-1539,19]],[[285428,388496],[2,-844]],[[285430,387652],[-211,220],[-263,-2217]],[[284956,385655],[81,-2289],[-234,434],[-329,2868],[-459,-1568],[-187,2280],[549,3865]],[[258657,386271],[-1,-537],[-1038,13]],[[257618,385747],[-2,2450]],[[270898,384461],[-214,-1052],[-154,1960]],[[270530,385369],[-22,1865]],[[271507,390062],[-150,-656],[68,-2319],[-484,-1260],[-43,-1366]],[[181367,389064],[0,-7900]],[[181367,381164],[-7,-10123]],[[181360,371041],[5,-11252]],[[181365,359789],[1,-2928]],[[181366,356861],[-1984,13],[0,179],[-2726,-7],[-435,-204]],[[199240,384018],[-2,564],[-1384,-11],[128,-1422],[415,-674],[-79,-1241]],[[198318,381234],[-2999,13]],[[195319,381247],[-49,6459]],[[273779,387839],[177,-533],[-261,-1184],[-892,-2813]],[[272803,383309],[-146,3878],[250,1036]],[[249196,386188],[3,-3707]],[[249199,382481],[-545,27]],[[248654,382508],[-40,1621],[-342,19],[-642,3983]],[[281542,384195],[-437,-1435],[-99,693]],[[281006,383453],[-396,1782],[-66,2015]],[[272803,383309],[-1,-6]],[[272802,383303],[-272,-1508],[-143,-3970]],[[272387,377825],[-212,339]],[[272175,378164],[-165,2328],[-356,2011],[-528,-202]],[[271126,382301],[46,1977],[-274,183]],[[214815,383401],[-1944,-33]],[[212871,383368],[-1832,-28]],[[211039,383340],[-284,-8]],[[210755,383332],[-8,4680]],[[227834,389742],[0,-8111]],[[227834,381631],[-920,3]],[[226914,381634],[-613,-2]],[[226301,381632],[-6,6498]],[[229372,388125],[-59,-8133]],[[229313,379992],[-869,39]],[[228444,380031],[-1,1618],[-609,-18]],[[265283,387562],[-139,-988]],[[265144,386574],[-341,378]],[[264803,386952],[-166,1760]],[[253533,388993],[-9,-5827]],[[253524,383166],[-611,-14]],[[252913,383152],[-925,24]],[[251988,383176],[9,4063]],[[261096,388152],[-572,-2670]],[[260524,385482],[-286,-2089],[-141,377]],[[260097,383770],[-46,2157],[-519,259]],[[270530,385369],[-363,-644],[-243,-2058]],[[269924,382667],[-670,3396]],[[262663,385034],[-353,-388]],[[262310,384646],[-796,417]],[[261514,385063],[-86,2506]],[[278140,386071],[-341,-3643],[-207,-671]],[[277592,381757],[-787,1597]],[[283755,387795],[-68,-3119]],[[283687,384676],[-6,-1714]],[[283681,382962],[-121,26]],[[283560,382988],[-214,707]],[[283346,383695],[-232,459]],[[283114,384154],[-246,1092]],[[264803,386952],[-255,-1635]],[[264548,385317],[-418,-1608],[-274,-40]],[[263856,383669],[-44,1680],[-276,2227]],[[185634,387890],[509,-3310],[-59,-2002],[-242,-1389]],[[185842,381189],[-97,-48]],[[185745,381141],[-4378,23]],[[254070,389014],[36,-2006],[-137,-3776]],[[253969,383232],[-445,-66]],[[254659,386113],[-464,-2724],[-218,-213]],[[253977,383176],[-8,56]],[[238586,387939],[-30,-5630]],[[238556,382309],[-1467,73]],[[237089,382382],[-71,9],[27,4329]],[[289213,386812],[95,-1817],[-160,-1191],[-692,-216]],[[288456,383588],[-506,-230]],[[287950,383358],[-283,-697],[-32,1164],[220,2459]],[[275018,383377],[-768,-3504],[-473,452]],[[273777,380325],[-279,2236],[-256,-553],[-440,1295]],[[256529,387977],[-2,-5249]],[[256527,382728],[-679,251]],[[255848,382979],[3,2497],[-256,-92],[-154,2933]],[[158378,381314],[146,-1463],[-437,1443]],[[160173,381790],[-408,-2224],[-354,-188],[-351,1518],[-215,-1697],[-283,501],[-160,1609]],[[239797,383250],[35,-3689]],[[239832,379561],[-1222,173]],[[238610,379734],[-54,2575]],[[255848,382979],[-1,-825],[-418,-676]],[[255429,381478],[-616,57]],[[254813,381535],[-309,1119],[-517,33]],[[253987,382687],[-10,489]],[[257618,385747],[6,-2451]],[[257624,383296],[-312,-1079]],[[257312,382217],[-630,-29]],[[256682,382188],[-155,540]],[[266613,385504],[-146,-2035],[-362,-1580]],[[266105,381889],[-246,1825],[-347,209]],[[265512,383923],[-368,2651]],[[261514,385063],[-513,-1353]],[[261001,383710],[-477,1772]],[[230661,381631],[-3,-1635],[-459,10]],[[230199,380006],[-886,-14]],[[209217,388123],[-12,-6998]],[[209205,381125],[-8,-7666]],[[209197,373459],[-808,1541]],[[208389,375000],[-823,1546],[-1023,-358],[-101,636]],[[206442,376824],[2,6377]],[[210755,383332],[-21,-1722],[-306,-20],[0,-1082],[-596,-8],[-627,625]],[[226301,381632],[-1535,-18]],[[224766,381614],[-19,1639]],[[248654,382508],[-383,35],[1,-1620],[-477,-883]],[[247795,380040],[-127,737]],[[247668,380777],[-303,1892],[54,2934]],[[250756,387221],[-8,-4860]],[[250748,382361],[-1241,119]],[[249507,382480],[-308,1]],[[286032,384373],[445,-3369],[-10,-1655],[-344,2010],[-710,2346],[-112,-1068],[-345,3018]],[[285430,387652],[602,-3279]],[[164735,386546],[-435,-1185],[-950,-7856],[-381,-2238]],[[162969,375267],[-759,4580]],[[162210,379847],[-187,2751]],[[187728,387743],[-228,-1435],[16,-5094]],[[187516,381214],[-1674,-25]],[[192580,387706],[28,-2493],[350,-3273],[-106,-714]],[[192852,381226],[-4142,-34],[-1194,22]],[[247668,380777],[-455,-1591]],[[247213,379186],[-466,-723],[-158,1359]],[[246589,379822],[-390,2376]],[[268978,383008],[-530,-70]],[[268448,382938],[-65,2420]],[[269001,386249],[-23,-3241]],[[268448,382938],[-363,-1281]],[[268085,381657],[-378,306],[-289,1389],[-223,-1372]],[[267195,381980],[-271,2366]],[[195319,381247],[2,-5061]],[[195321,376186],[-5,-7382]],[[195316,368804],[-5,-9040]],[[195311,359764],[-2662,-21]],[[192649,359743],[-2090,98]],[[190559,359841],[-1844,-32]],[[188715,359809],[653,1883],[604,385],[346,3276],[287,901],[-33,1955],[278,1645]],[[190850,369854],[446,2473],[227,3836],[527,219],[571,4056],[231,788]],[[263856,383669],[113,-1403]],[[263969,382266],[-621,-1697]],[[263348,380569],[-281,1464]],[[263067,382033],[-41,2922]],[[281006,383453],[-231,-1615]],[[280775,381838],[-817,1734]],[[279718,382246],[-89,-2345],[-162,-618]],[[279467,379283],[-168,-1840],[-296,-1250],[-422,176]],[[278581,376369],[-906,3632]],[[277675,380001],[127,1436],[-210,320]],[[278910,381985],[0,0]],[[279286,380244],[0,0]],[[251988,383176],[-7,-2447]],[[251981,380729],[-1184,-9]],[[250797,380720],[-49,1641]],[[237089,382382],[-23,-3302]],[[237066,379080],[-1549,436]],[[235517,379516],[3,6119]],[[172686,359243],[-1858,9194]],[[265512,383923],[-362,-1945]],[[265150,381978],[-326,1244],[-276,2095]],[[204361,383332],[336,-2213],[254,-3271],[312,-1387]],[[205263,376461],[44,-2691]],[[205307,373770],[-1620,-57]],[[203687,373713],[-1526,-10],[-2,1613],[-297,15]],[[201862,375331],[0,678],[-568,1791],[-287,-220]],[[201007,377580],[-1,3556]],[[289490,378912],[-53,-34]],[[289437,378878],[53,34]],[[290026,386799],[-57,0]],[[289865,385833],[-126,-2836],[-305,-809],[-323,-3508]],[[289111,378680],[-677,-383]],[[288434,378297],[-6,31]],[[288428,378328],[-108,835],[337,901],[-201,3524]],[[289925,386799],[-60,-966]],[[166826,373543],[-111,722]],[[166715,374265],[-627,2326],[-334,-1722],[-421,-936],[-390,89],[-379,1183],[-853,-3609]],[[163711,371596],[-742,3671]],[[233026,385664],[-30,-6554]],[[232996,379110],[-1226,36]],[[231770,379146],[1,2434]],[[242255,383838],[29,-5136]],[[242284,378702],[-616,90]],[[241668,378792],[-305,22],[-346,3550],[-156,167]],[[258807,386267],[-62,-3639],[-147,-850]],[[258598,381778],[-366,-1124]],[[258232,380654],[2,1594],[-308,13],[2,1095],[-304,-60]],[[259425,386178],[1,-1624],[368,-2836]],[[259794,381718],[-134,-3193]],[[259660,378525],[-140,-132]],[[259520,378393],[-502,215],[-245,853],[-175,2317]],[[269924,382667],[-59,-1572],[261,-1815],[-318,-1929]],[[269808,377351],[-304,-1660]],[[269504,375691],[-230,1692]],[[269274,377383],[-144,2308],[-237,1273],[85,2044]],[[260097,383770],[-303,-2052]],[[284512,383385],[-92,-1945]],[[284420,381440],[-8,-27]],[[284412,381413],[-144,-224]],[[284268,381189],[-437,1843],[-150,-70]],[[283687,384676],[250,-96],[535,1273],[40,-2468]],[[267195,381980],[-57,-1465]],[[267138,380515],[-472,-1482]],[[266666,379033],[-181,318]],[[266485,379351],[-380,2538]],[[282632,385111],[-701,-4527]],[[281931,380584],[-408,649],[-297,-356]],[[281226,380877],[-451,961]],[[234259,385658],[-34,-6558]],[[234225,379100],[-1229,10]],[[235517,379516],[0,-428]],[[235517,379088],[-1292,12]],[[261001,383710],[173,-405],[-53,-2162]],[[261121,381143],[-14,-545]],[[261107,380598],[-354,-821],[-435,244],[-629,-1650]],[[259689,378371],[-29,154]],[[283114,384154],[232,-459]],[[283560,382988],[-758,-4755]],[[282802,378233],[-126,314]],[[282676,378547],[-383,1648],[-362,389]],[[271126,382301],[-263,-1062],[-13,-2367]],[[270850,378872],[-720,-942]],[[270130,377930],[-322,-579]],[[263067,382033],[-389,-1466]],[[262678,380567],[-442,233]],[[262236,380800],[74,3846]],[[265150,381978],[-286,-1441]],[[264864,380537],[-573,-891]],[[264291,379646],[-263,849],[-59,1771]],[[262236,380800],[-219,-1713]],[[262017,379087],[-896,2056]],[[223541,383259],[-1,-4853],[-304,-4]],[[223236,378402],[-914,10],[-1,1609],[-610,4]],[[221711,380025],[-41,3254]],[[199425,381138],[158,-1618],[-159,-1773]],[[199424,377747],[-472,-1109]],[[198952,376638],[-30,887],[-420,1384],[23,1648],[-207,677]],[[157695,381344],[163,-971],[-42,-3300],[179,-855],[-251,-1183],[-370,1730],[-114,-335],[-478,2413],[-374,-283],[164,2872],[317,-1457],[-463,3938]],[[266485,379351],[-223,-717],[-503,-259]],[[265759,378375],[-341,2566],[-268,1037]],[[162210,379847],[3,-6325],[-189,423],[-596,-1236],[-739,-3959]],[[160689,368750],[-239,1114],[0,5132]],[[160450,374996],[-70,4168]],[[160380,379164],[28,1257]],[[243830,381256],[-6,-1909],[-732,4],[-351,-755]],[[242741,378596],[-457,106]],[[287272,377541],[-21,-6]],[[287251,377535],[21,6]],[[287356,378030],[36,-498]],[[287392,377532],[-31,2]],[[287361,377534],[-41,379]],[[287320,377913],[36,117]],[[287320,377913],[-5,-375]],[[287315,377538],[-19,-1]],[[287296,377537],[24,376]],[[287781,379692],[-1,1870],[-264,-794],[434,2590]],[[288428,378328],[-760,-1368],[113,2732]],[[281226,380877],[-275,-2360]],[[280951,378517],[-515,-3904]],[[280436,374613],[-425,-1183]],[[280011,373430],[-544,5853]],[[280484,379686],[0,0]],[[285766,378696],[-155,-919]],[[285611,377777],[-363,656],[-314,2280],[-163,-800]],[[284771,379913],[-351,1527]],[[284512,383385],[528,-1942],[635,-277],[253,-1943],[-162,-527]],[[241668,378792],[-8,-3016]],[[241660,375776],[-363,-1063],[-225,1684],[-655,60]],[[240417,376457],[-605,128]],[[239812,376585],[20,2976]],[[269274,377383],[-231,160],[-89,-1393]],[[268954,376150],[-910,2229],[-138,-652]],[[267906,377727],[-93,803]],[[267813,378530],[371,1947],[-99,1180]],[[277675,380001],[-469,-3994]],[[277206,376007],[-657,396],[-135,1144],[-337,-43]],[[276077,377504],[267,2102]],[[214814,383283],[8,-9753]],[[214822,373530],[1,-1754]],[[214823,371776],[-1967,-12]],[[212856,371764],[15,11604]],[[276077,377504],[-296,-1431],[-373,-3413]],[[275408,372660],[-430,-145],[-592,890]],[[274386,373405],[-399,2555]],[[273987,375960],[102,2168],[-312,2197]],[[267813,378530],[-479,65],[-196,1920]],[[206442,376824],[-331,1927],[-322,-2224],[-84,711],[-442,-777]],[[212856,371764],[-914,-4]],[[211942,371760],[-915,4]],[[211027,371764],[12,11576]],[[211027,371764],[-1822,7],[-8,1688]],[[258232,380654],[-83,-1403]],[[258149,379251],[-55,-2224],[-404,-1554]],[[257690,375473],[32,1004],[-294,274],[-178,1615]],[[257250,378366],[113,-4],[-51,3855]],[[273987,375960],[-386,-927]],[[273601,375033],[-357,1056],[-619,250],[-238,1486]],[[217438,383323],[-2,-8108],[40,-1726]],[[217476,373489],[-1219,0]],[[216257,373489],[-44,1699],[1,8110]],[[219879,383280],[1,-4863]],[[219880,378417],[-1219,28],[33,-4957]],[[218694,373488],[-1218,1]],[[216257,373489],[-1435,41]],[[253987,382687],[206,-1256],[-505,-5063]],[[253688,376368],[-787,325]],[[252901,376693],[12,6459]],[[224766,381614],[1,-6484]],[[224767,375130],[-1507,16]],[[223260,375146],[-24,3256]],[[221711,380025],[-1,-3246]],[[221710,376779],[-1831,18]],[[219879,376797],[1,1620]],[[252901,376693],[0,-14]],[[252901,376679],[-925,-16]],[[251976,376663],[5,4066]],[[284268,381189],[-150,-1310],[278,-2137]],[[284396,377742],[-309,-256],[-1,-1105]],[[284086,376381],[-456,-1904]],[[283630,374477],[-165,-177],[-222,1929],[-441,2004]],[[256682,382188],[-153,-1592],[-544,-3576],[-2,-876]],[[255983,376144],[-94,369]],[[255889,376513],[-415,787]],[[255474,377300],[-45,4178]],[[254813,381535],[-35,-5040]],[[254778,376495],[-630,72]],[[254148,376567],[55,-1760],[-337,-147]],[[253866,374660],[-178,1708]],[[272175,378164],[-318,-1099],[158,-2552]],[[272015,374513],[-260,-47]],[[271755,374466],[-295,397],[-169,2098],[-573,1279],[132,632]],[[249507,382480],[-6,-4914]],[[249501,377566],[-221,-2833]],[[249280,374733],[-473,1904],[-262,-566]],[[248545,376071],[-165,1642],[-585,2327]],[[238610,379734],[-22,-2902],[-171,-1635]],[[238417,375197],[-154,26]],[[238263,375223],[-509,95],[7,1075],[-716,162]],[[237045,376555],[21,2525]],[[250797,380720],[-133,-3247]],[[250664,377473],[-1163,93]],[[245319,382197],[-11,-8639]],[[245308,373558],[-149,-795]],[[245159,372763],[-438,51],[5,1622],[-613,34]],[[244113,374470],[-16,6769]],[[264291,379646],[-143,-3261]],[[264148,376385],[-273,-833]],[[263875,375552],[-122,1730],[-504,1186]],[[263249,378468],[99,2101]],[[246589,379822],[-14,-6361]],[[246575,373461],[-1267,97]],[[257250,378366],[-461,-1267]],[[256789,377099],[-280,-2533],[-526,1578]],[[259520,378393],[-23,-3366],[-403,-372]],[[259094,374655],[-945,4596]],[[263249,378468],[-138,-2639]],[[263111,375829],[-141,-279],[-100,2291]],[[262870,377841],[-192,2726]],[[265759,378375],[-26,-1496]],[[265733,376879],[-550,230]],[[265183,377109],[-319,3428]],[[228444,380031],[1,-3258]],[[228445,376773],[-1531,-4]],[[226914,376769],[0,4865]],[[231770,379146],[-5,-5694]],[[231765,373452],[0,-2431]],[[231765,371021],[-1573,61]],[[230192,371082],[7,8924]],[[226914,376769],[9,-3303],[-304,-23]],[[226619,373443],[-1830,-22]],[[224789,373421],[-22,1709]],[[255474,377300],[-169,-642],[-244,1256],[-44,-2642],[-239,1223]],[[244113,374470],[-771,42],[-10,-3582]],[[243332,370930],[-613,69]],[[242719,370999],[22,7597]],[[190850,369854],[-4778,-85],[-897,125]],[[185175,369894],[-17,4889],[309,-24],[-28,1599],[335,1],[-29,4782]],[[285474,374122],[-189,-777]],[[285285,373345],[-143,1233],[-392,-213],[-211,1145],[-143,2232]],[[284412,381413],[24,-1012],[357,-714],[24,-1757],[347,-1480],[310,-2328]],[[198952,376638],[-341,-2419]],[[198611,374219],[-359,1512],[-614,-665],[-130,1368],[-1561,19],[-626,-267]],[[282676,378547],[-305,-5181]],[[282371,373366],[-288,724],[-132,1442],[-320,1092]],[[281631,376624],[-680,1893]],[[185175,369894],[-607,4],[0,-813]],[[184568,369085],[-378,-469],[-604,143],[1,868],[-616,2],[-2,1663],[-315,-252],[-1294,1]],[[262017,379087],[-182,-1209]],[[261835,377878],[-646,-598],[-256,977]],[[260933,378257],[174,2341]],[[201007,377580],[-383,-320],[-19,-4903],[45,-4702]],[[200650,367655],[-984,-5]],[[199666,367650],[-1,4037]],[[199665,371687],[0,2392],[-241,3668]],[[262870,377841],[-381,-296],[-271,-1166]],[[262218,376379],[-341,113]],[[261877,376492],[-42,1386]],[[251976,376663],[-1,-809]],[[251975,375854],[-1238,-25]],[[250737,375829],[-73,1644]],[[285611,377777],[352,-2380]],[[285963,375397],[-300,-591]],[[285663,374806],[-240,292],[-546,3121],[-106,1694]],[[248545,376071],[-481,-3792]],[[248064,372279],[-249,-32],[-722,3897],[380,1914],[-260,1128]],[[260933,378257],[-419,-2943],[-266,-430]],[[260248,374884],[-559,3487]],[[267906,377727],[-44,-1950]],[[267862,375777],[-303,91],[-437,-1812],[23,-1008]],[[267145,373048],[-650,2775]],[[266495,375823],[171,3210]],[[265183,377109],[-100,-1733]],[[265083,375376],[-218,283]],[[264865,375659],[-717,726]],[[160450,374996],[-1124,-1844],[-235,1488],[-259,115],[-483,1735]],[[158349,376490],[-209,1238],[349,1656],[315,-572],[373,668],[783,-708],[357,1506],[63,-1114]],[[230192,371082],[-1,-2432]],[[230191,368650],[-1748,-16]],[[228443,368634],[2,8139]],[[223260,375146],[0,-1719]],[[223260,373427],[-1521,35]],[[221739,373462],[-29,3317]],[[278540,374733],[-257,-157],[-226,-2495],[-255,-813]],[[277802,371268],[-172,-1566]],[[277630,369702],[-515,2397],[26,1888]],[[277141,373987],[65,2020]],[[278581,376369],[-41,-1636]],[[277790,374553],[0,0]],[[278042,373830],[0,0]],[[248064,372279],[-101,-553]],[[247963,371726],[-1081,38]],[[246882,371764],[-315,-16],[8,1713]],[[163711,371596],[-1656,-4356],[43,-1065],[-726,-3845]],[[161372,362330],[-155,871],[-328,-611],[-162,2473],[145,499],[-185,3196]],[[160687,368758],[2,-8]],[[239812,376585],[-307,30],[-14,-1889]],[[239491,374726],[-1074,471]],[[237045,376555],[-22,-4864]],[[237023,371691],[-1516,258]],[[235507,371949],[0,363]],[[235507,372312],[10,6776]],[[266495,375823],[-378,-675]],[[266117,375148],[-384,1731]],[[280011,373430],[-196,-1764],[-242,63],[-67,-1661]],[[279506,370068],[-122,-195]],[[279384,369873],[-552,4225],[-292,635]],[[259094,374655],[-345,-3823]],[[258749,370832],[-373,-503],[-265,1039],[-246,-1076]],[[257865,370292],[-128,1809]],[[257737,372101],[-47,3372]],[[232996,379110],[-18,-5687]],[[232978,373423],[-1213,29]],[[234225,379100],[-28,-5680]],[[234197,373420],[-1219,3]],[[235507,372312],[-1310,27]],[[234197,372339],[0,1081]],[[287392,377532],[-31,2]],[[287315,377538],[-19,-1]],[[287272,377541],[-21,-6]],[[288876,376511],[119,-271],[-517,-3407],[-55,-2435],[-470,-1281]],[[287953,369117],[-126,1004]],[[287827,370121],[-283,134],[408,3843]],[[287952,374098],[291,1243],[191,2956]],[[289111,378680],[-235,-2169]],[[289490,378912],[-53,-34]],[[271755,374466],[-438,-677],[176,-948],[-282,-761]],[[271211,372080],[-347,406],[-184,-777],[-249,1135],[-83,2839],[-218,2247]],[[286401,372837],[-192,2274],[-246,286]],[[285766,378696],[289,-8],[647,-2450],[-301,-3401]],[[242719,370999],[-614,37]],[[242105,371036],[2,817],[-459,1220],[12,2703]],[[208389,375000],[-289,-1427],[-261,-3093],[-562,-3167],[-508,-171],[-617,-1904]],[[206152,365238],[46,2010],[-135,4024],[-310,765],[-525,-1497]],[[205228,370540],[79,3230]],[[283630,374477],[147,-1396],[514,-1037],[-44,-592]],[[284247,371452],[-292,-1688]],[[283955,369764],[-489,1286],[-105,1395],[-525,504]],[[282836,372949],[-465,417]],[[281362,373717],[-225,-1081]],[[281137,372636],[-621,1278],[-80,699]],[[281631,376624],[-269,-2907]],[[260248,374884],[159,-1472]],[[260407,373412],[-544,-3501],[-48,-1933]],[[259815,367978],[-430,176]],[[259385,368154],[-185,2177],[-451,501]],[[263875,375552],[-250,-1450]],[[263625,374102],[-126,-754],[-407,1614]],[[263092,374962],[19,867]],[[268954,376150],[-162,-516],[84,-2448]],[[268876,373186],[-340,667],[-495,-734]],[[268041,373119],[-179,2658]],[[219879,376797],[30,-8169]],[[219909,368628],[-1216,-7]],[[218693,368621],[1,4867]],[[257737,372101],[-515,1415]],[[257222,373516],[-85,1915],[-348,1668]],[[273601,375033],[187,-2460],[-193,530],[-410,-2364]],[[273185,370739],[-292,-249],[-64,-1204]],[[272829,369286],[-281,1662],[-394,3474],[-139,91]],[[261877,376492],[-288,-708],[-405,-2399]],[[261184,373385],[-333,-3287]],[[260851,370098],[-285,-39],[141,1604],[-300,1749]],[[271211,372080],[-148,-2078]],[[271063,370002],[-199,-676]],[[270864,369326],[-114,472]],[[270750,369798],[-437,240],[-582,3546]],[[269731,373584],[-227,2107]],[[255889,376513],[81,-2236],[-378,-1770]],[[255592,372507],[-241,-671]],[[255351,371836],[-666,-173]],[[254685,371663],[-57,1803],[-480,3101]],[[285285,373345],[270,-2335]],[[285555,371010],[-138,-2862]],[[285417,368148],[-256,2601]],[[285161,370749],[-819,3092],[-256,2540]],[[199665,371687],[-1364,1]],[[198301,371688],[8,1041],[302,1490]],[[263092,374962],[-79,-1906]],[[263013,373056],[-780,-638]],[[262233,372418],[68,3271],[-83,690]],[[201862,375331],[-47,-8024]],[[201815,367307],[-1165,-220],[0,568]],[[269731,373584],[-340,-1357],[-300,302]],[[269091,372529],[-215,657]],[[277141,373987],[-397,706],[-568,-2858]],[[276176,371835],[-345,-947],[-213,580]],[[275618,371468],[-210,1192]],[[276253,374852],[0,0]],[[250737,375829],[-7,-4869]],[[250730,370960],[-827,-8],[-198,-519]],[[249705,370433],[27,2110],[-434,1361],[-18,829]],[[266117,375148],[-215,-2029]],[[265902,373119],[-39,-3]],[[265863,373116],[-510,789],[-270,1471]],[[257222,373516],[17,-1185],[-615,-2106]],[[256624,370225],[-1032,2282]],[[264865,375659],[-28,-5332]],[[264837,370327],[-303,-812]],[[264534,369515],[-414,300]],[[264120,369815],[-495,4287]],[[253866,374660],[-364,-2527],[71,-1661]],[[253573,370472],[-674,471]],[[252899,370943],[2,5736]],[[262233,372418],[-14,-886]],[[262219,371532],[-865,379],[-170,1474]],[[221739,373462],[9,-4974]],[[221748,368488],[-1535,133]],[[220213,368621],[-304,7]],[[228443,368634],[-1823,-25]],[[226620,368609],[-1,4834]],[[252899,370943],[-103,7]],[[252796,370950],[-826,-12]],[[251970,370938],[5,4916]],[[285161,370749],[-468,-116]],[[284693,370633],[-446,819]],[[240417,376457],[5,-7664]],[[240422,368793],[-607,46],[-5,-1332]],[[239810,367507],[-304,42]],[[239506,367549],[-15,7177]],[[160687,368758],[-1094,45],[-338,-405]],[[159255,368398],[-342,754],[-149,3107],[-463,2405],[48,1826]],[[282836,372949],[-67,-2693]],[[282769,370256],[-815,2165],[-30,-1006],[-320,604]],[[281604,372019],[-242,1698]],[[249705,370433],[0,-111]],[[249705,370322],[-946,616]],[[248759,370938],[-796,-35]],[[247963,370903],[0,823]],[[238263,375223],[35,-4780]],[[238298,370443],[-1280,165]],[[237018,370608],[5,1083]],[[166715,374265],[-765,-4052],[-188,-2676],[-313,4],[-790,-4320]],[[164659,363221],[-270,1013],[-380,3367],[-298,3995]],[[254685,371663],[-553,-2946]],[[254132,368717],[-234,1247],[-124,-777]],[[253774,369187],[-201,1285]],[[242105,371036],[-8,-2450]],[[242097,368586],[-1210,160]],[[240887,368746],[-465,47]],[[198301,371688],[-2633,-143],[-352,-2741]],[[268041,373119],[135,-3677],[-93,-602]],[[268083,368840],[-64,308]],[[268019,369148],[-398,2375],[-439,725]],[[267182,372248],[-37,800]],[[273842,367755],[-4,20]],[[273838,367775],[-653,2964]],[[274386,373405],[-544,-5650]],[[251970,370938],[-927,-50]],[[251043,370888],[-313,72]],[[267182,372248],[-389,563],[-373,-1145]],[[266420,371666],[-518,1453]],[[265863,373116],[-284,-991],[-227,-2237]],[[265352,369888],[-515,439]],[[286401,371346],[-510,730],[-228,2730]],[[286401,372837],[0,-1491]],[[203687,373713],[-2,-6471]],[[203685,367242],[-1780,53]],[[201905,367295],[-90,12]],[[158120,372966],[-303,-2]],[[157817,372964],[-33,1343],[287,552],[49,-1893]],[[239506,367549],[-1222,189]],[[238284,367738],[14,2705]],[[264120,369815],[-276,-991]],[[263844,368824],[-589,2769]],[[263255,371593],[-242,1463]],[[224789,373421],[0,-4871]],[[224789,368550],[-1527,-11]],[[223262,368539],[-2,4888]],[[279384,369873],[-416,-2088]],[[278968,367785],[-186,-655]],[[278782,367130],[-282,1313]],[[278500,368443],[-442,1075],[-256,1750]],[[211942,371760],[-29,-11974]],[[211913,359786],[-2568,-75]],[[209345,359711],[-3196,-18]],[[206149,359693],[3,5545]],[[277630,369702],[-234,-1502],[-213,614],[-430,-1508],[-95,-1769]],[[276658,365537],[-630,2109]],[[276028,367646],[291,2274],[-143,1915]],[[245159,372763],[-5,-2033]],[[245154,370730],[-438,88],[-5,-1633],[288,-71],[-3,-1602]],[[244996,367512],[-1211,139]],[[243785,367651],[-302,26],[0,3251],[-151,2]],[[272829,369286],[-248,-1600]],[[272581,367686],[-815,1322],[-628,339],[-75,655]],[[281137,372636],[-25,-1023],[-602,-5508]],[[280510,366105],[-361,1085]],[[280149,367190],[-643,2878]],[[167514,370686],[-806,-4323],[-52,-2727],[-160,-1066],[-478,-109],[49,-889],[-436,-1628],[-318,-2892],[-1553,-1283],[-242,1461],[-239,3370]],[[163279,360600],[183,976],[522,1046],[675,599]],[[285850,370354],[-295,656]],[[285474,374122],[422,-2949],[651,-1010],[-346,-836],[-351,1027]],[[269091,372529],[-167,-3366],[109,-686],[-352,-3384]],[[268681,365093],[-160,2648],[-438,1099]],[[205228,370540],[-718,-4116]],[[204510,366424],[-823,2],[-2,816]],[[281604,372019],[-166,-3773]],[[281438,368246],[-287,-1612]],[[281151,366634],[-415,-1159],[-226,630]],[[270750,369798],[-954,-4451]],[[269796,365347],[-678,-1793]],[[269118,363554],[-32,-122]],[[269086,363432],[-466,1404]],[[268620,364836],[61,257]],[[246882,371764],[-41,-6070]],[[246841,365694],[-3,-838],[-515,-29]],[[246323,364827],[-104,1910],[11,4270],[-1076,-277]],[[216257,373489],[3,-6477]],[[216260,367012],[-85,2]],[[216175,367014],[-1352,13]],[[214823,367027],[0,4749]],[[257865,370292],[-200,-146],[146,-3006]],[[257811,367140],[-547,-1924],[-253,-1483]],[[257011,363733],[-559,3793]],[[256452,367526],[172,2699]],[[217476,373489],[0,-6490]],[[217476,366999],[-1216,13]],[[218693,368621],[0,-1623]],[[218693,366998],[-1157,2]],[[217536,367000],[-60,-1]],[[223262,368539],[4,-1604]],[[223266,366935],[-1482,-59]],[[221784,366876],[-36,1612]],[[226620,368609],[1,-1636]],[[226621,366973],[-1512,-42]],[[225109,366931],[-321,1],[1,1618]],[[232978,373423],[0,-6493]],[[232978,366930],[-1215,50]],[[231763,366980],[2,4041]],[[234197,372339],[-1,-5412]],[[234196,366927],[-1206,3]],[[232990,366930],[-12,0]],[[260851,370098],[153,-1651]],[[261004,368447],[-327,81]],[[260677,368528],[-206,-891]],[[260471,367637],[-656,341]],[[262219,371532],[-27,-1601]],[[262192,369931],[-95,-2448]],[[262097,367483],[-273,950],[-278,-893],[-215,1026],[-327,-119]],[[275618,371468],[-300,-1181],[80,-1043],[-490,-1589]],[[274908,367655],[-100,1093],[-720,-2035],[-246,1042]],[[266420,371666],[-159,-2460]],[[266261,369206],[-144,586],[-553,-948],[-155,569]],[[265409,369413],[-57,475]],[[263255,371593],[-527,-1618]],[[262728,369975],[-536,-44]],[[283955,369764],[143,-836]],[[284098,368928],[-199,-2026]],[[283899,366902],[-182,63]],[[283717,366965],[-298,1139]],[[283419,368104],[99,1639],[-146,1184],[-444,-782]],[[282928,370145],[-159,111]],[[158120,372966],[96,-2154],[271,-373],[409,-1982]],[[158896,368457],[-209,-644],[106,-2701]],[[158793,365112],[-4,-1305],[-464,-534],[77,-1486]],[[158402,361787],[-314,1660],[11,3041],[-319,2975],[37,3501]],[[268019,369148],[-408,283],[-84,-2105]],[[267527,367326],[-252,703],[-463,-1951],[-186,747],[-277,-815]],[[266349,366010],[-88,3196]],[[282769,370256],[-560,-2686]],[[282209,367570],[-433,1403],[-338,-727]],[[256452,367526],[-546,-453]],[[255906,367073],[-158,651],[-62,2672]],[[255686,370396],[-335,1440]],[[235507,371949],[0,-5388]],[[235507,366561],[0,-480]],[[235507,366081],[-1274,28]],[[234233,366109],[-37,818]],[[276028,367646],[-525,-1522]],[[275503,366124],[-183,485],[-294,-965]],[[275026,365644],[-118,2011]],[[206149,359693],[-183,-2]],[[205966,359691],[-1386,13]],[[204580,359704],[-148,938],[143,4467],[-65,1315]],[[237018,370608],[-19,-4324]],[[236999,366284],[-1492,277]],[[255686,370396],[-1221,-4086]],[[254465,366310],[33,525]],[[254498,366835],[-359,564],[-7,1318]],[[247963,370903],[3,-5312],[-200,54]],[[247766,365645],[-925,49]],[[214823,367027],[-1,-7366]],[[214822,359661],[-2675,122]],[[212147,359783],[-234,3]],[[198301,371688],[-141,-896],[-30,-2362],[-478,-2240],[-241,-2267],[2,-1425],[-247,-2726]],[[197166,359772],[-1855,-8]],[[199666,367650],[1,-7869]],[[199667,359781],[-2501,-9]],[[163279,360600],[-1048,-5633]],[[162231,354967],[-622,1773],[-205,2320]],[[161404,359060],[-32,3270]],[[263844,368824],[-156,-2928]],[[263688,365896],[-150,509],[-457,-2190]],[[263081,364215],[-5,2272],[-355,1235],[7,2253]],[[284693,370633],[402,-619],[175,-1717]],[[285270,368297],[-384,-511],[-28,-974]],[[284858,366812],[-760,2116]],[[259385,368154],[-66,-2032],[-241,-91]],[[259078,366031],[-868,-274]],[[258210,365757],[-399,1383]],[[278500,368443],[-229,-637],[32,-1405]],[[278303,366401],[-518,-5580]],[[277785,360821],[-108,-926],[-313,672]],[[277364,360567],[-363,2794],[-342,618]],[[276659,363979],[-1,1558]],[[277509,366213],[0,0]],[[184568,369085],[5,-9297]],[[184573,359788],[-3208,1]],[[231763,366980],[-8,-1628]],[[231755,365352],[-1561,65]],[[230194,365417],[-3,3233]],[[246323,364827],[-123,-4109]],[[246200,360718],[-515,61],[-148,783]],[[245537,361562],[-46,1372],[-349,1595],[-44,2975],[-102,8]],[[249705,370322],[275,-3107],[-171,-1185]],[[249809,366030],[-12,-1568]],[[249797,364462],[-367,-344],[-403,-1990]],[[249027,362128],[-283,3]],[[248744,362131],[15,8807]],[[243785,367651],[-25,-6959]],[[243760,360692],[-1207,136]],[[242553,360828],[-453,64]],[[242100,360892],[-3,7694]],[[253774,369187],[-63,-597],[-543,-371],[-221,-906]],[[252947,367313],[-159,351]],[[252788,367664],[8,3286]],[[252788,367664],[-169,-666],[-98,-2357],[240,-2056],[-183,-1538]],[[252578,361047],[0,1701],[-613,3309]],[[251965,366057],[5,4881]],[[283419,368104],[-491,2041]],[[251965,366057],[-546,-28],[-63,-614]],[[251356,365415],[-322,507]],[[251034,365922],[9,4966]],[[251034,365922],[-568,98]],[[250466,366020],[-657,10]],[[285850,370354],[308,-1052]],[[286158,369302],[-18,-1124]],[[286140,368178],[152,-3489],[-321,-310],[-554,3769]],[[248744,362131],[-259,19],[1,-1356],[-424,-246]],[[248062,360548],[-312,844],[16,4253]],[[273838,367775],[29,-1536],[-368,-994]],[[273499,365245],[-679,-1091]],[[272820,364154],[-382,1914]],[[272438,366068],[143,1618]],[[168203,368402],[-33,-2228],[201,-311],[167,-3441],[606,-1527],[213,-3239],[-23,-2583]],[[169334,355073],[-1716,-81],[-1,-1561],[-890,26],[-3,-1596],[-459,20],[-299,-1621]],[[165966,350260],[-246,-1295],[-814,-376],[-3,-4045],[-991,-5108]],[[163912,339436],[-929,3751],[57,1792],[-141,1162]],[[162899,346141],[229,1144],[-1,2966],[-896,4716]],[[238284,367738],[-14,-2706]],[[238270,365032],[-1188,149]],[[237082,365181],[-83,1103]],[[265409,369413],[279,-3141]],[[265688,366272],[-442,-1825]],[[265246,364447],[-535,1286]],[[264711,365733],[-177,3782]],[[255906,367073],[-251,-1811],[146,-2557]],[[255801,362705],[-529,-954],[-438,810]],[[254834,362561],[-346,1710],[-23,2039]],[[283717,366965],[-7,-1437]],[[283710,365528],[-51,70]],[[283659,365598],[-117,-1256]],[[283542,364342],[-75,-142]],[[283467,364200],[-36,-78]],[[283431,364122],[-87,-195]],[[283344,363927],[-565,790]],[[282779,364717],[-265,34],[-366,1828],[61,991]],[[280149,367190],[-243,-2778]],[[279906,364412],[-397,-808]],[[279509,363604],[-259,695],[-282,3486]],[[287953,369117],[-346,-3597],[-150,-3563],[-151,2650],[266,5441],[255,73]],[[263081,364215],[-502,-2274]],[[262579,361941],[-398,1305]],[[262181,363246],[-37,1321],[-302,1000]],[[261842,365567],[255,1916]],[[272438,366068],[-555,-2440],[-498,579]],[[271385,364207],[-316,930],[-383,3657],[178,532]],[[254498,366835],[-802,-2923],[-19,-787]],[[253677,363125],[-271,-607]],[[253406,362518],[-71,2345],[-388,2450]],[[188715,359809],[-3142,-15]],[[185573,359794],[-1000,-6]],[[266349,366010],[57,-1413]],[[266406,364597],[-349,1839],[-369,-164]],[[264711,365733],[-224,-683],[-203,-2440]],[[264284,362610],[-596,3286]],[[271385,364207],[-448,-1772]],[[270937,362435],[-326,-1378],[-364,-500]],[[270247,360557],[-283,4288],[-168,502]],[[286158,369302],[207,216],[310,-1576],[-73,-2371],[-462,2607]],[[268620,364836],[-243,-50],[-502,-1356]],[[267875,363430],[-354,1381],[6,2515]],[[282779,364717],[-405,-1350]],[[282374,363367],[-1213,1929]],[[281161,365296],[-10,1338]],[[284858,366812],[77,-164]],[[284935,366648],[-7,-2000],[-380,948],[-673,136],[24,1170]],[[240887,368746],[2,-7717]],[[240889,361029],[-605,67]],[[240284,361096],[-452,336]],[[239832,361432],[-22,6075]],[[161404,359060],[-763,407],[-257,-1537]],[[160384,357930],[-486,2766],[-658,1780],[-447,2636]],[[158896,368457],[359,-59]],[[261842,365567],[-522,-2203]],[[261320,363364],[-643,5164]],[[242100,360892],[-1211,137]],[[275026,365644],[-484,-1303]],[[274542,364341],[-690,-1814]],[[273852,362527],[-445,2381],[92,337]],[[230194,365417],[-2,-5660]],[[230192,359757],[-625,5]],[[229567,359762],[-1109,1]],[[228458,359763],[-15,8871]],[[228458,359763],[-876,-6]],[[227582,359757],[-948,0]],[[226634,359757],[-13,7216]],[[220213,368621],[49,-8798]],[[220262,359823],[-1515,-40]],[[218747,359783],[-1,7214],[-53,1]],[[221784,366876],[6,-7102]],[[221790,359774],[-1286,39]],[[220504,359813],[-242,10]],[[225109,366931],[7,-7184]],[[225116,359747],[-550,11]],[[224566,359758],[-1269,13]],[[223297,359771],[-31,7164]],[[261320,363364],[-485,-1545]],[[260835,361819],[-445,1353]],[[260390,363172],[-31,2226],[112,2239]],[[278782,367130],[-479,-729]],[[176230,341181],[676,-3534]],[[176906,337647],[-237,-294],[-2130,-6],[-3164,16]],[[171375,337363],[-1036,-167]],[[170339,337196],[74,1515],[-404,8051],[81,567],[-422,2802],[-73,2163],[-261,2779]],[[285316,364880],[167,-151]],[[285483,364729],[247,-950]],[[285730,363779],[-49,-868]],[[285681,362911],[-94,912],[-633,479],[-19,2346]],[[285270,368297],[233,-1659]],[[285503,366638],[-153,-1180]],[[285350,365458],[-34,-578]],[[260390,363172],[-160,-225]],[[260230,362947],[-453,-403],[-418,352]],[[259359,362896],[21,897],[-302,2238]],[[267875,363430],[-170,-1501],[19,-1797],[-189,-330]],[[267535,359802],[-167,367]],[[267368,360169],[-60,2883],[-338,936],[-80,1342],[-349,-222],[-82,-931]],[[266459,364177],[-53,420]],[[279509,363604],[-224,-3408]],[[279285,360196],[-519,776]],[[278766,360972],[-292,-176],[-159,1424],[-530,-1399]],[[239832,361432],[-1514,176]],[[238318,361608],[-48,3424]],[[253406,362518],[-128,-3094]],[[253278,359424],[-344,1548],[-334,-767]],[[252600,360205],[-233,1071]],[[252367,361276],[211,-229]],[[245537,361562],[-556,-176],[-16,-3767]],[[244965,357619],[-1212,98]],[[243753,357717],[7,2975]],[[201905,367295],[0,-3259],[563,-4371]],[[202468,359665],[-2632,116]],[[199836,359781],[-169,0]],[[276659,363979],[-321,-1646],[-128,662],[-334,-926]],[[275876,362069],[-139,-176]],[[275737,361893],[-37,2240],[-197,1991]],[[276171,365518],[-31,-844]],[[276140,364674],[211,-935],[217,1689],[-397,90]],[[257011,363733],[-117,-2666]],[[256894,361067],[-312,-151]],[[256582,360916],[-548,-371],[-28,566]],[[256006,361111],[-205,1594]],[[281161,365296],[-22,-3279]],[[281139,362017],[-569,-758]],[[280570,361259],[-694,2327],[30,826]],[[204580,359704],[-804,-10]],[[203776,359694],[-1308,-29]],[[258405,362945],[-625,205],[-142,-3375]],[[257638,359775],[-744,1292]],[[258210,365757],[195,-2812]],[[216175,367014],[3,-7320]],[[216178,359694],[-1317,-41]],[[214861,359653],[-39,8]],[[217536,367000],[3,-7258]],[[217539,359742],[-1361,-48]],[[218747,359783],[-870,-31]],[[217877,359752],[-338,-10]],[[232990,366930],[-4,-7162]],[[232986,359768],[-736,0]],[[232250,359768],[-495,-4]],[[231755,359764],[0,5588]],[[226634,359757],[-862,-11]],[[225772,359746],[-656,1]],[[223297,359771],[-1270,0]],[[222027,359771],[-237,3]],[[234233,366109],[4,-6336]],[[234237,359773],[-931,-5]],[[233306,359768],[-320,0]],[[254834,362561],[-147,-2742]],[[254687,359819],[-400,-773]],[[254287,359046],[-460,3989],[-150,90]],[[285503,366638],[154,-1165],[635,-2093],[-66,-1011]],[[286226,362369],[53,-610]],[[286279,361759],[-115,-211]],[[286164,361548],[-434,2231]],[[285483,364729],[-133,729]],[[237082,365181],[-19,-4504]],[[237063,360677],[-1556,162]],[[235507,360839],[0,5242]],[[275737,361893],[-473,-1710],[-546,-688]],[[274718,359495],[-101,1954]],[[274617,361449],[153,1098]],[[274770,362547],[-228,1794]],[[266459,364177],[-121,-2912],[119,-2629]],[[266457,358636],[-224,321]],[[266233,358957],[-784,1845]],[[265449,360802],[-274,2103],[71,1542]],[[264091,359038],[-613,-1731]],[[263478,357307],[-281,2109],[-435,319]],[[262762,359735],[-183,2206]],[[264284,362610],[-193,-3572]],[[235507,360839],[-1,-1078]],[[235506,359761],[-1085,11]],[[234421,359772],[-184,1]],[[259359,362896],[-159,-1977]],[[259200,360919],[-468,373],[-327,1653]],[[276140,364674],[31,844]],[[252367,361276],[-1008,2722]],[[251359,363998],[-3,1417]],[[272820,364154],[-287,-1785],[161,-863],[-535,-1063],[70,-474]],[[272229,359969],[-685,-1435],[-271,477]],[[271273,359011],[-336,3424]],[[250466,366020],[-48,-4181],[274,-849]],[[250692,360990],[92,-1565]],[[250784,359425],[-347,1557],[-155,-1006]],[[250282,359976],[-177,540],[-308,3946]],[[265449,360802],[-249,-1447],[-375,-411]],[[264825,358944],[-564,-169]],[[264261,358775],[-170,263]],[[251359,363998],[-14,-28]],[[251345,363970],[-427,-1200],[-226,-1780]],[[267368,360169],[-217,-969],[-641,-1367]],[[266510,357833],[-53,803]],[[283884,365227],[-225,371]],[[283710,365528],[174,-301]],[[248062,360548],[-410,-2218]],[[247652,358330],[-1170,83]],[[246482,358413],[-282,2305]],[[283884,365227],[441,266],[239,-1372]],[[284564,364121],[-405,-2254]],[[284159,361867],[-678,-2216]],[[283481,359651],[-2,3311]],[[283479,362962],[63,1380]],[[285316,364880],[0,0]],[[262181,363246],[-452,-3530],[-71,-1327]],[[261658,358389],[-614,241]],[[261044,358630],[-209,3189]],[[270247,360557],[-500,-1318]],[[269747,359239],[-387,951],[-242,3364]],[[231755,359764],[-101,-4]],[[231654,359760],[-1462,-3]],[[273852,362527],[-162,-1380]],[[273690,361147],[-561,-945],[-310,462],[-429,-1769]],[[272390,358895],[-161,1074]],[[283467,364200],[-36,-78]],[[282112,359573],[-317,632]],[[281795,360205],[-220,-114],[-436,1926]],[[282374,363367],[-290,-906],[28,-2888]],[[238318,361608],[-4,-1905]],[[238314,359703],[-2,-1358]],[[238312,358345],[-1259,166]],[[237053,358511],[10,2166]],[[160384,357930],[-174,-102]],[[160210,357828],[-281,264],[-163,-998]],[[159766,357094],[-380,2272],[-464,-405],[-520,2826]],[[283344,363927],[135,-965]],[[283481,359651],[-614,-2144]],[[282867,357507],[-109,310]],[[282758,357817],[-299,1492],[-347,264]],[[269086,363432],[-450,-1508],[10,-1311],[-407,-1312]],[[268239,359301],[-193,765],[-584,-1128],[73,864]],[[250282,359976],[-572,-2622]],[[249710,357354],[-478,-188]],[[249232,357166],[10,1268],[-274,2899],[59,795]],[[280570,361259],[-138,-3503]],[[280432,357756],[-433,-3599]],[[279999,354157],[-48,2703],[-666,3336]],[[274770,362547],[-153,-1098]],[[274718,359495],[-251,-984]],[[274467,358511],[-297,-982]],[[274170,357529],[-480,3618]],[[285497,362381],[-63,-1487],[-424,-1183]],[[285010,359711],[-289,-964]],[[284721,358747],[-34,2055],[-528,1065]],[[284564,364121],[581,-507],[352,-1233]],[[277364,360567],[-130,-3464]],[[277234,357103],[-337,-1260],[-663,787],[-116,-677]],[[276118,355953],[-70,869],[-471,579]],[[275577,357401],[360,2851],[-61,1817]],[[251671,358926],[-486,-648],[-308,443]],[[250877,358721],[-93,704]],[[251345,363970],[326,-5044]],[[252600,360205],[-4,-1497]],[[252596,358708],[-916,78]],[[251680,358786],[-9,140]],[[286164,361548],[133,-1959]],[[286297,359589],[-664,2557],[48,765]],[[269747,359239],[82,-1209],[-301,-551]],[[269528,357479],[-635,40],[-381,-1463]],[[268512,356056],[-298,1723]],[[268214,357779],[25,1522]],[[268976,359021],[0,0]],[[259200,360919],[-148,-2366]],[[259052,358553],[-666,-2939]],[[258386,355614],[14,578],[-508,429],[-79,979]],[[257813,357600],[-175,2175]],[[262762,359735],[-638,-2584]],[[262124,357151],[-421,-77]],[[261703,357074],[-45,1315]],[[261044,358630],[-402,-2235]],[[260642,356395],[-399,433]],[[260243,356828],[-13,6119]],[[254287,359046],[-781,-1739]],[[253506,357307],[-228,2117]],[[286374,362810],[-59,-945]],[[286315,361865],[-36,-106]],[[286226,362369],[148,441]],[[260243,356828],[-662,-2213]],[[259581,354615],[-529,3938]],[[256006,361111],[-211,-8003]],[[255795,353108],[-851,-65]],[[254944,353043],[-147,-18]],[[254797,353025],[95,6137],[-205,657]],[[285916,358061],[-1039,-4907]],[[284877,353154],[-34,77]],[[284843,353231],[-42,1109]],[[284801,354340],[305,3818],[-96,1553]],[[285497,362381],[59,-1872],[467,-1558],[-107,-890]],[[271273,359011],[-157,-551]],[[271116,358460],[-308,-1198],[-364,-180],[-73,-1054],[-635,-1658]],[[269736,354370],[-208,3109]],[[278766,360972],[-356,-9724]],[[278410,351248],[-345,-5]],[[278065,351243],[106,760],[-263,1161],[-285,-492],[86,-1439]],[[277709,351233],[-112,-1]],[[277597,351232],[-569,22]],[[277028,351254],[206,5849]],[[275577,357401],[-419,-1374],[-218,-1669]],[[274940,354358],[-473,4153]],[[249232,357166],[-26,-4320],[-719,34]],[[248487,352880],[-528,25]],[[247959,352905],[-9,1582],[-298,3843]],[[281795,360205],[-66,-4559]],[[281729,355646],[-596,560],[-701,1550]],[[286315,361865],[307,-510],[-325,-1766]],[[284721,358747],[-1328,-4408]],[[283393,354339],[-167,252],[88,2639],[-447,277]],[[246482,358413],[44,-2142]],[[246526,356271],[-1263,227]],[[245263,356498],[-303,38],[5,1083]],[[240284,361096],[-17,-4867]],[[240267,356229],[-1101,143]],[[239166,356372],[-105,8],[12,3261],[-759,62]],[[274170,357529],[-841,-1352]],[[273329,356177],[-156,-696],[-455,-110]],[[272718,355371],[-328,3524]],[[257813,357600],[-221,-728],[-202,-3628]],[[257390,353244],[-829,-106]],[[256561,353138],[21,7778]],[[256561,353138],[-151,-14]],[[256410,353124],[-615,-16]],[[242553,360828],[-66,-4878]],[[242487,355950],[-1818,221]],[[240669,356171],[-402,58]],[[253506,357307],[130,-2237]],[[253636,355070],[-1045,64]],[[252591,355134],[5,3574]],[[250877,358721],[-61,-2945]],[[250816,355776],[-215,-1257],[64,-1203]],[[250665,353316],[-203,-1647],[-216,1259]],[[250246,352928],[-536,4426]],[[279999,354157],[-151,-412],[-88,-2491]],[[279760,351254],[-173,-5]],[[279587,351249],[-953,1]],[[278634,351250],[-224,-2]],[[243753,357717],[-40,-7254]],[[243713,350463],[-1248,-11]],[[242465,350452],[22,5498]],[[237053,358511],[-17,-3425]],[[237036,355086],[-1531,349]],[[235505,355435],[1,4326]],[[266233,358957],[17,-975],[-599,-2206],[-221,-1817]],[[265430,353959],[-521,3091],[-84,1894]],[[282758,357817],[6,-3369],[-310,-3129]],[[282454,351319],[-369,-15]],[[282085,351304],[-408,-9]],[[281677,351295],[52,4351]],[[268214,357779],[-540,-707],[-177,-2081],[-906,-1449]],[[266591,353542],[-141,2662],[60,1629]],[[272718,355371],[-733,-1028],[-226,-1288]],[[271759,353055],[-261,2814],[-382,2591]],[[192649,359743],[0,-24859],[484,-3],[-47,-2692],[-3,-12919],[-57,-4901],[47,-1624],[-30,-12942],[-91,-9],[0,-3908]],[[192952,295886],[-303,170]],[[192649,296056],[0,7876],[-2090,0],[0,4901]],[[190559,308833],[0,51008]],[[190559,308833],[-267,68],[-549,2097],[-586,1352],[-625,-722],[-214,1094]],[[188318,312722],[8,6149],[-629,15],[-1,3314],[-1550,-155],[-3,3263],[-297,21],[4,2068],[-380,-318],[-149,1053],[-899,1045],[-616,2799],[-445,402]],[[183361,332378],[1,5119],[67,1656],[-123,2775],[363,944],[70,1118],[518,1618],[615,865],[469,2332],[-89,1927],[91,1456],[24,3833],[237,2875],[-31,898]],[[254797,353025],[-1049,764]],[[253748,353789],[-112,1281]],[[233306,359768],[-67,-1052],[1,-6427]],[[233240,352289],[-1054,-2]],[[232186,352287],[-1,6412],[65,1069]],[[231654,359760],[-1,-10708]],[[231653,349052],[-2,-4884],[-743,12]],[[230908,344180],[41,1148],[-278,232],[-357,1844],[-191,-673],[-230,2345],[-236,377],[-144,2237],[-300,-1766],[-371,678]],[[228842,350602],[273,1700],[-394,-68]],[[228721,352234],[-27,1706],[357,-36],[129,1256],[379,564],[8,4038]],[[224566,359758],[31,-9970]],[[224597,349788],[-210,-1342],[-482,1078],[-172,1065],[-325,12]],[[223408,350601],[-47,1717],[-453,3424],[-433,680]],[[222475,356422],[-448,3349]],[[234421,359772],[22,-6122]],[[234443,353650],[-17,-2995]],[[234426,350655],[-898,9]],[[233528,350664],[-1,1625],[-287,0]],[[232186,352287],[-8,-3237]],[[232178,349050],[-525,2]],[[227582,359757],[-1,-7541]],[[227581,352216],[-1788,-1]],[[225793,352215],[-21,7531]],[[225793,352215],[0,-2429]],[[225793,349786],[-1196,2]],[[228721,352234],[-1137,-18]],[[227584,352216],[-3,0]],[[235505,355435],[1,-1837]],[[235506,353598],[-1063,52]],[[222475,356422],[0,-4178],[-865,-44]],[[221610,352200],[-1107,8]],[[220503,352208],[1,7605]],[[203776,359694],[98,-2074],[-26,-3169],[117,-4650],[-91,-2221],[-239,-893],[559,-1234],[337,-2424],[573,-1622]],[[205104,341407],[-115,-752],[-407,114]],[[204582,340769],[2,439],[-988,-6],[48,-1326],[-533,2]],[[203111,339878],[-7,660]],[[203104,340538],[2,655],[-1777,-48],[0,4051],[-2059,60]],[[199270,345256],[16,10890],[481,2044],[69,1591]],[[199270,345256],[-7,-4086]],[[199263,341170],[-3954,38]],[[195309,341208],[2,18556]],[[195309,341208],[0,-19410]],[[195309,321798],[-1,-7083]],[[195308,314715],[-2,-14913]],[[195306,299802],[-838,-13],[-15,-899],[-397,-1422]],[[194056,297468],[-882,-3135],[-222,1553]],[[212147,359783],[-1,-9303]],[[212146,350480],[-110,1],[3,-8288]],[[212039,342193],[-1,-5881]],[[212038,336312],[-930,12]],[[211108,336324],[28,6448],[-1196,-11],[1,1643],[-599,9],[0,810]],[[209342,345223],[3,14488]],[[214861,359653],[-12,-9170]],[[214849,350483],[-362,-4]],[[214487,350479],[-2341,1]],[[217877,359752],[-24,-9286]],[[217853,350466],[-365,-7]],[[217488,350459],[-1501,5]],[[215987,350464],[-1138,19]],[[220503,352208],[-3,-1741]],[[220500,350467],[-1510,-6]],[[218990,350461],[-1137,5]],[[284801,354340],[42,-1109]],[[284877,353154],[-51,-1711]],[[284826,351443],[-1,-152]],[[284825,351291],[-691,43]],[[284134,351334],[-370,-26]],[[283764,351308],[-248,1790],[-323,549],[200,692]],[[263478,357307],[2,-789],[-559,-4123]],[[262921,352395],[-20,2]],[[262901,352397],[-528,227]],[[262373,352624],[-249,4527]],[[205966,359691],[58,-1481],[-86,-3571],[-387,-996],[-22,-2199],[163,-1117],[-61,-4280]],[[205631,346047],[-183,-448],[-37,-1983],[-307,-2209]],[[209342,345223],[-1191,14]],[[208151,345237],[-1195,-14],[1,803],[-1326,21]],[[239166,356372],[-30,-5934]],[[239136,350438],[-750,14]],[[238386,350452],[29,5979],[-103,1914]],[[162899,346141],[-231,-1190],[-456,1973],[-261,-927],[-61,1184],[-572,3412],[-183,-61],[-19,1981],[-417,1414],[44,778],[-533,3123]],[[265430,353959],[-148,-1854]],[[265282,352105],[-222,46]],[[265060,352151],[-603,36]],[[264457,352187],[-224,1543],[-135,2569],[163,2476]],[[286694,356681],[-124,-213]],[[286570,356468],[-12,177]],[[286558,356645],[136,36]],[[286870,358327],[-79,-1657]],[[286791,356670],[-328,768],[3,1657],[404,-768]],[[264457,352187],[-96,-2]],[[264361,352185],[-1440,210]],[[266591,353542],[-596,-1193]],[[265995,352349],[-713,-244]],[[251680,358786],[-1,-3231]],[[251679,355555],[-863,221]],[[252591,355134],[-9,-4639]],[[252582,350495],[-78,9]],[[252504,350504],[-835,20]],[[251669,350524],[10,5031]],[[261703,357074],[-225,-4257]],[[261478,352817],[-392,-140]],[[261086,352677],[-444,3718]],[[259581,354615],[12,-1748]],[[259593,352867],[-646,209]],[[258947,353076],[-577,206]],[[258370,353282],[16,2332]],[[238386,350452],[-787,-3]],[[237599,350449],[-586,0]],[[237013,350449],[23,4637]],[[287471,351575],[127,-159]],[[287598,351416],[-118,-4]],[[287480,351412],[-9,163]],[[287748,351418],[-69,0]],[[287679,351418],[-139,3115],[-110,-3119]],[[287430,351414],[-90,0]],[[287340,351414],[-61,2]],[[287279,351416],[-242,-2]],[[287037,351414],[1,2144],[150,496],[-378,1440],[-61,1239]],[[286749,356733],[42,-63]],[[286870,358327],[530,-179],[348,-6730]],[[274940,354358],[-364,-882],[-48,-1926]],[[274528,351550],[-629,24]],[[273899,351574],[-213,1696]],[[273686,353270],[-50,397]],[[273636,353667],[-307,2510]],[[270321,353193],[0,0]],[[270321,353193],[-338,-940]],[[269983,352253],[-141,1]],[[269842,352254],[-106,2116]],[[271759,353055],[-114,-499]],[[271645,352556],[-501,43]],[[271144,352599],[-890,-363]],[[270254,352236],[67,957]],[[247959,352905],[-202,-2472]],[[247757,350433],[-990,13]],[[246767,350446],[-44,3366],[-197,2459]],[[286570,356468],[-316,-133]],[[286254,356335],[304,310]],[[286305,356801],[-99,492]],[[286206,357293],[36,564]],[[286242,357857],[63,-1056]],[[286206,357293],[-103,-959],[-95,-4918]],[[286008,351416],[-140,0]],[[285868,351416],[-1042,27]],[[285916,358061],[-22,-1249],[348,1045]],[[163912,339436],[276,-2209]],[[164188,337227],[-3150,124]],[[161038,337351],[-326,1746],[-77,1735],[-231,787],[-396,3161],[-503,2053],[-222,5106],[442,1738],[41,3417]],[[283764,351308],[-1310,11]],[[283158,354580],[0,0]],[[268512,356056],[-491,-2310],[-105,-1528]],[[267916,352218],[-1356,66]],[[266560,352284],[-565,65]],[[281677,351295],[-774,-7]],[[280903,351288],[-371,-27]],[[280532,351261],[-772,-7]],[[262373,352624],[-838,204]],[[261535,352828],[-57,-11]],[[245263,356498],[-29,-6068]],[[245234,350430],[-782,-12]],[[244452,350418],[-119,11]],[[244333,350429],[-620,34]],[[269842,352254],[-878,-30]],[[268964,352224],[-617,-6]],[[268347,352218],[-431,0]],[[258370,353282],[-422,-315]],[[257948,352967],[-558,277]],[[276118,355953],[-148,-276],[114,-4413]],[[276084,351264],[-1077,151]],[[275007,351415],[-479,135]],[[250246,352928],[-253,-2473]],[[249993,350455],[-185,-21]],[[249808,350434],[-152,5]],[[249656,350439],[-17,-3004]],[[249639,347435],[-129,1228],[-367,441],[-80,-746],[-583,45]],[[248480,348403],[7,4477]],[[286305,356801],[-51,-466]],[[286694,356681],[55,52]],[[287037,351414],[-532,-2]],[[286505,351412],[-497,4]],[[277028,351254],[-871,10]],[[276157,351264],[-73,0]],[[276536,353676],[0,0]],[[181366,356861],[18,-12097],[-305,-3168],[-223,-193],[-381,2417],[-725,-8],[-350,-1052],[144,-4363],[76,-6300],[227,-3470],[73,-3058],[-216,-1125],[36,-1876]],[[179740,322568],[-2834,15079]],[[261086,352677],[-980,64]],[[260106,352741],[-513,126]],[[246767,350446],[-580,-1]],[[246187,350445],[-953,-15]],[[240669,356171],[-21,-5742]],[[240648,350429],[-227,4]],[[240421,350433],[-1233,7]],[[239188,350440],[-52,-2]],[[223408,350601],[13,-6429]],[[223421,344172],[-1188,51]],[[222233,344223],[-596,-4],[-27,7981]],[[273636,353667],[50,-397]],[[273899,351574],[-178,50]],[[273721,351624],[-1258,239]],[[272463,351863],[-903,251]],[[271560,352114],[85,442]],[[242465,350452],[-84,-5]],[[242381,350447],[-1055,0]],[[241326,350447],[-678,-18]],[[251669,350524],[-28,4]],[[251641,350528],[-22,-4]],[[251619,350524],[-191,1382],[-763,1410]],[[237013,350449],[-1507,13]],[[235506,350462],[0,3136]],[[253748,353789],[47,-3359]],[[253795,350430],[-1213,65]],[[170339,337196],[-4266,59]],[[166073,337255],[29,8918],[143,-17],[5,2474],[-284,1630]],[[254944,353043],[136,-5034]],[[255080,348009],[-639,-725],[-444,466]],[[253997,347750],[-26,131]],[[253971,347881],[-176,2549]],[[235506,350462],[154,-6283]],[[235660,344179],[-653,-10]],[[235007,344169],[-597,8]],[[234410,344177],[16,6478]],[[257414,348713],[-201,-1048],[-241,622]],[[256972,348287],[-208,-250],[-369,1607]],[[256395,349644],[15,3480]],[[257948,352967],[-373,-4226],[-161,-28]],[[258947,353076],[-67,-2837]],[[258880,350239],[-152,-2605]],[[258728,347634],[-470,-221],[-397,-1686]],[[257861,345727],[-447,2986]],[[249808,350434],[-152,5]],[[251619,350524],[-1425,9]],[[250194,350533],[-201,-78]],[[256395,349644],[-463,-2486]],[[255932,347158],[-632,238]],[[255300,347396],[-220,613]],[[278065,351243],[-356,-10]],[[260035,350463],[-39,-1632]],[[259996,348831],[-420,265]],[[259576,349096],[-528,340],[-168,803]],[[260106,352741],[-71,-2278]],[[270254,352236],[-271,17]],[[248480,348403],[6,-7255]],[[248486,341148],[-919,-50]],[[247567,341098],[-247,-14]],[[247320,341084],[522,3829]],[[247842,344913],[352,1893],[-43,1810],[-210,1819],[-184,-2]],[[262901,352397],[149,-1465]],[[263050,350932],[-592,1051],[-298,-537],[-193,-2702]],[[261967,348744],[-458,2312]],[[261509,351056],[26,1772]],[[261509,351056],[-433,-574],[-153,-1811]],[[260923,348671],[-582,2219],[-306,-427]],[[270706,350628],[-572,-2009],[-308,-73]],[[269826,348546],[-389,913],[-672,-256]],[[268765,349203],[199,3021]],[[271144,352599],[-438,-1971]],[[271560,352114],[-142,-3666]],[[271418,348448],[-528,-1943]],[[270890,346505],[-41,-397]],[[270849,346108],[-351,1893],[300,1976],[-92,651]],[[264361,352185],[5,-1439],[-252,-900],[56,-3382],[-120,-1264]],[[264050,345200],[-190,-992]],[[263860,344208],[-474,1990],[-250,1871]],[[263136,348069],[117,454],[-203,2409]],[[266560,352284],[206,-1169],[31,-2258]],[[266797,348857],[-174,-720],[-607,-561]],[[266016,347576],[-183,13],[-202,1690],[-279,-296]],[[265352,348983],[-292,3168]],[[228842,350602],[-61,-3232],[298,0],[0,-1617]],[[229079,345753],[-602,-1],[-1,-1624],[-593,-2]],[[227883,344126],[-297,100]],[[227586,344226],[-2,7990]],[[268347,352218],[-502,-560],[-752,-3146]],[[267093,348512],[-296,345]],[[233528,350664],[-299,-2],[-13,-8097]],[[233216,342565],[-366,1],[-131,1627],[-399,1]],[[232320,344194],[-150,-1],[8,4857]],[[265352,348983],[-275,-2785]],[[265077,346198],[-193,-791],[-429,316],[-38,-1253],[-367,730]],[[268765,349203],[-61,-398]],[[268704,348805],[-665,-1447],[-393,-1683]],[[267646,345675],[-224,1918],[-258,-1047]],[[267164,346546],[-71,1966]],[[227586,344226],[-599,4]],[[226987,344230],[-1193,4]],[[225794,344234],[-1,5552]],[[222233,344223],[3,-2818]],[[222236,341405],[-225,-158],[-276,-2614],[-162,-422],[-441,842],[-101,2155],[-521,-2268]],[[220510,338940],[0,3261]],[[220510,342201],[-10,8266]],[[272463,351863],[277,-3899]],[[272740,347964],[-622,-2325]],[[272118,345639],[-700,2809]],[[263136,348069],[-494,-1685],[-81,-2308]],[[262561,344076],[-537,-332]],[[262024,343744],[-54,115]],[[261970,343859],[103,957],[-106,3928]],[[273721,351624],[-182,-2986]],[[273539,348638],[-167,-516],[-189,1242],[-443,-1400]],[[230908,344180],[-82,5]],[[230826,344185],[-902,-43]],[[229924,344142],[-550,-15],[0,1626],[-295,0]],[[275007,351415],[-33,-5379]],[[274974,346036],[-1,-378]],[[274973,345658],[-247,763],[-926,-860]],[[273800,345561],[12,1685],[-273,1392]],[[285868,351416],[140,-749]],[[286008,350667],[106,-2464]],[[286114,348203],[-296,-486]],[[285818,347717],[-335,-961]],[[285483,346756],[-266,1180],[-452,775]],[[284765,348711],[60,2580]],[[287518,346134],[-187,1417]],[[287331,347551],[-272,272],[-554,3589]],[[287279,351416],[112,-2597],[320,-2366],[229,-3945],[-422,3626]],[[287748,351418],[261,-5943]],[[288009,345475],[-7,-9]],[[288002,345466],[-124,1107],[-199,4845]],[[287598,351416],[-258,-2]],[[287430,351414],[50,-2]],[[276157,351264],[-22,-5309]],[[276135,345955],[-1161,81]],[[287331,347551],[294,-3319],[-296,418],[-595,3435]],[[286734,348085],[-726,2582]],[[282085,351304],[5,-704]],[[282090,350600],[-40,-2323],[-263,-3338]],[[281787,344939],[-837,1178]],[[280950,346117],[82,751],[-129,4420]],[[284010,345760],[-232,-1458]],[[283778,344302],[-264,840],[-153,2057],[-292,-389],[-26,2141],[-250,1169],[-703,480]],[[284134,351334],[123,-1350],[-247,-4224]],[[284765,348711],[428,-836],[170,-2171]],[[285363,345704],[-1353,56]],[[279587,351249],[-16,-5692]],[[279571,345557],[-415,60]],[[279156,345617],[-565,56]],[[278591,345673],[43,5577]],[[280950,346117],[-322,-1897],[-207,205]],[[280421,344425],[-40,5196],[151,1640]],[[280421,344425],[-138,-2848]],[[280283,341577],[-564,914]],[[279719,342491],[-155,441],[7,2625]],[[277597,351232],[-60,-5440]],[[277537,345792],[-1,-133]],[[277536,345659],[-1401,296]],[[278591,345673],[-288,31]],[[278303,345704],[-766,88]],[[261970,343859],[-836,1715],[-211,1218]],[[260923,346792],[0,1879]],[[260923,346792],[-350,-1703],[-444,506]],[[260129,345595],[-184,949],[51,2287]],[[283778,344302],[137,-1340],[-235,-399]],[[283680,342563],[-211,-1306]],[[283469,341257],[-343,603],[-21,992],[-459,1162]],[[282646,344014],[-526,-168],[-333,1093]],[[234410,344177],[-298,-3],[1,-1624],[-250,5]],[[233863,342555],[-647,10]],[[270849,346108],[-412,-3021]],[[270437,343087],[-395,999]],[[270042,344086],[-95,1420],[-234,365]],[[269713,345871],[113,2675]],[[286734,348085],[37,-1381],[441,-2720],[-295,-510],[-337,1726]],[[286580,345200],[-247,782],[-219,2221]],[[251641,350528],[29,-1722],[-373,-22],[-27,-3471]],[[251270,345313],[-542,-338]],[[250728,344975],[-915,150]],[[249813,345125],[342,3659],[39,1749]],[[225794,344234],[-297,0]],[[225497,344234],[-1188,0]],[[224309,344234],[-888,-62]],[[252504,350504],[-37,-6508]],[[252467,343996],[-452,-1664]],[[252015,342332],[-416,1162],[-329,1819]],[[249813,345125],[-408,-509]],[[249405,344616],[-184,1254],[448,35],[-218,1039],[188,491]],[[215988,342196],[-1503,-3]],[[214485,342193],[2,8286]],[[215987,350464],[1,-8268]],[[253971,347881],[-287,-10],[29,-1851],[-361,-2613]],[[253352,343407],[3,475],[-888,114]],[[217487,342236],[0,-43]],[[217487,342193],[-1499,3]],[[217488,350459],[-1,-8223]],[[214485,342193],[-2446,0]],[[218990,350461],[-2,-8244]],[[218988,342217],[-1501,19]],[[220510,342201],[-1504,15]],[[219006,342216],[-18,1]],[[237599,350449],[-66,-2391],[209,-1186]],[[237742,346872],[-201,-1332]],[[237541,345540],[-237,-554],[-1005,172],[-161,-2143],[-448,53]],[[235690,343068],[-30,1111]],[[244333,350429],[-13,-3019],[-310,36],[-1,-1628],[-349,62]],[[243660,345880],[-1297,148]],[[242363,346028],[18,4419]],[[239188,350440],[-10,-7018]],[[239178,343422],[-500,71]],[[238678,343493],[-199,24],[-297,3290],[-440,65]],[[244452,350418],[274,-1428],[139,-3024]],[[244865,345966],[-253,-502],[-21,-6336]],[[244591,339128],[-295,861],[-689,91]],[[243607,340080],[78,1094],[-25,4706]],[[246125,346133],[-9,-2184]],[[246116,343949],[-922,327],[-329,1690]],[[246187,350445],[91,-4019],[-153,-293]],[[240421,350433],[-86,-130],[-16,-7014]],[[240319,343289],[-151,19]],[[240168,343308],[-990,114]],[[241326,350447],[-51,-4301],[214,-302],[163,-1657],[2,-1854]],[[241654,342333],[-1188,137],[-147,819]],[[242363,346028],[-108,-2388]],[[242255,343640],[-5,-1372],[-299,33],[-7,-1620],[-298,34]],[[241646,340715],[8,1618]],[[247842,344913],[-364,-14],[3,1093],[-1356,141]],[[166073,337255],[-1825,-23]],[[164248,337232],[-60,-5]],[[259576,349096],[-440,-2424]],[[259136,346672],[-408,962]],[[256972,348287],[-235,-4733],[-157,-1542]],[[256580,342012],[-358,78]],[[256222,342090],[89,2409],[-69,2509],[-310,150]],[[256334,346649],[0,0]],[[269713,345871],[-270,-1826],[-440,-1090]],[[269003,342955],[-147,935],[39,5066],[-191,-151]],[[273800,345561],[-24,-3359]],[[273776,342202],[-410,-186]],[[273366,342016],[-834,-920]],[[272532,341096],[-604,2255]],[[271928,343351],[190,2288]],[[266016,347576],[-182,-3348]],[[265834,344228],[-583,406]],[[265251,344634],[-174,1564]],[[260129,345595],[-76,-1981]],[[260053,343614],[-159,-706],[-544,-142]],[[259350,342766],[-214,3906]],[[249405,344616],[100,-1066],[-238,-810],[-78,-1541]],[[249189,341199],[-703,-51]],[[232320,344194],[0,-4873],[-163,-839]],[[232157,338482],[-593,17]],[[231564,338499],[9,4068],[-746,8],[-1,1610]],[[267164,346546],[-203,142],[-387,-2272]],[[266574,344416],[-254,92],[-305,-1843]],[[266015,342665],[-181,1563]],[[257861,345727],[219,-2690]],[[258080,343037],[-288,-2464]],[[257792,340573],[-469,1262],[-395,311],[-275,-1179],[-73,1045]],[[269003,342955],[-22,-1049]],[[268981,341906],[-95,483],[-414,-1436],[-99,-1242],[-215,431]],[[268158,340142],[-746,4390]],[[267412,344532],[234,1143]],[[271928,343351],[-738,-110]],[[271190,343241],[-274,1942],[-26,1322]],[[286580,345200],[260,-2059],[-166,-58],[-563,1793],[417,-1963],[-516,-158]],[[286012,342755],[-231,471],[37,4491]],[[263860,344208],[277,-2132]],[[264137,342076],[-291,-1173],[-653,-1443]],[[263193,339460],[-112,1610],[-520,3006]],[[255300,347396],[-148,-2919]],[[255152,344477],[-646,1244],[-421,-30]],[[254085,345691],[-88,2059]],[[254085,345691],[76,-2249],[-252,-3271],[243,-246],[-104,-1722]],[[254048,338203],[-22,-470]],[[254026,337733],[-577,561]],[[253449,338294],[-110,14],[13,5099]],[[259350,342766],[125,-2327]],[[259475,340439],[-385,-124]],[[259090,340315],[-438,450],[-572,2272]],[[286012,342755],[225,-327],[-290,-1136],[-496,1123],[-83,1530],[115,2811]],[[267412,344532],[-191,-1783]],[[267221,342749],[-165,989],[-482,678]],[[256222,342090],[-60,-1685]],[[256162,340405],[-921,637]],[[255241,341042],[-89,3435]],[[238678,343493],[80,-2934],[-203,-1599],[13,-2198]],[[238568,336762],[-493,95]],[[238075,336857],[-599,-165]],[[237476,336692],[-145,22]],[[237331,336714],[53,6146],[157,2680]],[[262024,343744],[-408,-705],[-49,-2263]],[[261567,340776],[-127,977],[-554,938],[-379,-1246]],[[260507,341445],[-454,2169]],[[265251,344634],[-424,-2187],[97,-598],[-378,-805],[-214,-1552]],[[264332,339492],[-195,2584]],[[274973,345658],[38,-1939],[-162,-1684]],[[274849,342035],[-546,78]],[[274303,342113],[-527,89]],[[271190,343241],[205,-849],[-197,-1934]],[[271198,340458],[-379,-47]],[[270819,340411],[-107,-893]],[[270712,339518],[-146,571],[-129,2998]],[[281787,344939],[-692,-7162]],[[281095,337777],[-139,1398],[-673,2402]],[[247320,341084],[-33,-576],[-1289,60]],[[245998,340568],[118,3381]],[[208151,345237],[293,-4777],[-163,-1891],[63,-1473]],[[208344,337096],[-1781,-187],[-951,1848],[-1038,6]],[[204574,338763],[8,2006]],[[276135,345955],[-22,-4592]],[[276113,341363],[-980,-698]],[[275133,340665],[-284,1370]],[[243607,340080],[-387,-1384]],[[243220,338696],[-296,1224],[-438,79],[156,1793],[-387,1848]],[[270042,344086],[-542,-1569]],[[269500,342517],[-247,-1769]],[[269253,340748],[-294,-190],[22,1348]],[[245998,340568],[-64,-1620],[-438,77]],[[245496,339025],[-463,92]],[[245033,339117],[-442,11]],[[277536,345659],[-27,-6363]],[[277509,339296],[-1406,391]],[[276103,339687],[10,1676]],[[278303,345704],[22,-6834]],[[278325,338870],[33,-603],[-850,-17]],[[277508,338250],[1,1046]],[[229924,344142],[1,-4066]],[[229925,340076],[-1447,8]],[[228478,340084],[-594,801],[-1,3241]],[[255241,341042],[-480,-1635],[-27,-1245]],[[254734,338162],[-379,-650],[-307,691]],[[285363,345704],[-90,-1720],[200,-3016],[-54,-1229]],[[285419,339739],[-161,-1091]],[[285258,338648],[-207,365],[-191,-1125],[-97,1432],[-186,-785],[-144,1522],[-394,-15],[-22,1229],[-337,1292]],[[279156,345617],[-183,-6998]],[[278973,338619],[-648,251]],[[279719,342491],[139,-1156],[-370,-2640],[-207,11]],[[279281,338706],[-308,-87]],[[237331,336714],[-1477,-33]],[[235854,336681],[-164,6387]],[[288818,335050],[63,-1480],[-197,-6894],[-610,-638]],[[288074,326038],[-5,98]],[[288069,326136],[643,1434],[149,5776],[-43,1704]],[[287690,334443],[-349,562]],[[287341,335005],[74,4092],[488,661],[174,-763],[41,-4813],[-337,-752],[-91,1013]],[[288009,345475],[568,-6869],[-469,3338],[-106,3522]],[[252015,342332],[-40,-5057]],[[251975,337275],[-583,117]],[[251392,337392],[-539,1205],[-221,2559]],[[250632,341156],[96,3819]],[[203103,338170],[0,312]],[[203103,338482],[0,-312]],[[203104,340538],[-422,-117],[-2,-2838],[423,-966]],[[203103,336617],[9,-10066]],[[203112,326551],[-2650,80]],[[200462,326631],[-119,1602],[-198,-3]],[[200145,328230],[5,12910],[-887,30]],[[211108,336324],[-13,-6367],[-717,-154]],[[210378,329803],[-244,605],[-702,7065],[-124,-377],[-964,0]],[[250632,341156],[-254,-2106],[-174,-102]],[[250204,338948],[-181,1222],[-661,-786]],[[249362,339384],[-173,1815]],[[282646,344014],[-362,-5323]],[[282284,338691],[-1014,-2547]],[[281270,336144],[-175,1633]],[[267221,342749],[-212,-3524]],[[267009,339225],[-457,518],[-553,2138]],[[265999,341881],[16,784]],[[265999,341881],[52,-1239],[-389,-1579]],[[265662,339063],[-165,-856],[-257,652],[-539,-385],[-75,-938]],[[264626,337536],[-271,1710]],[[264355,339246],[-23,246]],[[268158,340142],[-176,-2868]],[[267982,337274],[-326,-27],[-492,-1365]],[[267164,335882],[-155,3343]],[[263193,339460],[-282,-1554]],[[262911,337906],[-375,-1179]],[[262536,336727],[-941,74]],[[261595,336801],[1,25]],[[261596,336826],[-53,386]],[[261543,337212],[10,105]],[[261553,337317],[156,2179],[-142,1280]],[[226987,344230],[5,-8167]],[[226992,336063],[-1485,-16]],[[225507,336047],[-10,8187]],[[228478,340084],[-1,-4053]],[[228477,336031],[-1485,32]],[[225507,336047],[-296,-3],[0,-3229]],[[225211,332815],[-863,2]],[[224348,332817],[-25,4855]],[[224323,337672],[-14,6562]],[[224323,337672],[-2072,-4]],[[222251,337668],[-15,3737]],[[231564,338499],[-444,4],[-1,-4058]],[[231119,334445],[-1192,-1]],[[229927,334444],[-2,5632]],[[233863,342555],[-208,-1974],[146,-1820],[-99,-1073]],[[233702,337688],[-1043,-409],[-29,1219],[-323,-7]],[[232307,338491],[-150,-9]],[[235007,344169],[-28,-9733]],[[234979,334436],[-892,6]],[[234087,334442],[1,3236],[-386,10]],[[235854,336681],[57,-2246]],[[235911,334435],[-932,1]],[[270712,339518],[-422,-1622]],[[270290,337896],[-183,3519],[-607,1102]],[[283469,341257],[168,-1847],[-22,-1611]],[[283615,337799],[-112,244],[-755,-2883]],[[282748,335160],[-250,2798],[-214,733]],[[253449,338294],[-4,-541]],[[253445,337753],[-1088,149],[-103,-659]],[[252254,337243],[-279,32]],[[260507,341445],[21,-2479],[-126,-943]],[[260402,338023],[-565,156]],[[259837,338179],[-362,2260]],[[243220,338696],[34,-3025]],[[243254,335671],[-1124,135]],[[242130,335806],[-496,71],[5,1352]],[[241639,337229],[7,3486]],[[240168,343308],[-18,-7270]],[[240150,336038],[-594,78]],[[239556,336116],[-989,104],[1,542]],[[272532,341096],[-16,-3721]],[[272516,337375],[-83,-532]],[[272433,336843],[-535,176],[-700,3439]],[[241639,337229],[-594,41],[-3,-1349],[-498,75]],[[240544,335996],[-394,42]],[[259090,340315],[-150,-4665]],[[258940,335650],[-106,-1338]],[[258834,334312],[-300,532],[-141,-775],[-313,1294],[-345,-46]],[[257735,335317],[-130,449]],[[257605,335766],[203,1497],[-16,3310]],[[270290,337896],[-396,-2234]],[[269894,335662],[-202,1972],[-170,143]],[[269522,337777],[-269,2971]],[[261596,336826],[-53,386]],[[261553,337317],[-455,475],[-475,-445]],[[260623,337347],[-221,676]],[[281095,337777],[-585,-2047],[-679,-3519]],[[279831,332211],[-571,1186]],[[279260,333397],[-227,517],[248,4792]],[[285258,338648],[-235,-2967]],[[285023,335681],[-392,-875],[-524,1391]],[[284107,336197],[-492,1602]],[[269522,337777],[-293,-274],[-813,-2185],[-219,-149]],[[268197,335169],[-215,2105]],[[275133,340665],[83,-1449],[-296,-1328],[32,-1468]],[[274952,336420],[-480,1873],[-210,137]],[[274262,338430],[41,3683]],[[219006,342216],[1,-8139]],[[219007,334077],[-1521,-4]],[[217486,334073],[0,115]],[[217486,334188],[1,8005]],[[220510,338940],[0,-4867]],[[220510,334073],[-1503,4]],[[217486,334188],[-1496,-23]],[[215990,334165],[-2,8031]],[[214485,342193],[1,-7963]],[[214486,334230],[-2449,-96]],[[212037,334134],[1,2178]],[[274262,338430],[-178,-3319],[96,-3116]],[[274180,331995],[-135,-24]],[[274045,331971],[-450,-339]],[[273595,331632],[-36,1117]],[[273559,332749],[96,1381],[-522,2938]],[[273133,337068],[300,3340],[-67,1608]],[[215990,334165],[0,-71]],[[215990,334094],[-1504,-3]],[[214486,334091],[0,139]],[[257605,335766],[-267,-59]],[[257338,335707],[-1207,2677]],[[256131,338384],[31,2021]],[[273133,337068],[-79,883],[-538,-576]],[[263585,335029],[0,0]],[[263585,335029],[-124,-484]],[[263461,334545],[-99,4]],[[263362,334549],[-9,1250],[-281,765]],[[263072,336564],[-161,1342]],[[264355,339246],[-770,-4217]],[[267164,335882],[3,-378]],[[267167,335504],[-680,-2475],[-459,115]],[[266028,333144],[-109,2607],[-257,3312]],[[276103,339687],[-56,-7726]],[[276047,331961],[-323,-24]],[[275724,331937],[-70,1389],[-359,2653],[-343,441]],[[222251,337668],[32,-5648]],[[222283,332020],[-589,1],[-1,-1624],[-1183,13]],[[220510,330410],[0,3663]],[[200145,328230],[-3228,28],[-2,-6474],[-1606,14]],[[204574,338763],[16,-15442]],[[204590,323321],[-1474,-28]],[[203116,323293],[-4,3258]],[[203103,336617],[208,1297],[-208,256]],[[203103,338482],[8,1396]],[[249362,339384],[-275,31],[115,-1382],[-335,-1404],[-366,-377],[270,-1926],[-281,-846],[135,-918]],[[248625,332562],[-364,175],[-4,-2806]],[[248257,329931],[-36,-136]],[[248221,329795],[-36,1334],[-212,-483]],[[247973,330646],[-400,66]],[[247573,330712],[0,4866]],[[247573,335578],[-6,5520]],[[272433,336843],[-475,-3705]],[[271958,333138],[-10,-82]],[[271948,333056],[-430,281]],[[271518,333337],[-367,-86]],[[271151,333251],[-126,2692],[-326,1639],[222,1422],[-102,1407]],[[251392,337392],[-291,-2345],[-136,397]],[[250965,335444],[-553,1154],[-246,1169]],[[250166,337767],[38,1181]],[[247573,335578],[-2090,161]],[[245483,335739],[13,3286]],[[286926,335502],[-145,-1822],[-233,206],[79,1506],[-351,140]],[[286276,335532],[78,4358]],[[286354,339890],[552,1171],[303,-80],[50,-5635],[-333,156]],[[256131,338384],[-53,-2345],[-292,-1221]],[[255786,334818],[-336,-819],[-354,711],[-199,-818]],[[254897,333892],[-239,1016],[76,3254]],[[286276,335532],[-676,145]],[[285600,335677],[-577,4]],[[285419,339739],[847,1101],[88,-950]],[[259837,338179],[-278,-1956],[-1,-1386]],[[259558,334837],[-289,-248],[-329,1061]],[[271151,333251],[-416,-936],[-546,61]],[[270189,332376],[-338,1183],[43,2103]],[[250166,337767],[-127,96],[-281,-4503]],[[249758,333360],[-308,1168],[-405,125],[-420,-2091]],[[229927,334444],[-12,-3282]],[[229915,331162],[-1439,23]],[[228476,331185],[1,4846]],[[245033,339117],[-93,-1439],[-340,-1930],[-43,-3401],[-592,76]],[[243965,332423],[-591,68]],[[243374,332491],[12,3307],[-132,-127]],[[277508,338250],[-37,-6114]],[[277471,332136],[-595,-67]],[[276876,332069],[-829,-108]],[[263633,333862],[1,202]],[[263634,334064],[-1,-202]],[[264626,337536],[88,-2616],[-150,-1006]],[[264564,333914],[-285,822],[-464,206],[-191,-774]],[[263624,334168],[-163,377]],[[245483,335739],[-6,-4945]],[[245477,330794],[-2,-1650]],[[245475,329144],[-590,62],[-3,1642],[-263,-126]],[[244619,330722],[-662,63],[8,1638]],[[266028,333144],[-304,-143],[-504,-1878]],[[265220,331123],[-22,59]],[[265198,331182],[-124,1693],[-214,-288],[-296,1327]],[[279260,333397],[-157,-1140]],[[279103,332257],[-411,1984],[-648,-2053]],[[278044,332188],[-573,-52]],[[210378,329803],[0,-2771],[-614,432],[-745,-2269]],[[209019,325195],[-2,1368],[-3247,12],[1,-3244]],[[205771,323331],[-1181,-10]],[[282748,335160],[-108,-470]],[[282640,334690],[-343,-1480]],[[282297,333210],[-672,237]],[[281625,333447],[-169,331],[-186,2366]],[[275724,331937],[-313,-26]],[[275411,331911],[-1231,84]],[[232307,338491],[0,-2432],[148,-23],[1,-3216]],[[232456,332820],[-305,-2],[-149,-3257],[-294,2]],[[231708,329563],[-296,1],[-1,3262],[-292,-4],[0,1623]],[[234087,334442],[-14,-2089],[230,-1262]],[[234303,331091],[-538,-3686],[-283,599]],[[233482,328004],[-1,4815],[-1025,1]],[[260623,337347],[23,-3296],[103,-1581]],[[260749,332470],[-138,-25]],[[260611,332445],[-752,-140]],[[259859,332305],[-301,2532]],[[257338,335707],[-156,-2904],[-342,-2492]],[[256840,330311],[-446,715],[-239,-414]],[[256155,330612],[-245,215]],[[255910,330827],[24,1814],[-148,2177]],[[254026,337733],[-164,-1607],[-17,-2092],[167,-1717],[-89,-1887]],[[253923,330430],[-47,-579]],[[253876,329851],[-478,-216],[-127,792]],[[253271,330427],[-4,1966],[147,1494],[31,3866]],[[254897,333892],[-166,-2346]],[[254731,331546],[-712,-436],[-96,-680]],[[284107,336197],[0,-1816],[235,-1426]],[[284342,332955],[-25,-149]],[[284317,332806],[-216,-595],[-40,-1864]],[[284061,330347],[-198,-1249],[-361,-216]],[[283502,328882],[-237,1612]],[[283265,330494],[-76,1698],[-549,2498]],[[273559,332749],[-1601,389]],[[270189,332376],[-269,-1122]],[[269920,331254],[-1335,-829]],[[268585,330425],[-153,883],[95,1791],[-330,2070]],[[263072,336564],[-195,-3681],[-172,-21],[-175,-2136],[-273,-559]],[[262257,330167],[-331,919]],[[261926,331086],[77,2096],[533,3545]],[[261595,336801],[-473,-3690]],[[261122,333111],[-373,-641]],[[253271,330427],[-332,-80]],[[252939,330347],[-8,1375],[-394,358],[-302,1418]],[[252235,333498],[19,3745]],[[250965,335444],[-26,-4865]],[[250939,330579],[-293,-638]],[[250646,329941],[-809,110]],[[249837,330051],[-79,3309]],[[281625,333447],[-251,-3124],[4,-1252],[-421,-1196]],[[280957,327875],[-522,-413],[-138,955]],[[280297,328417],[-332,3369],[-134,425]],[[224348,332817],[-1,-1627]],[[224347,331190],[-2064,20]],[[222283,331210],[0,810]],[[179740,322568],[3,-2451],[444,-2895],[104,-2181],[266,-2705],[564,-2760]],[[181121,309576],[-326,-2417],[-494,-1592]],[[180301,305567],[-2460,-124],[1,-805],[-4499,-63],[2,-553],[-829,-8],[-928,527],[-315,-3026]],[[171273,301515],[-312,1400]],[[170961,302915],[145,1407],[238,4959],[-64,9968]],[[171280,319249],[98,4],[-3,18110]],[[164248,337232],[-2,-3233],[300,-23],[0,-1605],[244,-1640],[325,-34],[5,-1631],[193,12],[3,-1614],[397,-45],[3,-1614],[314,78],[-22,-1691],[246,-211],[0,-3299]],[[166254,320682],[-755,1375],[-936,2625],[-284,-1411],[-421,-724],[101,-1703],[-443,1647],[-537,-409]],[[162979,322082],[-5,3222],[-296,222],[-393,1772],[197,1534],[-165,1791],[-326,509],[-445,3198],[-340,589],[-168,2432]],[[171280,319249],[-3410,-90],[36,-551]],[[167906,318608],[-267,446],[-750,39],[-88,1202],[-462,376]],[[166339,320671],[-85,11]],[[252235,333498],[-176,-1852],[-459,-1134]],[[251600,330512],[-661,67]],[[242130,335806],[-34,-6487]],[[242096,329319],[-637,87]],[[241459,329406],[3,1623],[-1032,137]],[[240430,331166],[104,1602],[10,3228]],[[268585,330425],[-242,-1949],[-248,-495]],[[268095,327981],[-738,4146]],[[267357,332127],[82,739],[-272,2638]],[[238036,329533],[-270,1020],[-9,-2177],[-298,111],[-9,-1668],[-302,-291]],[[237148,326528],[-149,21],[20,4291]],[[237019,330840],[145,2397],[-141,1639],[249,-27],[204,1843]],[[238075,336857],[-39,-7324]],[[261926,331086],[-253,-1943]],[[261673,329143],[-493,2571],[-58,1397]],[[239556,336116],[-9,-2441],[-149,23],[-7,-2427],[-201,25],[-9,-2653]],[[239181,328643],[-294,242],[-94,1575],[-757,-927]],[[237019,330840],[-164,-1085],[-525,-532],[-176,1675],[-128,-957]],[[236026,329941],[-115,4494]],[[263362,334549],[-670,-5484]],[[262692,329065],[-240,-1164]],[[262452,327901],[-195,2266]],[[212037,334134],[-2,-8177]],[[212035,325957],[-1,-4264]],[[212034,321693],[-669,4],[-3,-1665],[-294,7],[-2,-1619],[-293,8],[-1,-1623],[-585,17],[-91,-1634]],[[210096,315188],[-586,7]],[[209510,315195],[-502,3],[-1,3241]],[[209007,318439],[12,6756]],[[284317,332806],[371,-2176],[955,-1729],[26,-1207]],[[285669,327694],[-54,-767],[-593,-357],[-140,698]],[[284882,327268],[-821,3079]],[[285600,335677],[126,-1604],[273,-883]],[[285999,333190],[-398,-1100],[175,-2296],[-1124,1767],[-310,1394]],[[240430,331166],[-12,-5402],[-114,-39]],[[240304,325725],[-549,-592],[-186,1872],[-330,1452]],[[239239,328457],[-58,186]],[[226992,336063],[8,-6492]],[[227000,329571],[-1,-750]],[[226999,328821],[0,-19]],[[226999,328802],[-475,-44],[-290,766],[-418,56]],[[225816,329580],[-586,-9],[-19,3244]],[[228476,331185],[-1,-1622]],[[228475,329563],[-1475,8]],[[243374,332491],[-161,-3273],[-723,82]],[[242490,329300],[-394,19]],[[257735,335317],[-73,-4276],[185,-1699]],[[257847,329342],[-411,-2087],[-227,201]],[[257209,327456],[-369,2855]],[[247573,330712],[-600,59]],[[246973,330771],[-1496,23]],[[288074,326038],[-5,98]],[[286926,335502],[415,-497]],[[287690,334443],[20,-1175],[-419,-3066],[-333,-1394],[-288,214],[-380,1513],[-221,-1048],[-325,2540],[265,-142],[-10,1305]],[[259859,332305],[-102,-4380]],[[259757,327925],[-680,1377],[-289,-544]],[[258788,328758],[11,1469]],[[258799,330227],[35,4085]],[[258799,330227],[-519,-1708],[-227,633]],[[258053,329152],[-206,190]],[[267357,332127],[-461,-864],[33,-2552]],[[266929,328711],[-948,-959]],[[265981,327752],[263,2891],[-1024,480]],[[265198,331182],[-169,-1111],[-21,-2086]],[[265008,327985],[-272,-947],[-243,467],[-219,-1110]],[[264274,326395],[-566,1467]],[[263708,327862],[264,836],[-23,1266],[-316,3898]],[[263634,334064],[-10,104]],[[255910,330827],[-429,244],[-353,-1106]],[[255128,329965],[-397,1581]],[[283265,330494],[-572,-1459],[-346,398]],[[282347,329433],[-50,3777]],[[248221,329795],[-58,-96]],[[248163,329699],[-190,947]],[[249837,330051],[-442,-508]],[[249395,329543],[-1138,388]],[[263708,327862],[-580,-827]],[[263128,327035],[-436,2030]],[[236026,329941],[-3,-304]],[[236023,329637],[-176,-1477],[-443,-194],[-252,1213],[-191,-612]],[[234961,328567],[-77,886],[-581,1638]],[[231708,329563],[0,-1618]],[[231708,327945],[-1282,3]],[[230426,327948],[0,3298],[-261,-1413],[-247,175]],[[229918,330008],[-3,1154]],[[279103,332257],[-595,-3991]],[[278508,328266],[-251,856],[-213,3066]],[[214486,334091],[-13,-8132]],[[214473,325959],[-2438,-2]],[[217486,334073],[-1,-8135]],[[217485,325938],[-1495,19]],[[215990,325957],[0,8137]],[[219007,334077],[3,-8124]],[[219010,325953],[-1525,-15]],[[220510,330410],[0,-4461]],[[220510,325949],[-1500,4]],[[215990,325957],[-1517,2]],[[271518,333337],[-29,-4139],[-182,-3290]],[[271307,325908],[-296,67]],[[271011,325975],[-264,59]],[[270747,326034],[1,1307],[-511,2796],[-304,-278]],[[269933,329859],[-13,1395]],[[282347,329433],[-80,-3574]],[[282267,325859],[-165,-563],[-420,838],[-332,-61]],[[281350,326073],[-393,1802]],[[252939,330347],[-5,-701]],[[252934,329646],[-672,-91],[-323,-2399],[-173,1]],[[251766,327157],[-166,3355]],[[280297,328417],[-211,-1294]],[[280086,327123],[-581,255],[-754,-1251]],[[278751,326127],[-353,1413],[110,726]],[[271948,333056],[227,-2696]],[[272175,330360],[382,-2967],[-24,-1802]],[[272533,325591],[-107,26]],[[272426,325617],[-1119,291]],[[273595,331632],[-24,-1620]],[[273571,330012],[-1396,348]],[[261673,329143],[-449,-3852]],[[261224,325291],[-188,2237],[-289,964]],[[260747,328492],[203,1622],[-339,2331]],[[233482,328004],[-312,226]],[[233170,328230],[-399,-1549],[-1070,-1312]],[[231701,325369],[7,2576]],[[225816,329580],[10,-9733]],[[225826,319847],[-1469,0]],[[224357,319847],[-4,4513]],[[224353,324360],[-6,6830]],[[244619,330722],[-212,-1147],[105,-2459],[-222,-2903]],[[244290,324213],[-331,36],[-3,-1236],[-368,689],[-238,-588]],[[243350,323114],[-586,854],[-297,-184]],[[242467,323784],[23,5516]],[[260747,328492],[-421,-561],[-85,-1375],[-371,150]],[[259870,326706],[-113,1219]],[[188318,312722],[-291,-1597],[-147,-4378],[653,-3],[-47,-2817]],[[188486,303927],[-642,1],[-1224,898],[-311,-3086],[-1306,2189],[-1641,-13]],[[183362,303916],[1,5933]],[[183363,309849],[-2,22529]],[[268095,327981],[-175,-2988],[-203,-1445],[134,-484]],[[267851,323064],[-277,-501]],[[267574,322563],[-1,-3]],[[267573,322560],[-644,6151]],[[274045,331971],[332,-4442],[324,-1103]],[[274701,326426],[-806,-3867]],[[273895,322559],[-185,1400]],[[273710,323959],[-377,-575],[22,1921]],[[273355,325305],[216,4707]],[[278751,326127],[-5,-339]],[[278746,325788],[-367,682],[-294,-913],[-345,-2208]],[[277740,323349],[-312,426],[-120,1819]],[[277308,325594],[-233,1635],[-199,4840]],[[277308,325594],[-662,369],[-623,-752]],[[276023,325211],[-58,1791],[126,2469],[-367,2466]],[[275411,331911],[-586,-5937]],[[274825,325974],[-124,452]],[[222283,331210],[11,-6489]],[[222294,324721],[-137,-42]],[[222157,324679],[-1040,43],[-1,-1614],[-294,3]],[[220822,323111],[-312,20]],[[220510,323131],[0,2818]],[[276023,325211],[-562,988]],[[275461,326199],[-228,-565],[-408,340]],[[269933,329859],[-238,-2103],[-19,-1656]],[[269676,326100],[-619,-853]],[[269057,325247],[-122,2959],[-350,2219]],[[255128,329965],[-90,-7353]],[[255038,322612],[-1051,46]],[[253987,322658],[9,5394],[-120,1799]],[[230426,327948],[0,-3245],[-137,1],[-1,-3821]],[[230288,320883],[-666,-983],[-127,785]],[[229495,320685],[-3,9491],[426,-168]],[[265981,327752],[-64,-577]],[[265917,327175],[-396,128],[-315,-711],[-198,1393]],[[224353,324360],[-360,353],[-1699,8]],[[229495,320685],[-432,1199]],[[229063,321884],[-238,-1061],[-352,454]],[[228473,321277],[2,8286]],[[241459,329406],[-204,-4750]],[[241255,324656],[-448,-779],[-906,103]],[[239901,323980],[403,1745]],[[256155,330612],[-49,-8076]],[[256106,322536],[-1068,76]],[[234961,328567],[-3,-2253],[-315,-6],[0,-2676]],[[234643,323632],[-1174,5]],[[233469,323637],[0,1895],[-295,3],[-4,2695]],[[262452,327901],[-222,-2978],[129,-2605]],[[262359,322318],[-796,-48]],[[261563,322270],[-276,-32]],[[261287,322238],[-305,-7]],[[260982,322231],[247,1083],[-5,1977]],[[257209,327456],[-20,-5059]],[[257189,322397],[-1046,132]],[[256143,322529],[-37,7]],[[237148,326528],[-7,-1358],[-305,-767]],[[236836,324403],[-229,-383],[-21,-2058],[-606,-644]],[[235980,321318],[43,8319]],[[246973,330771],[-2,-5539]],[[246971,325232],[-1504,74]],[[245467,325306],[8,3838]],[[245467,325306],[-7,-2742],[-291,30]],[[245169,322594],[-399,-324],[-210,-1347]],[[244560,320923],[13,2577],[-283,713]],[[248163,329699],[-214,-2386],[219,-628],[20,-1533],[-293,-394],[-116,-1726],[-268,-565]],[[247511,322467],[186,-1297],[-172,-1230]],[[247525,319940],[-289,-502]],[[247236,319438],[-2,1325]],[[247234,320763],[22,4543],[-285,-74]],[[251766,327157],[-13,-4698]],[[251753,322459],[-101,0]],[[251652,322459],[-541,-5]],[[251111,322454],[-505,-9]],[[250606,322445],[40,7496]],[[283502,328882],[-231,-2074]],[[283271,326808],[-356,-2934],[-358,-1180]],[[282557,322694],[-108,2561],[-182,604]],[[239239,328457],[-5,-1942],[-493,50],[-203,-1310],[-493,-195],[5,-2145]],[[238050,322915],[-1029,135],[-185,1353]],[[269057,325247],[-524,-1429]],[[268533,323818],[-371,-223]],[[268162,323595],[-311,-531]],[[253987,322658],[-600,-192]],[[253387,322466],[-453,-3]],[[252934,322463],[-50,3]],[[252884,322466],[50,7180]],[[273355,325305],[-822,286]],[[285469,320638],[-203,-947],[-926,-817]],[[284340,318874],[-64,5013]],[[284276,323887],[204,503],[398,-2748],[591,-1004]],[[284882,327268],[-235,-2199],[118,-897]],[[284765,324172],[-259,864],[-282,-1162]],[[284224,323874],[-953,2934]],[[258788,328758],[-158,-3843]],[[258630,324915],[-206,2015],[-376,1415],[5,807]],[[249395,329543],[-32,-7083]],[[249363,322460],[-223,-9]],[[249140,322451],[-1629,16]],[[270747,326034],[-688,163]],[[270059,326197],[-383,-97]],[[250606,322445],[-430,-12]],[[250176,322433],[-813,27]],[[252884,322466],[-1131,-7]],[[235980,321318],[-18,-3816]],[[235962,317502],[-19,-4124]],[[235943,313378],[-1327,-11]],[[234616,313367],[-1,3239],[-339,-2]],[[234276,316604],[0,3244],[171,1607],[196,17],[0,2160]],[[226999,328802],[9,-8951]],[[227008,319851],[-1,-3236]],[[227007,316615],[-1172,1]],[[225835,316616],[-9,3231]],[[228473,321277],[-555,610],[-74,2247],[-845,4687]],[[258630,324915],[-1,-2532]],[[258629,322383],[-1296,15]],[[257333,322398],[-144,-1]],[[242467,323784],[-5,-1009]],[[242462,322775],[-296,42],[-6,-1618],[-876,479]],[[241284,321678],[-150,871],[121,2107]],[[259870,326706],[26,-4377]],[[259896,322329],[-1243,53]],[[258653,322382],[-24,1]],[[263128,327035],[11,-1304],[-212,-3409]],[[262927,322322],[-101,-5]],[[262826,322317],[-467,1]],[[200462,326631],[361,-4880]],[[200823,321751],[-379,16],[-5,-7061]],[[200439,314706],[-1450,-14]],[[198989,314692],[-3681,23]],[[229063,321884],[-6,-2046]],[[229057,319838],[-2049,13]],[[267573,322560],[-1043,-136]],[[266530,322424],[-489,3117],[-223,-90]],[[265818,325451],[99,1724]],[[285669,327694],[310,747],[30,-1789],[-452,-994],[295,-347],[-138,-1387],[-575,-2025],[-396,1478],[22,795]],[[260982,322231],[-365,31]],[[260617,322262],[-721,67]],[[281350,326073],[46,-5342],[-149,-3077],[243,-283]],[[281490,317371],[-394,-3133]],[[281096,314238],[-204,2185],[-465,3448]],[[280427,319871],[-419,2517],[-71,1883],[149,2852]],[[239901,323980],[-690,-4023],[1,-1632]],[[239212,318325],[-296,-555]],[[238916,317770],[-881,36]],[[238035,317806],[15,5109]],[[233469,323637],[-441,-557],[-20,-6469]],[[233008,316611],[1,-1619],[-439,-6]],[[232570,314986],[-586,-6],[0,1619],[-585,2]],[[231399,316601],[1,1620]],[[231400,318221],[8,5253],[293,1895]],[[265818,325451],[-331,-348],[-413,-2793]],[[265074,322310],[-349,6]],[[264725,322316],[-533,16]],[[264192,322332],[82,4063]],[[231400,318221],[-877,0]],[[230523,318221],[1,2621],[-236,41]],[[264192,322332],[-835,-2]],[[263357,322330],[-430,-8]],[[280427,319871],[-1132,-400]],[[279295,319471],[-372,2212]],[[278923,321683],[-155,1648],[-22,2457]],[[284224,323874],[52,13]],[[284340,318874],[-211,-330]],[[284129,318544],[-198,2127],[-360,239],[-380,1073],[-481,70]],[[282710,322053],[-153,641]],[[203116,323293],[0,-1615],[261,2],[1,-1549],[-728,1]],[[202650,320132],[-59,615],[-705,54],[-101,-673],[-844,4],[-118,1619]],[[209007,318439],[-587,-6],[1,-1615],[-293,-11],[-4,-1617],[-1242,-7],[0,-4792]],[[206882,310391],[-1173,-5]],[[205709,310386],[1,4846],[62,-6],[-1,8105]],[[270059,326197],[-28,-6431],[221,-1212]],[[270252,318554],[-254,-3696],[-212,-1916]],[[269786,312942],[-390,2635],[-91,3609]],[[269305,319186],[-142,3644],[-154,854],[-476,134]],[[276023,325211],[227,-1073],[186,598],[147,-1356],[71,-2680],[-218,-1752]],[[276436,318948],[-6,0]],[[276430,318948],[-1089,137]],[[275341,319085],[120,7114]],[[278923,321683],[-437,-2238],[-436,97]],[[278050,319542],[-14,1963],[-296,1844]],[[275341,319085],[-679,65]],[[274662,319150],[-651,43],[43,2131],[-159,1235]],[[271464,320921],[-199,-1405],[-198,-4516]],[[271067,315000],[-604,2049],[-211,1505]],[[271011,325975],[333,-4681],[120,-373]],[[282710,322053],[66,-1014],[-81,-3692]],[[282695,317347],[-1205,24]],[[214473,325959],[-4,-8114]],[[214469,317845],[-993,-9]],[[213476,317836],[-1442,8]],[[212034,317844],[0,3849]],[[272173,319555],[-709,1366]],[[272426,325617],[-120,-2206],[-216,-215],[83,-3641]],[[219010,325953],[-5,-8102]],[[219005,317851],[-1126,10]],[[217879,317861],[-406,0]],[[217473,317861],[12,8077]],[[220510,323131],[0,-5304]],[[220510,317827],[-1157,20]],[[219353,317847],[-348,4]],[[215990,325957],[-18,-8107]],[[215972,317850],[-1028,10]],[[214944,317860],[-475,-15]],[[277740,323349],[-316,-946],[31,-1526],[-351,-1962]],[[277104,318915],[-668,33]],[[217473,317861],[-1061,-15]],[[216412,317846],[-440,4]],[[273710,323959],[112,-2797],[-91,-1952]],[[273731,319210],[-1617,15]],[[272114,319225],[59,330]],[[266530,322424],[-186,-15]],[[266344,322409],[-1079,-93]],[[265265,322316],[-191,-6]],[[247234,320763],[-1924,142],[-2,-829]],[[245308,320076],[-139,2518]],[[224357,319847],[-575,10],[0,-4860]],[[223782,314997],[-488,-1],[-284,811]],[[223010,315807],[139,1188],[-267,11],[14,2697],[-285,-467]],[[222611,319236],[-167,661],[-83,3112],[-204,1670]],[[284158,318179],[-29,365]],[[285469,320638],[29,1356],[695,560],[-107,-1081],[521,322],[-484,-2668],[-165,-1717],[-182,-43],[-131,1683],[-204,-1777],[-551,162],[-575,-954],[-157,1698]],[[222611,319236],[-441,-169],[-51,-1639],[-679,-8]],[[221440,317420],[-149,817],[-33,3269],[-436,-2],[0,1607]],[[164640,304627],[231,-1774],[-418,-878],[-322,2121],[509,531]],[[165055,305328],[975,-1440],[-461,-704],[-359,132],[-155,2012]],[[166339,320671],[-2,-7969],[-90,-1806]],[[166247,310896],[-249,759],[-867,-159],[-358,995],[-804,201],[-490,-415],[-116,1396],[-372,1055],[123,2396],[-24,2470],[-170,1150],[59,1338]],[[241284,321678],[-584,-794],[39,-1065]],[[240739,319819],[-931,87],[-3,-1605]],[[239805,318301],[-593,24]],[[238035,317806],[-613,-1484]],[[237422,316322],[-245,500],[-834,92],[-381,588]],[[244560,320923],[-20,-4593],[-199,26],[193,-1935]],[[244534,314421],[-604,81],[-3,-1622],[-230,23]],[[243697,312903],[96,2891],[-159,661],[20,4598],[-307,35],[3,2026]],[[269305,319186],[-840,-2741],[-145,-912]],[[268320,315533],[-168,2206],[10,5856]],[[243697,312903],[-75,13]],[[243622,312916],[-905,125]],[[242717,313041],[1,2458],[-234,29],[98,2072],[20,5167],[-140,8]],[[274662,319150],[434,-3775]],[[275096,315375],[-397,-1018],[74,-879],[-605,-586],[-6,1090],[-378,-1531]],[[273784,312451],[4,1591]],[[273788,314042],[49,2842],[-106,2326]],[[234276,316604],[-1268,7]],[[268320,315533],[-415,-2629]],[[267905,312904],[-174,218]],[[267731,313122],[-145,796]],[[267586,313918],[-653,2805]],[[266933,316723],[-40,540]],[[266893,317263],[305,3008],[323,1133],[53,1159]],[[205709,310386],[-1,-1614],[-1701,0]],[[204007,308772],[-1371,-6],[-3,3352]],[[202633,312118],[-147,1515],[16,6497],[148,2]],[[278050,319542],[43,-1241],[-360,-2634]],[[277733,315667],[-629,3248]],[[221440,317420],[1,-4034],[-493,-32]],[[220948,313354],[-223,1274],[-207,-261]],[[220518,314367],[-8,3460]],[[242717,313041],[-496,63]],[[242221,313104],[-106,14]],[[242115,313118],[5,1627],[-291,31],[-264,1666],[-303,324],[6,1345],[-291,53],[9,1622],[-247,33]],[[256004,318054],[-462,771]],[[255542,318825],[-243,556],[-714,-1852],[-404,1290],[-162,1542],[-347,180]],[[253672,320541],[-285,1925]],[[256143,322529],[-11,-3407],[-128,-1068]],[[245308,320076],[-7,-2463],[141,-1661]],[[245442,315952],[-8,-3255],[151,-26],[-6,-2480],[-150,6]],[[245429,310197],[-103,135],[-579,3570],[-213,519]],[[266893,317263],[-555,643],[-280,1346]],[[266058,319252],[-12,1037],[298,2120]],[[257333,322398],[-17,-8213]],[[257316,314185],[-332,545],[-546,1973]],[[256438,316703],[-434,1351]],[[252925,317998],[-982,22]],[[251943,318020],[-96,1898],[-197,-2],[2,2543]],[[252934,322463],[-9,-4465]],[[253555,314761],[-46,-2207]],[[253509,312554],[-482,7]],[[253027,312561],[-4,5160],[-98,277]],[[253672,320541],[-117,-5780]],[[251943,318020],[-47,-2985]],[[251896,315035],[-984,33]],[[250912,315068],[1,4041],[197,808],[1,2537]],[[249142,318305],[-519,9],[-331,-1193],[-263,918],[-215,-624]],[[247814,317415],[-5,2533],[-284,-8]],[[249140,322451],[2,-4146]],[[250912,315068],[-438,-279]],[[250474,314789],[-147,-6],[-2,4868],[-149,0],[0,2782]],[[250474,314789],[-1,-1631]],[[250473,313158],[-643,1090],[-534,3]],[[249296,314251],[-2,2447],[-152,1607]],[[266058,319252],[-82,-420]],[[265976,318832],[-275,-140]],[[265701,318692],[-106,2241],[-330,1383]],[[258653,322382],[-137,-6674],[89,-612]],[[258605,315096],[-29,-1678],[-447,-519],[-145,1195]],[[257984,314094],[-428,804],[-240,-713]],[[260678,319948],[-562,-4378],[-610,-2702],[-150,-76]],[[259356,312792],[-223,839],[-30,1452],[-498,13]],[[260617,322262],[61,-2314]],[[263365,319856],[101,-595],[-208,-1794],[7,-2679]],[[263265,314788],[-172,734],[-550,214]],[[262543,315736],[264,4518],[19,2063]],[[263357,322330],[8,-2474]],[[264725,322316],[-135,-684],[239,-2782],[-180,-2852]],[[264649,315998],[-86,-842]],[[264563,315156],[-22,278]],[[264541,315434],[-329,3527],[-293,881],[-554,14]],[[262543,315736],[-381,-217]],[[262162,315519],[-1,1815],[-262,888]],[[261899,318222],[232,965],[228,3131]],[[261899,318222],[-334,1613],[-2,2435]],[[265701,318692],[-212,-1318]],[[265489,317374],[-231,350],[-271,-1838],[-338,112]],[[284158,318179],[119,-1091],[-640,-3358],[-489,-1594]],[[283148,312136],[-159,657],[-294,4554]],[[261287,322238],[-239,-2821],[0,-1352],[-233,-2520]],[[260815,315545],[-137,4403]],[[262162,315519],[-52,-657]],[[262110,314862],[-107,4]],[[262003,314866],[-1169,24]],[[260834,314890],[-19,655]],[[230523,318221],[0,-1628],[-294,-3],[-5,-3251]],[[230224,313339],[-873,12]],[[229351,313351],[0,1623],[-293,811]],[[229058,315785],[-1,4053]],[[202633,312118],[-979,1703],[-317,896],[-898,-11]],[[279295,319471],[266,-2709],[-183,-3842]],[[279378,312920],[-224,-635],[-333,-2783]],[[278821,309502],[-4,24]],[[278817,309526],[-1052,5966]],[[277765,315492],[-32,175]],[[212034,317844],[-3,-8091]],[[212031,309753],[0,-189]],[[212031,309564],[-1940,9],[5,5615]],[[272114,319225],[155,-4645]],[[272269,314580],[-1,-1446]],[[272268,313134],[-327,-920],[-286,1648]],[[271655,313862],[-588,1138]],[[247236,319438],[-213,564],[88,-2171],[-320,-435],[282,-923],[-325,-596]],[[246748,315877],[-1306,75]],[[255542,318825],[30,-628],[-317,-3711]],[[255255,314486],[-1700,275]],[[166087,290612],[275,-966],[-300,-16],[25,982]],[[167726,304768],[-743,1873],[-183,2255],[-553,2000]],[[167906,318608],[691,-9433],[-103,-2110],[-329,6],[-439,-2303]],[[247814,317415],[-121,-2322],[126,-841]],[[247819,314252],[0,-806]],[[247819,313446],[-294,-28],[6,-1584],[-272,-13]],[[247259,311821],[-473,1881]],[[246786,313702],[-38,2175]],[[260834,314890],[37,-1199]],[[260871,313691],[-340,-2930],[-579,-3106]],[[259952,307655],[-731,14]],[[259221,307669],[-12,4226],[147,897]],[[281096,314238],[217,-1643],[-222,-1224]],[[281091,311371],[-528,-426],[-618,1727],[-567,248]],[[223010,315807],[-167,-929],[-209,-3035],[76,-1642]],[[222710,310201],[-463,2267],[-58,-1528],[-215,361]],[[221974,311301],[-292,391],[-321,-718],[-413,2380]],[[242115,313118],[-438,43],[-4,-817],[-514,69],[-222,-771]],[[240937,311642],[-45,1095],[-302,569]],[[240590,313306],[-41,1616],[-479,872],[30,2514],[-295,-7]],[[225835,316616],[-146,-4],[-1,-3243]],[[225688,313369],[-284,3],[-145,-1082],[-582,7],[-438,-809]],[[224239,311488],[-1,1890],[-456,-2],[0,1621]],[[229058,315785],[-744,-261],[154,-2163],[-579,4]],[[227889,313365],[-586,3]],[[227303,313368],[-1,3244],[-295,3]],[[264541,315434],[-415,-1027]],[[264126,314407],[-860,-257]],[[263266,314150],[-1,638]],[[266933,316723],[-339,-3833]],[[266594,312890],[-433,-921]],[[266161,311969],[-142,1338]],[[266019,313307],[139,814],[-135,1989],[115,1470],[-162,1252]],[[168718,285870],[613,-3552],[-361,241],[-252,3311]],[[168676,294217],[663,-1336],[118,-1995],[-366,419],[-148,2046],[-267,866]],[[170961,302915],[-533,9],[-383,-3729]],[[170045,299195],[-299,468],[-188,-1225],[-342,666],[55,1824],[-185,2349],[-237,1371],[-531,-107],[-202,-603],[-390,830]],[[273788,314042],[-1519,538]],[[269786,312942],[-1189,-5125]],[[268597,307817],[-93,1503]],[[268504,309320],[-251,3127],[-348,457]],[[276707,313833],[-152,-754],[-450,18],[-676,-2348]],[[275429,310749],[-117,2670],[-216,1956]],[[276430,318948],[164,-2140],[207,-808],[-94,-2167]],[[277765,315492],[-510,-6014]],[[277255,309478],[-68,132]],[[277187,309610],[-190,3520],[-290,703]],[[256438,316703],[-14,-6930]],[[256424,309773],[0,-270]],[[256424,309503],[-1169,98]],[[255255,309601],[0,4885]],[[266019,313307],[-494,36]],[[265525,313343],[-95,2291],[59,1740]],[[271655,313862],[-387,-3128],[-316,-1220],[-138,-1801]],[[270814,307713],[-619,2360],[-220,1485]],[[269975,311558],[-189,1384]],[[209510,315195],[8,-9723]],[[209518,305472],[-585,10],[-1,-1637],[-1755,87],[0,1633],[-298,10]],[[206879,305575],[3,4816]],[[249296,314251],[-147,-1]],[[249149,314250],[-1330,2]],[[240590,313306],[-339,47],[-92,-2176],[-1279,142]],[[238880,311319],[36,6451]],[[231399,316601],[1,-3247],[-148,-1629],[-731,-2]],[[230521,311723],[-297,20],[0,1596]],[[253027,312561],[-593,32]],[[252434,312593],[1,817],[-542,-5]],[[251893,313405],[3,1630]],[[216412,317846],[0,-8101]],[[216412,309745],[-260,3]],[[216152,309748],[-1207,11]],[[214945,309759],[-1,8101]],[[217879,317861],[-4,-8108]],[[217875,309753],[-265,-6]],[[217610,309747],[-1198,-2]],[[214945,309759],[-258,1]],[[214687,309760],[-1212,-2]],[[213475,309758],[1,8078]],[[219353,317847],[-5,-8079]],[[219348,309768],[-278,10]],[[219070,309778],[-1195,-25]],[[220518,314367],[0,-4631]],[[220518,309736],[-1170,32]],[[238880,311319],[-3,-1067]],[[238877,310252],[-196,24]],[[238681,310276],[-1273,172]],[[237408,310448],[14,5874]],[[213475,309758],[-250,-2]],[[213225,309756],[-1194,-3]],[[265525,313343],[-379,-1617]],[[265146,311726],[-589,2243],[6,1187]],[[282602,309349],[-10,65]],[[282592,309414],[10,-65]],[[283148,312136],[-617,-2746]],[[282531,309390],[-232,1787],[-388,-117],[-188,-953]],[[281723,310107],[-370,469]],[[281353,310576],[-262,795]],[[237408,310448],[-890,101],[31,-3035]],[[236549,307514],[-631,-49]],[[235918,307465],[25,5913]],[[257984,314094],[-89,-4491]],[[257895,309603],[-1471,170]],[[267586,313918],[-206,-1092],[-615,-311]],[[266765,312515],[-171,375]],[[227303,313368],[-1,-4054]],[[227302,309314],[-1606,11]],[[225696,309325],[-8,4044]],[[232570,314986],[1,-1620],[-301,-10],[2,-6508]],[[232272,306848],[-593,2]],[[231679,306850],[-1159,9]],[[230520,306859],[1,4864]],[[234616,313367],[-304,7],[0,-4907],[-291,4],[-15,-1618]],[[234006,306853],[-1734,-5]],[[246786,313702],[-19,-2055],[-497,-863],[38,-1147],[-293,-1723],[-253,398],[310,-1738],[-363,-407]],[[245709,306167],[2,-24]],[[245711,306143],[-453,5]],[[245258,306148],[93,410],[78,3639]],[[229351,313351],[-149,-2],[-1,-3219],[-146,-6]],[[229055,310124],[-301,798],[-865,13],[0,2430]],[[224239,311488],[147,-1345],[-2,-3288]],[[224384,306855],[-433,-385],[-521,1416]],[[223430,307886],[-657,27],[-63,2288]],[[263266,314150],[3,-2538]],[[263269,311612],[-982,-375]],[[262287,311237],[-273,215],[96,3410]],[[278817,309526],[-155,-873]],[[278662,308653],[-702,873],[-466,-1309]],[[277494,308217],[-239,1261]],[[265146,311726],[62,-1577]],[[265208,310149],[-838,21]],[[264370,310170],[1,855]],[[264371,311025],[-245,3382]],[[275429,310749],[0,-38]],[[275429,310711],[-371,-1301],[-162,-2339]],[[274896,307071],[-379,-1286]],[[274517,305785],[-288,-570],[-418,2121],[121,1598]],[[273932,308934],[155,2002],[-303,1515]],[[212031,309564],[-10,-8889]],[[212021,300675],[-14,-4738]],[[212007,295937],[-1275,-7]],[[210732,295930],[1,1618],[-581,-19],[4,3038],[-348,-1],[0,4885],[-290,21]],[[251893,313405],[-148,-262],[-98,-2422]],[[251647,310721],[-1175,275]],[[250472,310996],[1,2162]],[[259221,307669],[-550,-1893]],[[258671,305776],[-416,2982]],[[258255,308758],[-360,845]],[[262003,314866],[-203,-2819],[-428,-1224],[-148,-1566],[-209,5]],[[261015,309262],[-144,4429]],[[262287,311237],[-115,-5511]],[[262172,305726],[-1045,-291]],[[261127,305435],[-112,3827]],[[204007,308772],[-2,-8089],[-357,6],[0,-3269],[-895,189],[3,-3357]],[[202756,294252],[-282,-80],[-3451,20]],[[199023,294192],[-10,14576],[-24,5924]],[[199023,294192],[-802,21],[0,-5147]],[[198221,289066],[-2916,138]],[[195305,289204],[1,10598]],[[255255,309601],[-293,45]],[[254962,309646],[-1501,262]],[[253461,309908],[48,2646]],[[221974,311301],[-1,-5776]],[[221973,305525],[-842,203],[-613,2379]],[[220518,308107],[0,1629]],[[273932,308934],[-751,-1022],[-232,-720],[-385,1197]],[[272564,308389],[-296,4745]],[[245258,306148],[-220,-1243],[179,-1808],[-328,48],[-478,1055]],[[244411,304200],[-66,1223]],[[244345,305423],[-235,2560],[-507,303],[19,4630]],[[264371,311025],[-903,8],[-199,579]],[[250472,310996],[3,-4065]],[[250475,306931],[-737,17]],[[249738,306948],[1,538],[-589,29]],[[249150,307515],[-1,6735]],[[249150,307515],[-2,-540],[-585,15]],[[248563,306990],[-568,-79]],[[247995,306911],[2,1971],[-165,660],[-13,3904]],[[267731,313122],[-172,-4100]],[[267559,309022],[-165,-649],[-348,481],[-159,-756]],[[266887,308098],[-122,4417]],[[277187,309610],[-200,-184],[-957,-3894]],[[276030,305532],[-239,1408],[128,659],[-179,3128],[-311,-16]],[[272564,308389],[-61,-778]],[[272503,307611],[-371,-2255]],[[272132,305356],[-182,81],[-533,1971],[-394,-959]],[[271023,306449],[-209,1264]],[[261127,305435],[65,-2172]],[[261192,303263],[-366,-423]],[[260826,302840],[-582,504]],[[260244,303344],[-292,4311]],[[247116,305288],[0,-1624]],[[247116,303664],[-574,36]],[[246542,303700],[-7,2441],[-826,26]],[[247259,311821],[2,-3022],[-144,17],[-1,-3528]],[[247995,306911],[-1,-1623],[-878,0]],[[266161,311969],[-151,-1209],[137,-1333]],[[266147,309427],[-550,-3123]],[[265597,306304],[-143,-537],[-540,1289]],[[264914,307056],[377,2075],[-83,1018]],[[252434,312593],[-3,-7005]],[[252431,305588],[-490,-270]],[[251941,305318],[-294,8]],[[251647,305326],[0,5395]],[[229055,310124],[0,-2981]],[[229055,307143],[-102,-1894]],[[228953,305249],[-1646,5]],[[227307,305254],[-5,4060]],[[230520,306859],[-129,-798],[-366,1]],[[230025,306062],[-236,1076],[-734,5]],[[225696,309325],[4,-2728]],[[225700,306597],[-87,-539],[-544,798],[-165,-1383]],[[224904,305473],[-175,-372],[-345,1754]],[[235918,307465],[-20,-4634]],[[235898,302831],[-24,-5642]],[[235874,297189],[-725,1210]],[[235149,298399],[-86,1119],[-236,-357],[-301,2194],[-518,1395]],[[234008,302750],[-2,4103]],[[240937,311642],[-27,-4926]],[[240910,306716],[-579,117]],[[240331,306833],[-388,76],[-25,1423],[-898,817],[-143,1103]],[[192649,296056],[0,-2068],[-471,-21],[-761,-1597],[-17,-3435]],[[191400,288935],[-518,-982],[-412,-2924],[-457,4942],[-260,4020]],[[189753,293991],[-316,3938],[-198,-1382],[-452,2730],[42,1284],[-343,3366]],[[268504,309320],[-576,-1453],[-270,224]],[[267658,308091],[-99,931]],[[242221,313104],[-71,-8013]],[[242150,305091],[-288,-50]],[[241862,305041],[-455,1583],[-497,92]],[[244345,305423],[-785,1742],[-49,-1484],[-582,-12],[0,-544]],[[242929,305125],[-779,-34]],[[266887,308098],[-133,-492]],[[266754,307606],[-217,1203],[-390,618]],[[281353,310576],[-256,-2617],[-334,-211],[-135,-1225],[-339,131],[-50,-1711],[-247,-2039]],[[279992,302904],[-1171,6598]],[[269975,311558],[-244,-1242],[240,-2297],[-54,-2071],[-167,-824]],[[269750,305124],[-567,147],[-179,-1082]],[[269004,304189],[-407,3628]],[[253461,309908],[-84,-4366]],[[253377,305542],[-946,46]],[[223430,307886],[-2,-7046]],[[223428,300840],[-1456,0]],[[221972,300840],[1,4685]],[[263252,305383],[-219,26]],[[263033,305409],[-515,58]],[[262518,305467],[-346,259]],[[263269,311612],[-17,-6229]],[[264370,310170],[-2,-2778]],[[264368,307392],[-267,-203],[-179,-1894]],[[263922,305295],[-670,88]],[[271023,306449],[-385,-3232]],[[270638,303217],[-108,-157]],[[270530,303060],[5,560],[-727,-210],[-58,1714]],[[281981,302597],[-3,82]],[[281978,302679],[3,-82]],[[282531,309390],[61,24]],[[282602,309349],[-405,-2625],[-165,-1820],[-109,2614]],[[281923,307518],[-200,2589]],[[251647,305326],[-876,-15]],[[250771,305311],[-294,-4],[-2,1624]],[[276030,305532],[200,-730]],[[276230,304802],[-500,-1758],[-394,2382],[-444,601],[4,1044]],[[280269,301329],[-18,102]],[[280251,301431],[18,-102]],[[281981,302597],[-3,82]],[[281923,307518],[8,-4063],[-186,-1610],[-387,486],[-737,-285],[-373,-588]],[[280248,301458],[-33,175]],[[280215,301633],[-11,68]],[[280204,301701],[-212,1203]],[[237408,310448],[27,-3045],[293,-50],[-7,-3257]],[[237721,304096],[-23,-4862],[-351,60]],[[237347,299294],[-4,1]],[[237343,299295],[-252,2449],[-133,5739],[-409,31]],[[238681,310276],[-66,-2357],[143,-159],[46,-2922],[169,-1709]],[[238973,303129],[-231,-2]],[[238742,303127],[-202,-247],[-819,1216]],[[206879,305575],[2,-12857],[-37,-4822],[-1146,-121]],[[205698,287775],[-6,3219],[-1134,-5],[-3,1595],[-1724,6]],[[202831,292590],[-75,1662]],[[240331,306833],[-20,-6441]],[[240311,300392],[-587,-612]],[[239724,299780],[-512,1234],[-239,2115]],[[264914,307056],[-97,-2181]],[[264817,304875],[-449,1093],[0,1424]],[[254962,309646],[-4,-5674]],[[254958,303972],[1,-1619]],[[254959,302353],[-880,88]],[[254079,302441],[-98,2460],[-614,116]],[[253367,305017],[10,525]],[[183362,303916],[0,-11575]],[[183362,292341],[-1739,3],[0,1620],[-863,-20],[-2,-8075],[-687,-61]],[[180071,285808],[-439,260],[-94,887],[82,3389],[-151,600],[21,1938],[272,564]],[[179762,293446],[283,2149],[77,2691],[-106,4262],[275,1900],[10,1119]],[[181121,309576],[282,-790],[1201,-586],[252,1502],[507,147]],[[219070,309778],[-4,-8907]],[[219066,300871],[-1455,-38]],[[217611,300833],[-1,8914]],[[220518,308107],[-138,101],[-4,-7331]],[[220376,300877],[-1310,-6]],[[258255,308758],[-186,-143],[-223,-2439],[-292,-747],[-162,-1946],[-429,-1122],[-130,-1067]],[[256833,301294],[-358,591],[-165,1920]],[[256310,303805],[109,-16],[5,5714]],[[217611,300833],[-1456,-59]],[[216155,300774],[-3,8974]],[[216155,300774],[-1454,-107]],[[214701,300667],[-14,9093]],[[214701,300667],[-1476,8]],[[213225,300675],[0,9081]],[[277494,308217],[-65,-785],[217,-4446],[468,-2796]],[[278114,300190],[-479,-436],[-840,1115],[-215,922]],[[276580,301791],[-276,1144]],[[276304,302935],[225,772],[-299,1095]],[[213225,300675],[-1204,0]],[[256310,303805],[-1352,167]],[[280269,301329],[-18,102]],[[280248,301458],[-33,175]],[[280204,301701],[21,-471],[-685,-2076],[-529,-3189]],[[279011,295965],[-269,-6],[-253,2494]],[[278489,298453],[-185,3127],[-229,1784],[245,1596],[342,3693]],[[278489,298453],[-265,205],[-88,1174]],[[278136,299832],[-22,358]],[[266754,307606],[118,-2920]],[[266872,304686],[-494,-1390]],[[266378,303296],[-72,1226],[-571,652],[-138,1130]],[[227307,305254],[-1,-3184]],[[227306,302070],[-352,1622],[-507,-2568],[-302,758]],[[226145,301882],[90,1860],[-381,232],[-154,2623]],[[269004,304189],[84,-1082]],[[269088,303107],[-225,529]],[[268863,303636],[-374,-245]],[[268489,303391],[-548,1345]],[[267941,304736],[-283,3355]],[[267941,304736],[-418,57],[-368,-881]],[[267155,303912],[-283,774]],[[274517,305785],[-85,-1864],[134,-4155],[-61,-615]],[[274505,299151],[-581,663],[-511,1884]],[[273413,301698],[-135,2326],[-372,1680],[-227,40],[-176,1867]],[[258671,305776],[-62,-2960]],[[258609,302816],[-230,-1971],[-471,-601],[9,-685]],[[257917,299559],[-515,1404],[-542,-471]],[[256860,300492],[-27,802]],[[221972,300840],[4,-1864]],[[221976,298976],[-229,1201],[-392,509],[-834,191]],[[220521,300877],[-145,0]],[[224904,305473],[0,-4595]],[[224904,300878],[-1476,-38]],[[260244,303344],[-448,-474],[-461,-1877]],[[259335,300993],[-374,2733],[-352,-910]],[[273413,301698],[-85,-3220]],[[273328,298478],[-403,-1011]],[[272925,297467],[-503,1412],[-567,2766]],[[271855,301645],[277,3711]],[[249738,306948],[7,-5485]],[[249745,301463],[-730,-2],[-437,570]],[[248578,302031],[-15,4959]],[[237343,299295],[-645,694],[-800,2842]],[[271855,301645],[-224,-1169]],[[271631,300476],[-514,965],[-162,2030],[-317,-254]],[[264817,304875],[-499,-1732]],[[264318,303143],[-198,104],[-7,-5871]],[[264113,297376],[-299,18],[-3,-1823]],[[263811,295571],[-420,-901]],[[263391,294670],[-672,167]],[[262719,294837],[116,1165]],[[262835,296002],[415,1552],[228,1602]],[[263478,299156],[298,1538],[245,2560],[-99,2041]],[[230025,306062],[101,-882],[-211,-1084],[101,-2120]],[[230016,301976],[-185,350],[-328,-1482],[-268,388],[-184,1849]],[[229051,303081],[-98,2168]],[[244411,304200],[-353,-701],[-25,-3601]],[[244033,299898],[-1166,156]],[[242867,300054],[62,5071]],[[276304,302935],[-783,-2653],[-394,-674],[-382,-2307]],[[274745,297301],[-240,1850]],[[265597,306304],[-143,-2280],[195,-1399]],[[265649,302625],[-510,-2676]],[[265139,299949],[-116,-620]],[[265023,299329],[-659,3103],[-46,711]],[[248578,302031],[0,-1380],[-586,-270],[-1,-1632]],[[247991,298749],[-443,543],[316,1089],[-750,38]],[[247114,300419],[2,3245]],[[250771,305311],[3,-4878],[-150,-1369]],[[250624,299064],[-878,-312]],[[249746,298752],[-1,2711]],[[231679,306850],[149,-590],[1,-3198],[271,-1475],[224,-5]],[[232324,301582],[-241,-630]],[[232083,300952],[-247,888],[-429,-565],[-250,-1798],[-558,-651]],[[230599,298826],[-125,948],[-568,1263],[110,939]],[[234008,302750],[-177,481],[-257,-1569]],[[233574,301662],[-669,200],[-133,968],[-448,-1248]],[[241862,305041],[-376,-48],[-11,-1571],[394,-3307]],[[241869,300115],[-693,133]],[[241176,300248],[-865,144]],[[226145,301882],[-3,-7872]],[[226142,294010],[-1232,-6]],[[224910,294004],[-6,6874]],[[266378,303296],[-305,-1111]],[[266073,302185],[-424,440]],[[246542,303700],[-5,-6913],[-292,-2],[0,-1616]],[[246245,295169],[-1257,-5]],[[244988,295164],[-45,601]],[[244943,295765],[269,600],[-236,1264],[282,816],[-40,1454],[303,-381],[-185,3715],[560,1252],[-185,1658]],[[244943,295765],[-41,-823],[-579,866]],[[244323,295808],[8,4035],[-298,55]],[[262518,305467],[-156,-2441],[-200,-873]],[[262162,302153],[-936,-51]],[[261226,302102],[-34,1161]],[[253367,305017],[-116,-5834]],[[253251,299183],[-738,-1766]],[[252513,297417],[38,555],[-611,6],[-2,2435]],[[251938,300413],[3,4905]],[[210732,295930],[-587,-11],[-3,-3235],[-124,-7],[0,-8009],[-134,-3]],[[209884,284665],[-2861,-34],[-28,-8244]],[[206995,276387],[-1400,-32],[4,8251],[101,1],[-2,3168]],[[263478,299156],[-127,1132],[-280,33]],[[263071,300321],[-38,5088]],[[179762,293446],[-4058,-181]],[[175704,293265],[-2642,-5],[-586,89],[-362,1122],[-386,245]],[[171728,294716],[266,2870],[-339,971],[-382,2958]],[[263071,300321],[-494,-473]],[[262577,299848],[-380,581]],[[262197,300429],[-35,1724]],[[270530,303060],[-303,-446],[112,-1814],[-159,-1241],[160,-3138]],[[270340,296421],[-326,1802]],[[270014,298223],[-319,2645],[-607,2239]],[[251938,300413],[-588,25],[-1,-1081],[-287,-277]],[[251062,299080],[-438,-16]],[[229051,303081],[-27,-98]],[[229024,302983],[-420,-2885],[-33,-1359],[-271,1490],[50,1869],[-342,-239],[-177,-1289],[-249,388],[-61,1401]],[[227521,302359],[-215,-289]],[[242867,300054],[0,-1626]],[[242867,298428],[-989,63]],[[241878,298491],[-9,1624]],[[254079,302441],[15,-7371]],[[254094,295070],[-915,185]],[[253179,295255],[72,3928]],[[268489,303391],[-191,-579],[-387,-2963],[106,-885]],[[268017,298964],[-130,-738]],[[267887,298226],[-343,98],[-450,1171]],[[267094,299495],[12,1605]],[[267106,301100],[49,2812]],[[189753,293991],[-1506,-3],[-6,-4849],[-1376,-18],[-349,1600],[-5,-14590]],[[186511,276131],[-3149,-34]],[[183362,276097],[0,16244]],[[267106,301100],[-728,2196]],[[238742,303127],[-74,-8950]],[[238668,294177],[-668,110]],[[238000,294287],[-287,2380]],[[237713,296667],[-388,1099],[22,1528]],[[256860,300492],[-290,-2959],[-338,-792],[-245,-1878]],[[255987,294863],[-144,1386],[-293,274]],[[255550,296523],[0,1620],[-291,55],[-9,3268],[-291,887]],[[268863,303636],[139,-2860],[323,-3556]],[[269325,297220],[-471,-581]],[[268854,296639],[-87,-160]],[[268767,296479],[-371,1043],[-216,-422],[-163,1864]],[[227521,302359],[-8,-8969]],[[227513,293390],[-1201,1]],[[226312,293391],[-170,619]],[[259335,300993],[56,-1317],[-280,-1720]],[[259111,297956],[-212,-1709],[70,-886],[-507,-2769]],[[258462,292592],[1,2075],[-386,811]],[[258077,295478],[-160,4081]],[[247114,300419],[-4,-8971]],[[247110,291448],[-285,-6],[1,-1092],[-449,-9]],[[246377,290341],[-133,6],[1,4822]],[[270014,298223],[-538,-798]],[[269476,297425],[-151,-205]],[[271631,300476],[-1005,-5255]],[[270626,295221],[-44,238]],[[270582,295459],[-242,962]],[[265023,299329],[-257,-2572],[-190,585]],[[264576,297342],[-271,24]],[[264305,297366],[-192,10]],[[260085,295669],[3,546],[-557,17],[-158,1641],[-262,83]],[[260826,302840],[-296,-1219],[-6,-4236],[-439,-1716]],[[267094,299495],[-351,-1180]],[[266743,298315],[-277,2223]],[[266466,300538],[-393,1647]],[[261226,302102],[134,-4629]],[[261360,297473],[95,-3169]],[[261455,294304],[-945,242]],[[260510,294546],[-581,51]],[[259929,294597],[156,1072]],[[235149,298399],[-2,-6961]],[[235147,291438],[-172,646]],[[234975,292084],[-223,645],[-660,-165]],[[234092,292564],[-504,-211]],[[233588,292353],[-6,48]],[[233582,292401],[-8,9261]],[[239724,299780],[-34,-6034]],[[239690,293746],[-694,-158]],[[238996,293588],[-328,589]],[[230599,298826],[-15,-6106]],[[230584,292720],[-1252,143]],[[229332,292863],[-306,203]],[[229026,293066],[-2,9917]],[[229026,293066],[-1224,262]],[[227802,293328],[-289,62]],[[237713,296667],[-606,-1072]],[[237107,295595],[-319,701],[-638,-836],[-276,1729]],[[276580,301791],[-335,-2181],[7,-1181],[-302,-3864]],[[275950,294565],[-336,-991]],[[275614,293574],[-478,40],[-285,2097]],[[274851,295711],[-106,1590]],[[171728,294716],[-240,-2194]],[[171488,292522],[-527,2873],[-607,2125],[-309,1675]],[[233582,292401],[-515,1393],[-746,743],[-269,-627]],[[232052,293910],[31,7042]],[[266466,300538],[-488,-4111]],[[265978,296427],[-652,2741]],[[265326,299168],[-187,781]],[[255550,296523],[-579,144],[-98,-1641],[-485,59]],[[254388,295085],[-294,-15]],[[262197,300429],[-36,-1811],[-801,-1145]],[[249746,298752],[0,-823]],[[249746,297929],[-780,-17]],[[248966,297912],[-952,5]],[[248014,297917],[-23,832]],[[278136,299832],[-261,-1730],[-65,-2062],[-295,-1491],[-382,-3556]],[[277133,290993],[-221,469],[-599,3177],[-363,-74]],[[232052,293910],[-3,-967]],[[232049,292943],[-1216,-1077]],[[230833,291866],[-249,854]],[[274851,295711],[-156,130],[-298,-1759],[-415,2480],[-367,85],[-287,1831]],[[272925,297467],[-517,-3023]],[[272408,294444],[-1070,-5441]],[[271338,289003],[-265,919]],[[271073,289922],[-257,2318],[43,1727],[-233,1254]],[[258077,295478],[-439,-1479],[-435,-2504],[-547,-1593]],[[256656,289902],[-109,9]],[[256547,289911],[-268,1111],[-470,3450],[178,391]],[[220521,300877],[16,-8165]],[[220537,292712],[-1467,9]],[[219070,292721],[-4,8150]],[[221976,298976],[5,-6234]],[[221981,292742],[-1444,-30]],[[224910,294004],[2,-1314]],[[224912,292690],[-1486,25]],[[223426,292715],[2,8125]],[[219070,292721],[-1453,-12]],[[217617,292709],[-6,8124]],[[217617,292709],[-1443,-46]],[[216174,292663],[-19,8111]],[[223426,292715],[-1445,27]],[[216174,292663],[-1446,-96]],[[214728,292567],[-27,8100]],[[213225,300675],[57,-8129]],[[213282,292546],[-1287,-1]],[[211995,292545],[12,3392]],[[214728,292567],[-1446,-21]],[[241176,300248],[-11,-1320],[-519,-4119],[192,-1475],[382,-1184]],[[241220,292150],[-1146,188]],[[240074,292338],[-391,56],[7,1352]],[[266743,298315],[382,-3066],[-30,-933]],[[267095,294316],[-707,-913]],[[266388,293403],[-414,1707]],[[265974,295110],[4,1317]],[[262719,294837],[-459,-1602]],[[262260,293235],[-776,49]],[[261484,293284],[-29,1020]],[[262577,299848],[-12,-3860],[270,14]],[[248014,297917],[76,-4059],[-205,-781],[1,-1557]],[[247886,291520],[-437,-568]],[[247449,290952],[-227,-1244],[-112,1740]],[[252513,297417],[69,-2402],[-508,-278]],[[252074,294737],[0,1098],[-967,-56]],[[251107,295779],[-45,3301]],[[241878,298491],[54,-3824],[-182,-2390],[74,-1398]],[[241824,290879],[-230,-523],[-374,1794]],[[244323,295808],[-11,-3253]],[[244312,292555],[-1484,172]],[[242828,292727],[39,5701]],[[264965,295106],[-389,2236]],[[265326,299168],[-46,-1734],[-315,-2328]],[[278269,287761],[-76,264]],[[278193,288025],[76,-264]],[[279011,295965],[-403,-3616],[-92,-2363],[-262,1132],[231,-2620],[-212,-589],[-375,805]],[[277898,288714],[-123,574]],[[277775,289288],[-642,1705]],[[195305,289204],[0,-8031]],[[195305,281173],[-1,-6538]],[[195304,274635],[-185,-4]],[[195119,274631],[-313,2178],[43,3573],[-198,582],[-595,5810],[0,10694]],[[267887,298226],[124,-662],[-175,-1445],[3,-2070]],[[267839,294049],[-421,-2128]],[[267418,291921],[-323,2395]],[[253179,295255],[-83,-4574]],[[253096,290681],[-1016,-26]],[[252080,290655],[-6,4082]],[[265974,295110],[21,-726],[-419,-926],[-105,-1288]],[[265471,292170],[-170,1415]],[[265301,293585],[-336,1521]],[[251107,295779],[-195,-539]],[[250912,295240],[-465,-211],[-350,-1146]],[[250097,293883],[-350,16],[-1,4030]],[[268767,296479],[-480,-2909]],[[268287,293570],[-448,479]],[[237107,295595],[1,-5232]],[[237108,290363],[-341,756],[-196,-1322],[-639,965],[-521,-443]],[[235411,290319],[-264,1119]],[[275614,293574],[-88,-2688],[-301,-779]],[[275225,290107],[-353,382],[-40,1052],[-800,-2861]],[[274032,288680],[-22,-72]],[[274010,288608],[-398,2352],[-785,2546]],[[272827,293506],[-419,938]],[[242828,292727],[23,-1854],[-369,-1417],[-65,-1108]],[[242417,288348],[-241,1667],[-352,864]],[[270582,295459],[-741,-3542]],[[269841,291917],[-395,3623],[30,1885]],[[259929,294597],[-147,0],[-210,-3806],[-384,39],[-157,-3572]],[[259031,287258],[-882,-28]],[[258149,287230],[-35,1334],[210,2007],[198,283],[-60,1738]],[[250097,293883],[-203,-504],[0,-2742]],[[249894,290637],[-533,-8]],[[249361,290629],[-5,2179],[-385,256],[-5,4848]],[[249361,290629],[-291,-1322]],[[249070,289307],[-1184,2213]],[[269841,291917],[-165,-789]],[[269676,291128],[-84,-4]],[[269592,291124],[-424,845],[-314,4670]],[[195119,274631],[-3726,22]],[[191393,274653],[0,1610]],[[191393,276263],[7,12672]],[[264305,297366],[79,-1214],[-282,-4275]],[[264102,291877],[-95,1]],[[264007,291878],[19,2058],[-215,1635]],[[265301,293585],[-113,-1287],[-385,-1433]],[[264803,290865],[-134,697],[-567,315]],[[238000,294287],[55,-4237],[-203,-2133],[-208,87],[128,-2330]],[[237772,285674],[-29,1]],[[237743,285675],[-635,-3]],[[237108,285672],[0,4691]],[[269592,291124],[-134,-694]],[[269458,290430],[-356,976],[-547,-1372]],[[268555,290034],[-268,3536]],[[256547,289911],[-602,-2120],[-102,-2362],[-288,-52]],[[255555,285377],[-818,64]],[[254737,285441],[-325,194],[-15,2539]],[[254397,288174],[-9,6911]],[[211995,292545],[-22,-7993]],[[211973,284552],[0,-8133]],[[211973,276419],[1,-8101]],[[211974,268318],[0,-1611],[-730,-3]],[[211244,266704],[-1105,-3]],[[210139,266701],[0,9724],[-254,-1],[-1,8241]],[[252080,290655],[-401,-9]],[[251679,290646],[-766,-8]],[[250913,290638],[-1,4602]],[[244988,295164],[-59,-1673],[289,734],[-25,-3356],[150,-2943],[-317,-598],[225,-984],[-128,-819]],[[245123,285525],[1,-135]],[[245124,285390],[-272,18]],[[244852,285408],[-481,19]],[[244371,285427],[-67,-3]],[[244304,285424],[8,7131]],[[264007,291878],[-125,-1789],[-179,18]],[[263703,290107],[-245,1867],[-67,2696]],[[258149,287230],[-73,-1530]],[[258076,285700],[-259,915],[-757,37],[2,-409]],[[257062,286243],[-403,2157],[-3,1502]],[[271073,289922],[-669,-320],[-229,1237],[-260,-547]],[[269915,290292],[-239,836]],[[254397,288174],[-931,-2937]],[[253466,285237],[-470,-87]],[[252996,285150],[100,5531]],[[250913,290638],[-58,-1637],[-291,-9],[-2,-1641],[-289,-10]],[[250273,287341],[4,3290],[-383,6]],[[246377,290341],[40,-3234]],[[246417,287107],[-592,20],[0,-1670]],[[245825,285457],[-702,68]],[[266388,293403],[-34,-4888]],[[266354,288515],[-753,-748]],[[265601,287767],[-17,902]],[[265584,288669],[-113,3501]],[[263703,290107],[-14,-676]],[[263689,289431],[-975,45],[-28,-605]],[[262686,288871],[-213,626]],[[262473,289497],[-213,3738]],[[277775,289288],[-200,-1209],[-345,-446],[-431,-3537]],[[276799,284096],[-357,-1958],[-73,1501]],[[276369,283639],[-170,-184],[-119,1854],[-263,408]],[[275817,285717],[-479,2444],[-113,1946]],[[175704,293265],[8,-6545],[-64,-13],[-10,-8499]],[[175638,278208],[-2839,-1564],[17,2580],[-320,834],[-131,1949],[82,972],[-206,4489],[-531,4208],[-222,846]],[[261484,293284],[160,-5558]],[[261644,287726],[11,-400]],[[261655,287326],[-1005,-14]],[[260650,287312],[-168,-13]],[[260482,287299],[28,7247]],[[260482,287299],[-991,-305]],[[259491,286994],[-460,264]],[[233588,292353],[-6,-56]],[[233582,292297],[-853,-556],[-398,-899],[-290,-1445]],[[232041,289397],[8,3546]],[[272827,293506],[-15,-5200],[98,-784]],[[272910,287522],[-478,-196],[-495,933],[-201,-1171]],[[271736,287088],[-363,872],[-35,1043]],[[267418,291921],[45,-835],[-297,-951],[-55,-1335]],[[267111,288800],[-431,-34]],[[266680,288766],[-326,-251]],[[202831,292590],[15,-6293]],[[202846,286297],[-1525,-4281],[1,-811],[-1146,18],[-2,-3253]],[[200174,277970],[-860,-3]],[[199314,277967],[-358,449],[43,2766],[-165,3253],[-161,393],[-104,4257],[-348,-19]],[[238996,293588],[-214,-3466],[-110,32],[-25,-4497]],[[238647,285657],[-84,3]],[[238563,285660],[-791,14]],[[268555,290034],[21,-284]],[[268576,289750],[-391,-1856],[-455,-1078]],[[267730,286816],[-127,1308],[-492,676]],[[183362,276097],[1,-8677]],[[183363,267420],[-4124,8475],[12,2285],[251,1897]],[[179502,280077],[536,715],[179,2808],[-146,2208]],[[191393,276263],[-1966,-35],[0,-192],[-2916,95]],[[226312,293391],[-10,-8053]],[[226302,285338],[-375,38]],[[225927,285376],[-1032,85]],[[224895,285461],[17,7229]],[[240074,292338],[-28,-6700]],[[240046,285638],[-697,10]],[[239349,285648],[-702,9]],[[265584,288669],[-611,417]],[[264973,289086],[-229,4]],[[264744,289090],[59,1775]],[[274010,288608],[-793,-2802]],[[273217,285806],[-307,1716]],[[179502,280077],[-3864,-1869]],[[227802,293328],[-42,-8197]],[[227760,285131],[-406,74]],[[227354,285205],[-1052,133]],[[229332,292863],[-28,-7781]],[[229304,285082],[-524,-54]],[[228780,285028],[-1020,103]],[[262473,289497],[-829,-1771]],[[232039,284934],[-231,5],[-115,-1982],[146,-674]],[[231839,282283],[-397,24]],[[231442,282307],[-615,60]],[[230827,282367],[1,2607]],[[230828,284974],[5,6892]],[[232041,289397],[-2,-4463]],[[230828,284974],[-612,11]],[[230216,284985],[-912,97]],[[219070,292721],[-5,-8097]],[[219065,284624],[-381,10]],[[218684,284634],[-1066,126]],[[217618,284760],[-1,7949]],[[220537,292712],[6,-8141]],[[220543,284571],[-433,-2]],[[220110,284569],[-1045,55]],[[234975,292084],[-31,-7088]],[[234944,284996],[-291,1575],[-562,-613]],[[234091,285958],[1,6606]],[[217618,284760],[-376,-124]],[[217242,284636],[-1070,-48]],[[216172,284588],[2,8075]],[[223426,292715],[8,-8203]],[[223434,284512],[-404,2]],[[223030,284514],[-1045,0]],[[221985,284514],[-4,8228]],[[224895,285461],[-2,-1035],[-413,38]],[[224480,284464],[-1046,48]],[[221985,284514],[-393,-1]],[[221592,284513],[-1049,58]],[[244304,285424],[-1696,42]],[[242608,285466],[-191,2882]],[[216172,284588],[-373,15]],[[215799,284603],[-1072,-39]],[[214727,284564],[1,8003]],[[206995,276387],[10,-9680]],[[207005,266707],[-196,-1]],[[206809,266706],[-3008,34]],[[203801,266740],[-1057,-19]],[[202744,266721],[2,17810],[100,1766]],[[213282,292546],[0,-7999]],[[213282,284547],[-1309,5]],[[214727,284564],[-369,-14]],[[214358,284550],[-1076,-3]],[[234091,285958],[-73,-393]],[[234018,285565],[-60,-960],[-377,12]],[[233581,284617],[1,7680]],[[242608,285466],[-1828,115]],[[240780,285581],[-734,57]],[[233581,284617],[-993,-40]],[[232588,284577],[-549,357]],[[235411,290319],[-5,-7248]],[[235406,283071],[-145,-6]],[[235261,283065],[-39,471]],[[235222,283536],[-278,1460]],[[249070,289307],[-205,-1883],[-209,-3399],[-189,-953]],[[248467,283072],[-1109,2439]],[[247358,285511],[-151,1938],[245,381],[-3,3122]],[[264744,289090],[-346,-257]],[[264398,288833],[-567,369],[-126,-463]],[[263705,288739],[-16,692]],[[275817,285717],[197,-1750],[-222,-2037],[-677,748]],[[275115,282678],[4,3610],[-520,96],[-567,2296]],[[247358,285511],[-237,-29],[-2,-1592],[-572,-19]],[[246547,283871],[-8,3240],[-122,-4]],[[269458,290430],[-641,-2764]],[[268817,287666],[-241,2084]],[[269915,290292],[98,-679],[-163,-3128],[52,-2327],[-122,-1900]],[[269780,282258],[-329,-1370]],[[269451,280888],[-242,1119]],[[269209,282007],[-81,3666],[-311,1993]],[[237108,285672],[0,-2572]],[[237108,283100],[-1702,-29]],[[271736,287088],[202,-924]],[[271938,286164],[-630,-2537]],[[271308,283627],[-251,828],[-618,-687],[-182,-1941]],[[270257,281827],[-477,431]],[[252996,285150],[-21,-1156]],[[252975,283994],[-1295,-58]],[[251680,283936],[-1,6710]],[[251680,283936],[-283,11]],[[251397,283947],[-1128,95]],[[250269,284042],[4,3299]],[[250269,284042],[-1132,-33],[-12,-840]],[[249125,283169],[-658,-97]],[[257062,286243],[14,-3977],[-399,14]],[[256677,282280],[-836,-103],[-284,801]],[[255557,282978],[-2,2399]],[[269209,282007],[-689,-991],[-95,755],[-402,-936]],[[268023,280835],[-353,3484]],[[267670,284319],[60,2497]],[[263705,288739],[-84,-3973]],[[263621,284766],[55,-1652]],[[263676,283114],[-539,-692]],[[263137,282422],[-449,518]],[[262688,282940],[-2,5931]],[[262688,282940],[-898,-37]],[[261790,282903],[-135,4423]],[[199314,277967],[-878,-18],[0,-1622],[-852,-5],[-2,-8136],[36,-4019]],[[197618,264167],[-857,-7],[-37,4023],[3,8134],[-307,3],[2,1614],[-576,1],[-4,3232],[-537,6]],[[264398,288833],[-62,-3686]],[[264336,285147],[-715,-381]],[[264973,289086],[-7,-4742],[-221,-293]],[[264745,284051],[2,1069],[-411,27]],[[265601,287767],[265,-1985],[29,-1347]],[[265895,284435],[-506,-1945]],[[265389,282490],[-646,23]],[[264743,282513],[2,1538]],[[267670,284319],[-205,1161],[-587,-1541]],[[266878,283939],[-198,4827]],[[266878,283939],[-137,-520]],[[266741,283419],[-297,-993]],[[266444,282426],[-403,800],[-146,1209]],[[275274,275950],[263,628]],[[275537,276578],[-263,-628]],[[275115,282678],[-128,-2424],[77,-2306]],[[275064,277948],[21,-1800],[-453,1074]],[[274632,277222],[-397,2101],[-304,486]],[[273931,279809],[-206,2389],[-508,3608]],[[273217,285806],[-382,-1361],[-117,-2127],[-399,-1752]],[[272319,280566],[-381,5598]],[[254737,285441],[-323,-2640],[122,-3923],[141,-1191],[-210,-1224]],[[254467,276463],[-114,140]],[[254353,276603],[-184,1820],[-373,-689],[-63,3380],[-367,2778],[100,1345]],[[261790,282903],[136,-2308]],[[261926,280595],[-1276,-335]],[[260650,280260],[0,7052]],[[260650,280260],[-288,-579],[2,-1890]],[[260364,277791],[-286,-278],[4,-1615],[-248,-23]],[[259834,275875],[-17,4870],[-320,8]],[[259497,280753],[-6,6241]],[[259497,280753],[-1025,-27]],[[258472,280726],[-231,1150],[-165,3824]],[[246547,283871],[-291,-1615],[175,-342],[-73,-2890]],[[246358,279024],[-674,12],[3,3264],[138,3157]],[[235222,283536],[-1204,-38]],[[234018,283498],[0,2067]],[[258472,280726],[-106,-860]],[[258366,279866],[-839,-31],[-2,-819],[-563,46]],[[256962,279062],[-281,-17],[0,1226]],[[256681,280271],[-4,2009]],[[202744,266721],[-672,-16],[-49,-2499],[301,-1532]],[[202324,262674],[-2142,-5]],[[200182,262669],[-8,15301]],[[272319,280566],[44,-2784]],[[272363,277782],[-442,-1977]],[[271921,275805],[-817,2977]],[[271104,278782],[-65,752],[269,4093]],[[273931,279809],[-121,-807]],[[273810,279002],[-90,-725],[-314,2440],[-742,-3662]],[[272664,277055],[-301,727]],[[248467,283072],[-232,-2682],[-637,-1400],[-479,-1603]],[[247119,277387],[-290,-1241]],[[246829,276146],[-463,2072]],[[246366,278218],[-8,806]],[[238701,271113],[-400,6]],[[238301,271119],[-149,1514],[-322,444],[-466,-2739],[-255,8]],[[237109,270346],[-1,3654]],[[237108,274000],[0,5599]],[[237108,279599],[0,3501]],[[237743,285675],[-72,-1365],[156,-1652],[-2,-3964],[475,-5512],[401,-2069]],[[238563,285660],[193,-3037],[29,-8285],[191,-5]],[[238976,274333],[-155,-3244]],[[238821,271089],[-120,24]],[[239516,277586],[89,-2415],[-250,-840],[-379,2]],[[239349,285648],[4,-5606],[141,-1],[22,-2455]],[[240779,280837],[-287,-13],[-146,-3236]],[[240346,277588],[-830,-2]],[[240780,285581],[-1,-4744]],[[242619,280157],[-575,-2639],[-400,30]],[[241644,277548],[0,1623],[-290,937],[-575,729]],[[242608,285466],[11,-5309]],[[234018,283498],[-3,-6187]],[[234015,277311],[-896,740],[-334,1437]],[[232785,279488],[-113,619]],[[232672,280107],[-84,4470]],[[246366,278218],[-28,-988],[-363,175],[-2,-2477],[-251,41],[-36,2444],[-221,12]],[[245465,277425],[-297,1279],[258,1421],[-227,255],[-6,1864],[185,934],[-62,1883],[-223,-1603],[31,1932]],[[268023,280835],[-29,-951]],[[267994,279884],[-750,-2311]],[[267244,277573],[-503,5846]],[[244371,285427],[-163,-2146],[-308,-2022],[-90,-2138]],[[243810,279121],[-368,-2106],[-400,-900]],[[243042,276115],[-26,3010],[-397,1032]],[[255557,282978],[-142,-817],[-5,-3254],[-143,-3],[2,-3233]],[[255269,275671],[-569,-21],[-233,813]],[[244852,285408],[-216,-2699],[-7,-2066],[-283,-3112]],[[244346,277531],[-420,5],[-116,1585]],[[225927,285376],[-30,-8275]],[[225897,277101],[-4,-879]],[[225893,276222],[-1133,26]],[[224760,276248],[-282,40]],[[224478,276288],[2,8176]],[[245465,277425],[-76,-261]],[[245389,277164],[-175,-453],[-903,-2]],[[244311,276709],[35,822]],[[227354,285205],[-18,-8168]],[[227336,277037],[-185,1]],[[227151,277038],[-1254,63]],[[264743,282513],[-219,-2967]],[[264524,279546],[-235,1091]],[[264289,280637],[-264,580],[-349,1897]],[[254353,276603],[-497,-1834],[-41,-1059],[323,-1231]],[[254138,272479],[-1368,-35]],[[252770,272444],[91,5016]],[[252861,277460],[114,6534]],[[228780,285028],[-17,-8117]],[[228763,276911],[-135,15]],[[228628,276926],[-1292,111]],[[230216,284985],[-6,-3141]],[[230210,281844],[-31,-4996]],[[230179,276848],[-1416,63]],[[230827,282367],[0,-512],[-617,-11]],[[232672,280107],[-600,940],[-233,1236]],[[218684,284634],[-13,-8157]],[[218671,276477],[-1432,44]],[[217239,276521],[3,8115]],[[210139,266701],[-717,-2]],[[209422,266699],[-123,-1]],[[209299,266698],[-2294,9]],[[220110,284569],[-7,-8139]],[[220103,276430],[-1432,47]],[[217239,276521],[0,-67]],[[217239,276454],[-1432,21]],[[215807,276475],[-8,8128]],[[215807,276475],[-1432,-36]],[[214375,276439],[-17,8111]],[[214375,276439],[-24,-1]],[[214351,276438],[-2378,-19]],[[221592,284513],[0,-8235]],[[221592,276278],[-48,0]],[[221544,276278],[-1441,152]],[[223030,284514],[0,-8234]],[[223030,276280],[-51,1]],[[222979,276281],[-1387,-3]],[[224478,276288],[-1448,-8]],[[271104,278782],[-447,-786]],[[270657,277996],[-226,1046],[-174,2785]],[[266444,282426],[-235,-3362]],[[266209,279064],[-288,518]],[[265921,279582],[-345,931],[-187,1977]],[[251397,283947],[0,-6509]],[[251397,277438],[-1123,-9]],[[250274,277429],[-5,6613]],[[250274,277429],[-14,-1]],[[250260,277428],[-1136,16],[0,1060]],[[249124,278504],[1,4665]],[[252861,277460],[-1464,-22]],[[235261,283065],[11,-1603]],[[235272,281462],[1,-2617]],[[235273,278845],[-795,-2144]],[[234478,276701],[-463,610]],[[267244,277573],[-756,-2456]],[[266488,275117],[-124,3123],[-155,824]],[[264289,280637],[-142,-524],[-197,-2955],[-99,3]],[[263851,277161],[-538,-506]],[[263313,276655],[-159,-301]],[[263154,276354],[0,1215]],[[263154,277569],[-17,4853]],[[237108,279599],[-280,902],[-87,-872],[-299,1007],[-286,-1037],[-341,13],[-543,1850]],[[249124,278504],[-150,-881]],[[248974,277623],[-206,-33],[-579,-3455]],[[248189,274135],[-495,8],[0,1630],[-575,8],[0,1606]],[[256681,280271],[-69,-2498],[-191,-1960],[-869,-125],[4,-3255],[-143,-9]],[[255413,272424],[-143,-6],[-1,3253]],[[263154,277569],[-1076,448]],[[262078,278017],[-152,2578]],[[265921,279582],[1,-27]],[[265922,279555],[-184,34],[-659,-3026]],[[265079,276563],[-284,2687],[-271,296]],[[231442,282307],[2,-8954]],[[231444,273353],[-1048,21]],[[230396,273374],[-217,3474]],[[270657,277996],[-80,-1269]],[[270577,276727],[-329,-341],[-291,-3111],[55,-642]],[[270012,272633],[0,-5]],[[270012,272628],[-338,-491],[-153,1147]],[[269521,273284],[74,1606],[-301,1073],[-437,275]],[[268857,276238],[507,2501],[87,2149]],[[232785,279488],[1,-3857],[404,-2322]],[[233190,273309],[-1746,44]],[[268857,276238],[-59,1856],[-542,1928],[-262,-138]],[[237108,274000],[-848,-1143],[-402,1182]],[[235858,274039],[-244,1]],[[235614,274040],[-342,532],[1,4273]],[[197618,264167],[24,-1497]],[[197642,262670],[0,-8383],[-2345,-24]],[[195297,254263],[7,20372]],[[259834,275875],[18,-769],[-399,-590]],[[259453,274516],[-479,386],[-118,1013],[-485,-1586]],[[258371,274329],[-5,5537]],[[241644,277548],[-1,-1627]],[[241643,275921],[-577,18],[2,-798],[-433,-2]],[[240635,275139],[-285,12],[-4,2437]],[[273810,279002],[98,-1654]],[[273908,277348],[8,-2098]],[[273916,275250],[-11,-827]],[[273905,274423],[15,-90]],[[273920,274333],[-20,-184]],[[273900,274149],[-60,-391]],[[273840,273758],[-438,-2518],[192,-2128]],[[273594,269112],[-42,-1026],[-431,800]],[[273121,268886],[-76,2025]],[[273045,270911],[44,2084],[-425,4060]],[[265079,276563],[-41,-438]],[[265038,276125],[-259,143],[-399,-2644]],[[264380,273624],[-384,781]],[[263996,274405],[-145,2756]],[[262078,278017],[222,-1817]],[[262300,276200],[-162,-699],[-765,-79],[10,-1076],[-289,-21]],[[261094,274325],[-155,1623],[-575,1843]],[[256962,279062],[29,-2257],[255,-3763]],[[257246,273042],[-260,-2156],[4,-3296]],[[256990,267590],[-755,-8],[-206,1771],[-614,2276]],[[255415,271629],[-2,795]],[[243042,276115],[-252,-2319],[-86,-1941]],[[242704,271855],[-773,7]],[[241931,271862],[-288,2425],[0,1634]],[[268650,272453],[-454,-2106]],[[268196,270347],[-296,-908],[-412,5134]],[[267488,274573],[-244,3000]],[[268857,276238],[-273,-3364],[66,-421]],[[258371,274329],[-238,-1220]],[[258133,273109],[-631,1134],[-256,-1201]],[[274213,271739],[154,-1026],[-395,-1964],[241,2990]],[[273840,273758],[261,-1594],[-185,-2613],[-322,-439]],[[273920,274333],[-20,-184]],[[273916,275250],[-11,-827]],[[274414,275982],[264,-153],[280,-1525],[-294,-989],[-167,-1799],[-139,1959],[56,2507]],[[274160,276707],[170,-315],[46,-4156],[-277,1436],[-122,1905],[183,1130]],[[274632,277222],[33,-1172],[-536,816],[-157,-984],[-64,1466]],[[265516,275419],[-478,706]],[[265922,279555],[-53,-2958],[-353,-1178]],[[266488,275117],[0,-942]],[[266488,274175],[-328,-2106]],[[266160,272069],[-649,44]],[[265511,272113],[5,3306]],[[234478,276701],[5,-3100]],[[234483,273601],[0,-4335]],[[234483,269266],[-1322,-45]],[[233161,269221],[-81,1801],[110,2287]],[[244311,276709],[-53,-2457]],[[244258,274252],[-441,-8],[-351,-1304],[-29,-1625],[-344,-1797]],[[243093,269518],[-234,271],[-155,2066]],[[235614,274040],[-175,-434],[-956,-5]],[[271921,275805],[37,-1771],[275,-2845]],[[272233,271189],[-961,-1642]],[[271272,269547],[-98,1564],[-427,592]],[[270747,271703],[140,2716],[-310,2308]],[[250260,277428],[-1,-6571]],[[250259,270857],[-1135,-31]],[[249124,270826],[0,2438],[-150,4359]],[[246829,276146],[-140,-526],[-66,-2601],[-184,267],[-97,-2398]],[[246342,270888],[-114,-1301],[-411,-1386],[-315,625]],[[245502,268826],[-2,115]],[[245500,268941],[-62,69]],[[245438,269010],[-314,191],[117,1426]],[[245241,270627],[337,223],[252,2167],[-256,411],[68,1606],[-306,123],[53,2007]],[[263154,276354],[-211,-1787],[-562,-838]],[[262381,273729],[-81,2471]],[[200182,262669],[-2540,1]],[[273045,270911],[-142,208],[-547,-2636]],[[272356,268483],[-123,2706]],[[261094,274325],[1,-3262]],[[261095,271063],[-1173,-45],[-180,787]],[[259742,271805],[-289,2711]],[[249124,270826],[0,-3253]],[[249124,267573],[-1393,50]],[[247731,267623],[-42,2293],[500,4219]],[[240635,275139],[6,-4082],[-110,-1625]],[[240531,269432],[-347,19]],[[240184,269451],[-691,9]],[[239493,269460],[-75,1632],[-597,-3]],[[267488,274573],[-96,548],[-482,-3351]],[[266910,271770],[-422,2405]],[[252770,272444],[-27,-1509]],[[252743,270935],[-1345,-64]],[[251398,270871],[-1,6567]],[[251398,270871],[-1139,-14]],[[247731,267623],[-1360,-26]],[[246371,267597],[-29,3291]],[[245500,268941],[-62,69]],[[245241,270627],[-1027,-144]],[[244214,270483],[-102,1786],[146,1983]],[[263996,274405],[-106,-4613]],[[263890,269792],[-3,-599]],[[263887,269193],[-267,9],[-340,1832]],[[263280,271034],[-27,-2]],[[263253,271032],[60,5623]],[[270747,271703],[-157,186]],[[270590,271889],[-578,744]],[[227151,277038],[5,-4409]],[[227156,272629],[-469,-48],[-452,-1537]],[[226235,271044],[-342,5178]],[[228628,276926],[2,-5287]],[[228630,271639],[-1087,-1713]],[[227543,269926],[-107,-742],[-280,1303]],[[227156,270487],[0,2142]],[[230396,273374],[193,-555]],[[230589,272819],[-1430,-4745]],[[229159,268074],[-124,-407],[-405,3972]],[[255415,271629],[-143,-188],[4,-2272],[-284,-8],[4,-2362],[-126,-263]],[[254870,266536],[-1130,-22]],[[253740,266514],[-126,1159],[208,569],[68,3760],[248,477]],[[263253,271032],[-736,-37]],[[262517,270995],[98,588],[-330,1226],[96,920]],[[217239,276454],[-26,-8133]],[[217213,268321],[-224,-2]],[[216989,268319],[-1200,8]],[[215789,268327],[18,8148]],[[218671,276477],[-13,-8190]],[[218658,268287],[-436,22]],[[218222,268309],[-1009,12]],[[220103,276430],[-15,-8194]],[[220088,268236],[-232,-5]],[[219856,268231],[-1198,56]],[[215789,268327],[-226,-12]],[[215563,268315],[-1212,-2]],[[214351,268313],[0,8125]],[[214351,268313],[-211,4]],[[214140,268317],[-1427,-23]],[[212713,268294],[-739,24]],[[265511,272113],[-316,-756],[-183,-1475]],[[265012,269882],[-430,1091]],[[264582,270973],[-206,1238],[4,1413]],[[221544,276278],[-6,-8068]],[[221538,268210],[-230,15]],[[221308,268225],[-1220,11]],[[224760,276248],[1,-3983],[-213,-701]],[[224548,271564],[-1040,-3414]],[[223508,268150],[-541,29]],[[222967,268179],[12,8102]],[[226235,271044],[225,-2721]],[[226460,268323],[-392,-1293]],[[226068,267030],[-565,-1869]],[[225503,265161],[-119,1166],[-213,-723],[-623,5960]],[[222967,268179],[-215,-7]],[[222752,268172],[-1214,38]],[[191393,274653],[-1,-12959]],[[191392,261694],[-1985,-98],[1,-3801],[-565,-10],[0,-1773]],[[188843,256012],[-5480,11408]],[[269521,273284],[-686,-1046]],[[268835,272238],[-185,215]],[[262517,270995],[-386,-1847],[16,-1277]],[[262147,267871],[-360,-24],[-203,1604],[-424,-24]],[[261160,269427],[-65,1636]],[[259742,271805],[-223,-444],[6,-3714]],[[259525,267647],[5,-1554],[-546,-20]],[[258984,266073],[-309,-26],[-4,1611],[-286,-16]],[[258385,267642],[-7,3608],[-245,1859]],[[241931,271862],[-1,-2445]],[[241930,269417],[-1399,15]],[[268196,270347],[-120,-1132]],[[268076,269215],[-774,-4371]],[[267302,264844],[-222,1408],[-144,2418]],[[266936,268670],[128,1356],[-154,1744]],[[264582,270973],[-5,-1325],[-687,144]],[[195297,254263],[-3928,9]],[[191369,254272],[23,7422]],[[258385,267642],[-117,-1603]],[[258268,266039],[-1141,-46]],[[257127,265993],[-137,1597]],[[235858,274039],[-247,-3594],[-55,-4247],[247,15]],[[235803,266213],[165,-2415]],[[235968,263798],[-1351,24]],[[234617,263822],[-133,12],[-1,5432]],[[244214,270483],[8,-2105],[-240,-3867]],[[243982,264511],[-59,1623],[-511,-1824],[-304,1856]],[[243108,266166],[-15,3352]],[[266936,268670],[-756,231]],[[266180,268901],[-20,3168]],[[237109,270346],[75,-4024]],[[237184,266322],[-1381,-109]],[[233161,269221],[87,-951]],[[233248,268270],[-1739,-1463]],[[231509,266807],[-6,123]],[[231503,266930],[-162,2702],[-752,3187]],[[269315,266119],[-168,-187]],[[269147,265932],[-152,1040],[-160,5266]],[[269521,273284],[-206,-7165]],[[270012,272628],[143,-2844],[-121,-4705]],[[270034,265079],[-575,986]],[[269459,266065],[-144,54]],[[238301,271119],[215,-817],[-12,-1665],[522,-3202]],[[239026,265435],[-240,-345],[-1,-1274]],[[238785,263816],[-1220,-19]],[[237565,263797],[-381,2525]],[[231503,266930],[-1231,-4027]],[[230272,262903],[-620,348]],[[229652,263251],[-493,4823]],[[227156,270487],[-696,-2164]],[[270590,271889],[127,-3675],[608,-624]],[[271325,267590],[-175,-616]],[[271150,266974],[-403,-4200]],[[270747,262774],[-221,704]],[[270526,263478],[-177,1370],[-315,231]],[[253740,266514],[-294,-3348],[252,-2067]],[[253698,261099],[-1047,-27]],[[252651,261072],[-25,3649]],[[252626,264721],[117,6214]],[[269147,265932],[-285,-745]],[[268862,265187],[-233,1642],[-442,1367],[-111,1019]],[[261160,269427],[-51,-2452],[-440,-328],[-199,-2179]],[[260470,264468],[-371,2],[3,1617],[-263,0],[-26,1492],[-288,68]],[[266180,268901],[-4,-1685]],[[266176,267216],[-979,51]],[[265197,267267],[-185,2615]],[[271272,269547],[173,-1187]],[[271445,268360],[-120,-770]],[[243108,266166],[-323,-822]],[[242785,265344],[-856,1]],[[241929,265345],[1,4072]],[[257127,265993],[-143,-9],[6,-2440]],[[256990,263544],[-1655,-26]],[[255335,263518],[-332,-39],[-133,3057]],[[229652,263251],[-593,-1974]],[[229059,261277],[-293,2871],[-669,-2188]],[[228097,261960],[2,1607],[-452,1892],[172,815],[-272,611],[-4,3041]],[[225503,265161],[144,-1411],[-855,-2936]],[[224792,260814],[-81,738],[-490,-430]],[[224221,261122],[-713,7028]],[[245502,268826],[-127,-1816],[-313,-1032],[-167,-1704]],[[244895,264274],[-271,-484],[77,-1751]],[[244701,262039],[-172,-288]],[[244529,261751],[-462,332]],[[244067,262083],[-85,2428]],[[272356,268483],[444,-3810]],[[272800,264673],[248,-709],[-50,-2336],[-442,1524]],[[272556,263152],[-244,2510],[-518,507],[-349,2191]],[[239493,269460],[179,-3272],[-322,2],[-3,-1287],[-321,532]],[[273103,263918],[241,-619],[-261,-1765],[-124,1738],[144,646]],[[273429,267956],[229,-1122],[-211,-969],[-18,2091]],[[273121,268886],[209,-711],[80,-2196],[219,-966],[-158,-953],[-438,1416],[-233,-803]],[[263887,269193],[-34,-3123]],[[263853,266070],[-27,-877],[-408,18]],[[263418,265211],[-156,2]],[[263262,265213],[18,5821]],[[263262,265213],[-700,78]],[[262562,265291],[-449,1252]],[[262113,266543],[34,1328]],[[265197,267267],[107,-2251]],[[265304,265016],[-1157,121],[0,-795]],[[264147,264342],[-294,1728]],[[252626,264721],[-415,-683],[-803,-568]],[[251408,263470],[-10,7401]],[[246371,267597],[-50,-4881]],[[246321,262716],[-773,1409],[-653,149]],[[251408,263470],[-92,-39]],[[251316,263431],[-1040,-418]],[[250276,263013],[-17,7844]],[[250276,263013],[-235,-98]],[[250041,262915],[-702,-302]],[[249339,262613],[-25,4964],[-190,-4]],[[228097,261960],[-913,-2937]],[[227184,259023],[-236,2294],[-214,-718]],[[226734,260599],[-666,6431]],[[268862,265187],[-531,-2010]],[[268331,263177],[-444,-557]],[[267887,262620],[-507,1246]],[[267380,263866],[-78,978]],[[241929,265345],[-139,-2434]],[[241790,262911],[-715,10],[-2,-1631],[-985,-12]],[[240088,261278],[65,2739],[214,1718],[-183,3716]],[[240088,261278],[187,-1588],[292,-495],[230,-1479]],[[240797,257716],[-541,-2780],[-190,-402]],[[240066,254534],[-710,332]],[[239356,254866],[-1,3236],[-281,-4],[-4,3279],[-287,5],[2,2434]],[[262113,266543],[-202,-2490],[-1,-1443]],[[261910,262610],[38,-331]],[[261948,262279],[-247,-1122],[-557,79],[-1,-1623]],[[261143,259613],[-925,-27]],[[260218,259586],[239,3100],[13,1782]],[[234617,263822],[-104,-1184],[69,-2704],[234,-2043]],[[234816,257891],[-383,-1878]],[[234433,256013],[-270,786],[-111,1768],[-372,550]],[[233680,259117],[-7,1203],[-351,1944],[-118,1559],[44,4447]],[[267380,263866],[-842,-12]],[[266538,263854],[-369,127]],[[266169,263981],[7,3235]],[[273082,261033],[-133,-2580],[-38,2027],[171,553]],[[272823,261759],[40,-1068],[-396,-433]],[[272467,260258],[-390,846]],[[272077,261104],[-418,1229],[-343,2022],[41,1842],[-207,777]],[[272556,263152],[267,-1393]],[[218222,268309],[-10,-7269]],[[218212,261040],[-111,-2492],[-1121,-108]],[[216980,258440],[1,1757]],[[216981,260197],[8,8122]],[[216981,260197],[-1417,9]],[[215564,260206],[-1,8109]],[[215564,260206],[-1425,-1]],[[214139,260205],[1,8112]],[[214139,260205],[-86,1]],[[214053,260206],[-1251,7]],[[212802,260213],[-89,1]],[[212713,260214],[0,8080]],[[212713,260214],[-1472,-6]],[[211241,260208],[3,6496]],[[219856,268231],[-1,-7250]],[[219855,260981],[-1643,59]],[[233680,259117],[-1054,-950],[-246,-706]],[[232380,257461],[15,2779],[-147,-648]],[[232248,259592],[-249,2566],[-298,601],[38,1653],[-230,2395]],[[222752,268172],[-19,-11396]],[[222733,256776],[-311,-1069],[-354,1218],[-198,-944],[-249,1262]],[[221621,257243],[-334,1575]],[[221287,258818],[21,9407]],[[221287,258818],[-1085,65]],[[220202,258883],[-344,21],[-3,2077]],[[224221,261122],[-255,-235],[-56,-1438],[-589,-2355]],[[223321,257094],[-276,-434]],[[223045,256660],[-312,116]],[[260218,259586],[-114,-1]],[[260104,259585],[-994,-4]],[[259110,259581],[-7,3224],[-142,-9],[23,3277]],[[259110,259581],[-134,-3309]],[[258976,256272],[-231,290],[-9,1358],[-330,-1424],[-4,1398],[-276,-34]],[[258126,257860],[-1,2419],[142,7],[1,5753]],[[247731,267623],[274,-3564],[26,-1966]],[[248031,262093],[-342,-655]],[[247689,261438],[-106,-329],[-1263,-25]],[[246320,261084],[1,1632]],[[249339,262613],[-284,-111]],[[249055,262502],[-612,-242]],[[248443,262260],[-412,-167]],[[266169,263981],[-1,-930],[-528,-12]],[[265640,263039],[-382,825]],[[265258,263864],[46,1152]],[[225328,255832],[-536,4982]],[[226734,260599],[-1152,-3886]],[[225582,256713],[-254,-881]],[[272077,261104],[-207,-2290],[-271,-705]],[[271599,258109],[-852,4665]],[[232248,259592],[-1251,-3816]],[[230997,255776],[-725,7127]],[[209299,266698],[-217,-16660]],[[209082,250038],[-2270,-8220]],[[206812,241818],[27,3208],[-30,21680]],[[206812,241818],[-175,-639]],[[206637,241179],[-655,3287],[-500,980],[-452,2433],[-137,1784],[-466,1523],[-267,2269],[-357,1834]],[[203803,255289],[-2,11451]],[[203803,255289],[-617,1714],[-455,4718],[-407,953]],[[211241,260208],[-789,7]],[[210452,260215],[-226,1786],[-294,419],[-120,2210],[-127,-359],[-263,2428]],[[210452,260215],[261,-433],[176,-3774],[535,32],[186,-1031],[512,-16]],[[212122,254993],[-1599,-11260]],[[210523,243733],[-1441,6305]],[[262562,265291],[-9,-2748],[-135,18]],[[262418,262561],[-508,49]],[[255335,263518],[-42,-2446],[-142,-14],[4,-2674],[-156,-741],[132,-1479],[-365,-603],[-167,-1944]],[[254599,253617],[-506,-1945]],[[254093,251672],[-63,1221],[229,2447],[-147,-275],[95,2172],[-484,2051],[-25,1811]],[[237565,263797],[235,-2648],[-112,-2155]],[[237688,258994],[-417,-313]],[[237271,258681],[-296,953],[-858,614]],[[236117,260248],[-149,3550]],[[244067,262083],[-416,-314],[-35,-1776],[-277,-170],[-1,-3574],[-157,-1570],[81,-1640]],[[243262,253039],[-258,530],[-178,-1296],[-48,1873]],[[242778,254146],[7,11198]],[[269459,266065],[0,-2384],[-247,7],[-4,-2375]],[[269208,261313],[-295,-710]],[[268913,260603],[-583,-23],[1,2597]],[[264147,264342],[212,-842],[-100,-3841]],[[264259,259659],[-425,-12]],[[263834,259647],[-269,0]],[[263565,259647],[-156,2805],[9,2759]],[[270526,263478],[-236,-999],[1,-5623]],[[270291,256856],[-262,1105]],[[270029,257961],[-821,3352]],[[258126,257860],[-564,-26]],[[257562,257834],[-384,25],[-188,2000],[3,2240]],[[256993,262099],[-3,1445]],[[263565,259647],[-754,-29]],[[262811,259618],[-348,-20],[-45,2963]],[[242778,254146],[-219,178]],[[242559,254324],[-305,2653]],[[242254,256977],[-195,277],[-241,2436],[-28,3221]],[[265258,263864],[-181,-812],[-39,-2866]],[[265038,260186],[-69,-497],[-710,-30]],[[252692,256194],[-1093,-40]],[[251599,256154],[-283,-4]],[[251316,256150],[0,7281]],[[252651,261072],[41,-4878]],[[246320,261084],[-2,-1627]],[[246318,259457],[-1160,-19]],[[245158,259438],[-408,1159],[-49,1442]],[[227703,254041],[-519,4982]],[[229059,261277],[365,-3474]],[[229424,257803],[-1329,-4514]],[[228095,253289],[-182,-662],[-210,1414]],[[266538,263854],[73,-1664]],[[266611,262190],[-131,-3052]],[[266480,259138],[-414,-483]],[[266066,258655],[-411,1022],[-15,3362]],[[267887,262620],[-7,-2008]],[[267880,260612],[-912,139],[0,1414],[-357,25]],[[266066,258655],[-13,-4419]],[[266053,254236],[-962,80]],[[265091,254316],[4,2024]],[[265095,256340],[-57,3846]],[[236117,260248],[245,-1198],[-44,-6786]],[[236318,252264],[-313,1644],[-176,1815],[-632,881],[-381,1287]],[[239356,254866],[-430,-7],[-146,-1627],[-240,7],[-65,-1704]],[[238475,251535],[-133,-175]],[[238342,251360],[-234,2339],[-88,3955],[-332,1340]],[[271599,258109],[109,-1596]],[[271708,256513],[-299,-2289]],[[271409,254224],[-861,806]],[[270548,255030],[-29,1448],[-228,378]],[[256993,262099],[-405,-644],[-234,-1414],[-90,-2314],[-724,-4799]],[[255540,252928],[-525,-297]],[[255015,252631],[-416,986]],[[251316,256150],[-565,11]],[[250751,256161],[-705,0]],[[250046,256161],[-5,6754]],[[229993,252193],[-569,5610]],[[230997,255776],[-230,-1047]],[[230767,254729],[-774,-2536]],[[268913,260603],[84,-3784]],[[268997,256819],[-88,-1964]],[[268909,254855],[-525,11],[-61,691],[-839,350]],[[267484,255907],[-15,965]],[[267469,256872],[411,3740]],[[242254,256977],[-1212,-1624],[-245,2363]],[[250046,256161],[-146,0]],[[249900,256161],[-376,-7]],[[249524,256154],[-46,1959],[-425,1319],[2,3070]],[[262811,259618],[-6,-2207]],[[262805,257411],[-584,-282],[-45,579]],[[262176,257708],[-228,4571]],[[262176,257708],[-117,-3881]],[[262059,253827],[-919,118]],[[261140,253945],[3,5668]],[[249524,256154],[-188,2]],[[249336,256156],[-474,-1],[3,-814],[-378,9]],[[248487,255350],[-44,6910]],[[267469,256872],[-538,70]],[[266931,256942],[-451,2196]],[[244529,261751],[-61,-2123],[-259,460],[229,-1495],[-276,-598],[18,-3120],[-205,749],[175,-2364],[-388,-522],[182,-1099]],[[243944,251639],[-102,-1383],[173,-932],[-202,-1255]],[[243813,248069],[-73,-541]],[[243740,247528],[12,447]],[[243752,247975],[-182,929]],[[243570,248904],[129,2677],[-437,1458]],[[248487,255350],[-226,-1014]],[[248261,254336],[-568,265]],[[247693,254601],[-4,6837]],[[257562,257834],[96,-2877],[-99,-436],[7,-2858]],[[257566,251663],[-178,1282],[-1848,-17]],[[245158,259438],[-12,-4903]],[[245146,254535],[-217,450],[-648,-955],[-50,-1687],[-287,-704]],[[191369,254272],[-1713,-9],[-813,1749]],[[225328,255832],[-812,-3459]],[[224516,252373],[-177,743],[-194,2530],[-443,142],[-381,1306]],[[247693,254601],[-46,0]],[[247647,254601],[-802,-13]],[[246845,254588],[-237,-7]],[[246608,254581],[0,4882],[-290,-6]],[[270029,257961],[-328,-1873],[-203,-243]],[[269498,255845],[-501,974]],[[226342,249372],[-760,7341]],[[227703,254041],[-1361,-4669]],[[220202,258883],[-12,-9165]],[[220190,249718],[-1597,-26]],[[218593,249692],[-12,8143],[-1602,88]],[[216979,257923],[1,517]],[[254093,251672],[-73,-563]],[[254020,251109],[-140,-343],[-854,-8],[-285,-550]],[[252741,250208],[-49,5986]],[[272538,253809],[-20,-18]],[[272518,253791],[20,18]],[[272206,254211],[-81,132]],[[272125,254343],[81,-132]],[[272895,258076],[-221,-2947],[-140,1837],[361,1110]],[[272467,260258],[359,-497],[69,-954],[-372,-537],[-14,-3438],[142,-738],[-530,300]],[[272121,254394],[-413,2119]],[[232380,257461],[233,-3410],[-191,-919],[-110,-3298]],[[232312,249834],[-586,-39],[-742,-2204]],[[230984,247591],[-246,5282],[29,1856]],[[215564,260206],[-1,-10640]],[[215563,249566],[-1463,119]],[[214100,249685],[-47,10521]],[[214100,249685],[-245,18]],[[213855,249703],[-113,1973],[-687,2489],[-254,-616]],[[212801,253549],[1,6664]],[[237271,258681],[4,-1923],[-170,-1045],[14,-5133]],[[237119,250580],[-252,-651]],[[236867,249929],[-549,2335]],[[216979,257923],[-19,-8365]],[[216960,249558],[-1397,8]],[[212801,253549],[-158,-489],[-521,1933]],[[265095,256340],[-399,-66]],[[264696,256274],[-805,-62]],[[263891,256212],[-57,3435]],[[263891,256212],[-578,-53]],[[263313,256159],[-423,45],[-85,1207]],[[261140,253945],[-191,-1283],[-485,462],[-142,-1410]],[[260322,251714],[-224,22]],[[260098,251736],[6,7849]],[[260098,251736],[-1120,-77]],[[258978,251659],[-2,4613]],[[246608,254581],[-1058,-7],[-230,-518]],[[245320,254056],[-174,479]],[[238342,251360],[-866,-337]],[[237476,251023],[-357,-443]],[[266931,256942],[-267,-2339]],[[266664,254603],[-217,-427]],[[266447,254176],[-394,60]],[[234433,256013],[126,-728]],[[234559,255285],[-1328,-6123]],[[233231,249162],[-511,-2377]],[[232720,246785],[-160,1025],[32,1830],[-280,194]],[[221621,257243],[-4,-7538]],[[221617,249705],[-1427,13]],[[270548,255030],[135,-1217],[-559,-1966],[-260,400]],[[269864,252247],[-282,1361],[-84,2237]],[[258978,251659],[17,-3690]],[[258995,247969],[-561,3]],[[258434,247972],[-835,6]],[[257599,247978],[-33,3685]],[[218593,249692],[-762,-76]],[[217831,249616],[-871,-58]],[[236867,249929],[-914,-1226]],[[235953,248703],[-290,477]],[[235663,249180],[-783,1632]],[[234880,250812],[-186,3541],[-135,932]],[[263313,256159],[-12,-3258]],[[263301,252901],[-768,-60],[-14,-3409]],[[262519,249432],[-296,57]],[[262223,249489],[-201,1664],[37,2674]],[[229346,250051],[-672,-2227]],[[228674,247824],[-579,5465]],[[229993,252193],[-647,-2142]],[[242559,254324],[-409,-313],[69,-6633],[-201,52]],[[242018,247430],[-298,679],[-277,-1715],[-308,-248]],[[241135,246146],[-630,-114]],[[240505,246032],[-16,6745],[-423,1757]],[[223045,256660],[-5,-9678]],[[223040,246982],[-1093,-7]],[[221947,246975],[-331,2],[1,2728]],[[224516,252373],[110,-2638],[233,-1105]],[[224859,248630],[-17,-2012]],[[224842,246618],[-1446,-2]],[[223396,246616],[-356,366]],[[267484,255907],[289,-2604],[-29,-1804]],[[267744,251499],[-329,-678],[-91,-2266]],[[267324,248555],[-270,35]],[[267054,248590],[-114,4038],[-276,1975]],[[269864,252247],[216,-986],[1,-1604],[213,-1377]],[[270294,248280],[-791,53],[-15,-4065],[756,-203],[-181,-4018]],[[270063,240047],[-569,247]],[[269494,240294],[-47,4436],[-166,-9],[13,2675],[-283,1029],[-221,3071]],[[268790,251496],[-78,1759],[195,-14],[2,1614]],[[226342,249372],[-12,-642]],[[226330,248730],[-1471,-100]],[[272538,253809],[99,-1698],[-281,-1545],[-109,1279],[124,2060],[147,-114]],[[272121,254394],[4,-51]],[[272206,254211],[155,-844],[-204,-1997],[71,-2599],[-325,802]],[[271903,249573],[-591,1665]],[[271312,251238],[97,2986]],[[264696,256274],[-167,-1562],[-391,-949],[-69,-1200],[-328,-1574],[-68,-1447]],[[263673,249542],[-96,10]],[[263577,249552],[15,3293],[-291,56]],[[265091,254316],[-12,-4797]],[[265079,249519],[-315,12]],[[264764,249531],[-724,18]],[[264040,249549],[-367,-7]],[[252741,250208],[18,-2159]],[[252759,248049],[-1139,-6]],[[251620,248043],[-21,8111]],[[250751,256161],[23,-9759]],[[250774,246402],[-565,1]],[[250209,246403],[-23,1870]],[[250186,248273],[3,6262],[-287,-6],[-2,1632]],[[250186,248273],[-850,-146]],[[249336,248127],[0,8029]],[[251620,248043],[-141,-1624]],[[251479,246419],[-705,-17]],[[249336,248127],[-206,-4]],[[249130,248123],[-300,-5]],[[248830,248118],[0,1358],[-237,-5],[-309,1881],[-23,2984]],[[268790,251496],[-833,9]],[[267957,251505],[-213,-6]],[[234880,250812],[-269,-1952],[-727,-4046]],[[233884,244814],[-145,1530],[-209,-879]],[[233530,245465],[-193,-34],[-106,3731]],[[213855,249703],[556,-1561],[548,-266],[329,-1468],[180,-3868],[116,-913]],[[215584,241627],[-1029,49],[-1,-1089],[-570,23],[-1,-5857],[-623,-16],[0,-4293]],[[213360,230444],[-2433,11417]],[[210927,241861],[-404,1872]],[[271312,251238],[-472,-2257]],[[270840,248981],[-349,502],[-197,-1203]],[[245320,254056],[101,-734],[-2,-5262]],[[245419,248060],[-324,3]],[[245095,248063],[-1282,6]],[[240505,246032],[-431,-220]],[[240074,245812],[-1233,93],[-383,-267]],[[238458,245638],[138,2981],[-121,2916]],[[230984,247591],[-297,-1132],[-306,-3133],[-307,-918]],[[230074,242408],[-150,659]],[[229924,243067],[-83,1791],[-230,1423],[-92,2593],[-173,1177]],[[267054,248590],[-504,85]],[[266550,248675],[-277,858]],[[266273,249533],[181,2384],[-7,2259]],[[246845,254588],[1,-6513]],[[246846,248075],[-54,0]],[[246792,248075],[-721,-7]],[[246071,248068],[-652,-8]],[[247647,254601],[2,-6508]],[[247649,248093],[-244,-5]],[[247405,248088],[-559,-13]],[[248830,248118],[-1181,-25]],[[243570,248904],[-74,-473]],[[243496,248431],[-155,-910],[-26,-2268]],[[243315,245253],[-1107,9]],[[242208,245262],[-190,2168]],[[266273,249533],[3,-822],[-452,71]],[[265824,248782],[-746,72],[1,665]],[[228674,247824],[-526,-1799],[-157,-2555]],[[227991,243470],[-863,2198],[-567,666]],[[226561,246334],[-231,2396]],[[255287,234666],[-1,83]],[[255286,234749],[1,-83]],[[255015,252631],[47,-4598]],[[255062,248033],[-100,-2449],[293,-2293],[342,-1258],[-72,-3553]],[[255525,238480],[118,-709],[-472,-3244],[-776,-834],[-363,138],[618,1003],[-446,2270],[13,2456],[-99,3217],[-227,556]],[[253891,243333],[-3,423]],[[253888,243756],[51,358]],[[253939,244114],[171,4893],[-90,2102]],[[262223,249489],[73,-1396]],[[262296,248093],[-1354,-67]],[[260942,248026],[7,3775],[-627,-87]],[[257599,247978],[-271,46]],[[257328,248024],[-1052,38]],[[256276,248062],[-1214,-29]],[[263577,249552],[-524,-184]],[[263053,249368],[-534,64]],[[260942,248026],[-27,-3]],[[260915,248023],[-1496,-67]],[[259419,247956],[-424,13]],[[238458,245638],[-203,-3553],[-301,-2557],[116,-1802],[-122,-774]],[[237948,236952],[-67,-1096],[157,-1846]],[[238038,234010],[-535,-29]],[[237503,233981],[99,13117],[-126,3925]],[[269494,240294],[-114,46]],[[269380,240340],[-346,136]],[[269034,240476],[-18,2136],[-493,493],[-222,1907],[-346,637]],[[267955,245649],[2,5856]],[[267955,245649],[-134,-371]],[[267821,245278],[-66,1821],[-380,53],[-51,1403]],[[253891,243333],[-3,423]],[[253939,244114],[-283,-5327],[-10,-2293],[-805,175]],[[252841,236669],[-46,6489]],[[252795,243158],[-36,4891]],[[272307,246842],[-203,-3711],[-81,2743],[284,968]],[[271903,249573],[306,-2479],[-160,-4072],[-292,-203]],[[271757,242819],[-831,2065]],[[270926,244884],[0,3896],[-86,201]],[[237503,233981],[-603,-19]],[[236900,233962],[97,1937],[-94,1644],[125,1714]],[[237028,239257],[58,2123],[-200,796],[-363,6258],[-570,269]],[[235663,249180],[-268,-876],[313,-9030]],[[235708,239274],[21,-669],[-542,-18]],[[235187,238587],[-329,77]],[[234858,238664],[56,883],[-384,657],[-240,1922],[-168,-357],[-46,2247],[-192,798]],[[229924,243067],[-953,-3232]],[[228971,239835],[-534,-1857]],[[228437,237978],[-325,5186],[-121,306]],[[209922,237143],[-279,-6],[-3006,4042]],[[210927,241861],[-1005,-4718]],[[232720,246785],[-683,-1234]],[[232037,245551],[-850,-766]],[[231187,244785],[-203,2806]],[[221947,246975],[2,-4279]],[[221949,242696],[-1761,-8]],[[220188,242688],[2,7030]],[[220188,242688],[-2353,-80]],[[217835,242608],[-4,7008]],[[217835,242608],[0,-7787]],[[217835,234821],[-2223,5]],[[215612,234826],[279,1284],[-229,1216],[263,2566],[-18,1121],[-323,614]],[[264040,249549],[-12,-7240]],[[264028,242309],[-1345,402]],[[262683,242711],[300,3247],[70,3410]],[[264764,249531],[114,-3092],[-22,-4394]],[[264856,242045],[-554,176]],[[264302,242221],[-274,88]],[[267013,241274],[-88,36]],[[266925,241310],[88,-36]],[[266550,248675],[98,-2368],[249,-1172],[-274,-444],[-99,-1221],[356,-2142]],[[266880,241328],[-709,260]],[[266171,241588],[-368,135]],[[265803,241723],[21,7059]],[[265803,241723],[-735,253]],[[265068,241976],[-212,69]],[[262683,242711],[-3,0]],[[262680,242711],[-384,5382]],[[270926,244884],[-366,-1369],[-37,-1852],[123,-1709],[-124,-3745]],[[270522,236209],[-335,-64],[-107,1022],[-17,2880]],[[237028,239257],[-1320,17]],[[233530,245465],[-89,-6609]],[[233441,238856],[-666,83],[-646,2258]],[[232129,241197],[-92,4354]],[[244740,241602],[-76,124]],[[244664,241726],[-138,1864],[-596,-161],[213,1884],[-298,-207],[-105,2422]],[[245095,248063],[-355,-6461]],[[243752,247975],[-256,456]],[[226561,246334],[-375,-2253],[-241,-2998]],[[225945,241083],[-211,-3685]],[[225734,237398],[-625,1109]],[[225109,238507],[-245,3502],[174,1353],[-6,1659],[-190,1597]],[[267821,245278],[-326,-1351],[-1,-2847]],[[267494,241080],[-481,194]],[[266925,241310],[-45,18]],[[244664,241726],[-233,-2740],[-196,-267]],[[244235,238719],[-602,5]],[[243633,238724],[-154,6]],[[243479,238730],[63,3797],[-227,2726]],[[250209,246403],[-2,-4881]],[[250207,241522],[-565,12],[0,-2448],[-406,-1072]],[[249236,238014],[-349,2359],[-75,1498]],[[248812,241871],[318,6252]],[[248812,241871],[-426,-21],[-726,869]],[[247660,242719],[-255,5369]],[[242208,245262],[112,-1502],[-109,-3720],[-661,-627],[-124,-1004]],[[241426,238409],[-387,12]],[[241039,238421],[5,125]],[[241044,238546],[92,1719],[-1,5881]],[[262680,242711],[-189,-1959]],[[262491,240752],[-656,31],[-11,-813],[-591,47]],[[261233,240017],[-142,13]],[[261091,240030],[7,4056],[-466,837]],[[260632,244923],[283,3100]],[[247660,242719],[35,-9077]],[[247695,233642],[-97,120]],[[247598,233762],[-55,1170],[-290,-154]],[[247253,234778],[-284,1435],[-176,2313],[-1,3039]],[[246792,241565],[0,6510]],[[255286,234749],[1,-83]],[[256955,236440],[0,-65]],[[256955,236375],[0,65]],[[256232,239600],[16,-2077],[-262,-1642],[-543,-650],[107,1726],[233,526],[-258,997]],[[256276,248062],[-334,-1385],[-81,-3537],[371,-3540]],[[257287,236666],[-332,-291]],[[256955,236440],[333,330]],[[257288,236770],[-1,-104]],[[257328,248024],[-38,-10998]],[[257290,237026],[-1052,-1037],[665,1910],[-226,962],[-136,-1053],[-250,2450],[-59,-658]],[[252795,243158],[-1315,0]],[[251480,243158],[-1,3261]],[[246792,241565],[-957,-12]],[[245835,241553],[167,1301]],[[246002,242854],[-21,4096],[90,1118]],[[257288,236770],[-1,-104]],[[258411,236876],[-1,-356]],[[258410,236520],[1,356]],[[258434,247972],[-17,-10056]],[[258417,237916],[-166,1066],[-410,-2043],[-551,87]],[[246002,242854],[-1125,-260],[-120,-1038]],[[244757,241556],[-17,46]],[[260632,244923],[-55,-1607],[-562,869],[-66,-1633]],[[259949,242552],[-537,37],[7,5367]],[[259949,242552],[-196,-1239],[99,-2458],[-315,-2141]],[[259537,236714],[-9,-2239]],[[259528,234475],[-1118,2045]],[[258411,236876],[815,-134],[-426,1780],[-383,-606]],[[231187,244785],[-45,-4378],[221,-3089],[-139,-1696]],[[231224,235622],[12,736],[-414,181]],[[230822,236539],[-165,3013],[-358,1437],[-319,392],[94,1027]],[[223396,246616],[1,-7872]],[[223397,238744],[-946,25]],[[222451,238769],[-503,-4],[1,3931]],[[225109,238507],[-674,263]],[[224435,238770],[-1038,-26]],[[251480,243158],[0,-1086]],[[251480,242072],[-990,-2],[-283,-548]],[[228437,237978],[-499,-1008]],[[227938,236970],[-97,304]],[[227841,237274],[-634,1522],[-437,-1264],[-263,727],[-303,2900],[-259,-76]],[[234858,238664],[-878,-2778]],[[233980,235886],[-396,1125],[-143,1845]],[[241044,238546],[-396,8],[-9,-943],[-990,-261]],[[239649,237350],[2,3244],[421,9],[2,5209]],[[239649,237350],[0,-383]],[[239649,236967],[-708,-8],[1,1629],[-283,7],[1,-1638],[-712,-5]],[[269034,240476],[-293,114]],[[268741,240590],[-1247,490]],[[232129,241197],[72,-7162]],[[232201,234035],[-806,-383]],[[231395,233652],[-171,1970]],[[243479,238730],[216,-1918],[-535,412],[-328,-898]],[[242832,236326],[-172,208],[-256,-1508]],[[242404,235026],[-95,2568],[-194,809],[-689,6]],[[261091,240030],[-143,0],[-5,-2437],[-1018,73],[-388,-952]],[[271757,242819],[498,-308],[-34,-3431],[-155,926],[-295,-107]],[[271771,239899],[-322,203],[-925,-5549]],[[270524,234553],[-2,1656]],[[252841,236669],[-233,-1277],[-178,1270],[-575,-702],[-371,1452]],[[251484,237412],[-4,4660]],[[230822,236539],[-952,-1550]],[[229870,234989],[-278,410],[-621,4436]],[[245835,241553],[-210,-1460],[-15,-2061],[278,-2141]],[[245888,235891],[-360,-439]],[[245528,235452],[-338,30]],[[245190,235482],[-277,639],[124,2784],[-183,-36],[-97,2687]],[[249236,238014],[203,-4319],[261,-907]],[[249700,232788],[-283,-440]],[[249417,232348],[-471,-67],[-254,868],[-304,-894],[-309,779]],[[248079,233034],[-384,608]],[[264302,242221],[-267,-2074],[-54,-1701],[-693,-1753]],[[263288,236693],[-375,1330],[-2,1097],[-281,268],[-139,1364]],[[222451,238769],[6,-3969]],[[222457,234800],[-1261,75]],[[221196,234875],[-1009,-7]],[[220187,234868],[1,7820]],[[220187,234868],[-1627,-38]],[[218560,234830],[-725,-9]],[[265068,241976],[85,-2795],[-178,-1],[-97,-4623]],[[264878,234557],[-479,557],[-1297,-62]],[[263102,235052],[186,1641]],[[251484,237412],[-1134,-2305],[-141,1298]],[[250209,236405],[-2,5117]],[[266171,241588],[21,-1291],[-370,-1383],[42,-1369],[-274,-2432]],[[265590,235113],[-266,-1103],[-213,-2775]],[[265111,231235],[-234,65]],[[264877,231300],[1,3257]],[[209945,215738],[-23,21405]],[[213360,230444],[686,-3232]],[[214046,227212],[-190,-2193],[-356,427],[-421,-724],[-394,-4187],[-190,-3082],[-26,-2697],[-288,-495],[-452,-3923],[-784,1354],[-338,2103],[-458,443],[-204,1500]],[[245190,235482],[-472,-15],[-483,3252]],[[215612,234826],[-8,-9436]],[[215604,225390],[-988,210],[-570,1612]],[[247253,234778],[-437,-1708],[-207,515]],[[246609,233585],[-184,-840],[-537,3146]],[[267013,241274],[105,-135],[78,-2865],[203,-1638]],[[267399,236636],[-213,-2319]],[[267186,234317],[-334,-5]],[[266852,234312],[-283,815],[-979,-14]],[[250209,236405],[34,-1301],[-543,-2316]],[[268741,240590],[-104,-715],[226,-2818],[-101,-1193],[-315,-129]],[[268447,235735],[-586,1763],[-462,-862]],[[209945,215738],[-500,620],[-308,933],[-500,3116],[-209,112],[-481,2313],[-467,5014],[-27,4955],[-481,3934],[-34,2231],[-301,2213]],[[233980,235886],[192,-3310]],[[234172,232576],[-466,-2513],[-754,2533],[-380,-1252],[-368,-202]],[[232204,231142],[-3,2893]],[[227841,237274],[-343,-3901],[-438,-2646]],[[227060,230727],[-169,-807]],[[226891,229920],[-1289,6177]],[[225602,236097],[132,1301]],[[262491,240752],[-511,-7566]],[[261980,233186],[-763,18]],[[261217,233204],[16,6813]],[[263102,235052],[119,-3212],[349,-2174]],[[263570,229666],[-1288,56],[-50,-791]],[[262232,228931],[-307,1350],[55,2905]],[[269380,240340],[4,-8336]],[[269384,232004],[-309,-3002],[110,-639]],[[269185,228363],[-359,-2063]],[[268826,226300],[-394,1903]],[[268432,228203],[15,7532]],[[272248,234159],[-9,-2722],[-434,369]],[[271805,231806],[-186,99],[91,1895],[-113,886],[151,2028],[471,-633],[29,-1922]],[[272388,234172],[-135,-12]],[[272253,234160],[-2,2672],[137,-2660]],[[271771,239899],[387,-696],[23,-1979],[-488,-402],[1,-1429],[-265,-959],[92,-1411]],[[271521,233023],[-998,-79]],[[270523,232944],[1,1609]],[[270523,232944],[0,-816]],[[270523,232128],[-259,0]],[[270264,232128],[-880,-124]],[[261213,230026],[7,-1939]],[[261220,228087],[-832,3622],[181,87],[319,-1693],[325,-77]],[[261217,233204],[-4,-3056]],[[261213,230148],[-256,-46],[-113,1656],[-199,-122],[-386,1289],[-6,-1143],[-725,2693]],[[229870,234989],[-428,-2537]],[[229442,232452],[-640,-2031]],[[228802,230421],[-160,3871],[-704,2678]],[[236900,233962],[5,-1513]],[[236905,232449],[-444,185],[-185,-1246],[-288,179]],[[235988,231567],[-421,-17],[-380,7037]],[[224435,238770],[11,-6720]],[[224446,232050],[-926,-12]],[[223520,232038],[-1061,-74],[-2,2836]],[[225602,236097],[-347,-5926]],[[225255,230171],[-325,-1870]],[[224930,228301],[-484,3749]],[[245308,223867],[-799,1569],[36,945],[-274,2263]],[[244271,228644],[604,288]],[[244875,228932],[202,-2332],[251,-831],[-20,-1902]],[[244559,230559],[-645,-478],[-352,1661],[-258,-1459],[-367,543]],[[242937,230826],[126,1509],[-231,3991]],[[243633,238724],[170,-1018],[53,-2472],[406,-1498],[39,-2365],[258,-812]],[[245528,235452],[-237,-4825]],[[245291,230627],[-330,-692]],[[244961,229935],[-402,624]],[[235988,231567],[8,-4160]],[[235996,227407],[-1499,-99]],[[234497,227308],[-325,5268]],[[239649,236967],[279,-441],[96,-4141],[287,-11],[13,-1373],[-300,-819]],[[240024,230182],[-368,260],[-1652,-19]],[[238004,230423],[34,3587]],[[241039,238421],[19,-7239]],[[241058,231182],[-312,-1023]],[[240746,230159],[-722,23]],[[242404,235026],[-397,-2818]],[[242007,232208],[-408,-1799],[-382,1123],[-159,-350]],[[268432,228203],[-222,-845]],[[268210,227358],[-526,3596],[-439,645],[-59,2718]],[[228802,230421],[-812,-4931]],[[227990,225490],[-930,5237]],[[231395,233652],[-274,-1644],[130,-1237]],[[231251,230771],[-412,482],[-916,-965]],[[229923,230288],[-71,1867],[-410,297]],[[242937,230826],[-38,-675]],[[242899,230151],[-457,1472],[-435,585]],[[226432,226825],[-346,-1970]],[[226086,224855],[-87,1789],[-744,3527]],[[226891,229920],[-512,-2621],[53,-474]],[[246609,233585],[-25,-1023]],[[246584,232562],[-762,-995],[-135,-866]],[[245687,230701],[-396,-74]],[[266852,234312],[-10,-6935],[145,-1216]],[[266987,226161],[-200,-2815]],[[266787,223346],[-399,936],[-283,2980],[-405,1944],[-589,2029]],[[264877,231300],[-794,-652],[8,-1736],[-255,55]],[[263836,228967],[-266,699]],[[247598,233762],[-477,-3687],[-248,-295],[-10,-2337]],[[246863,227443],[-319,-37]],[[246544,227406],[-91,2727],[131,2429]],[[218560,234830],[1,-12368]],[[218561,222462],[3,-3801],[-131,-3065],[-135,-246]],[[218298,215350],[-586,2295],[-155,1738],[-559,1472],[-109,1937],[-428,2706],[-230,-539],[-627,431]],[[221196,234875],[-9,-4031]],[[221187,230844],[-585,153],[-126,-5326],[-4,-3217]],[[220472,222454],[-273,-4]],[[220199,222450],[-1638,12]],[[223520,232038],[7,-6644]],[[223527,225394],[-721,2115],[-1182,235]],[[221624,227744],[-252,18],[4,3078],[-189,4]],[[268210,227358],[-114,-1168]],[[268096,226190],[-1109,-29]],[[272842,223332],[13,0]],[[272855,223332],[-13,0]],[[272837,223331],[-14,0]],[[272823,223331],[-64,-213]],[[272759,223118],[-215,-625],[-556,-55]],[[271988,222438],[-4,2546]],[[271984,224984],[-177,2427],[99,1442],[-322,1680],[221,1273]],[[272248,234159],[350,-6434],[-19,-1080],[258,-3314]],[[272388,234172],[216,-5656],[-351,5644]],[[238004,230423],[-370,-3472]],[[237634,226951],[-15,2086],[-480,948],[-234,2464]],[[232204,231142],[-439,1364],[375,-6984]],[[232140,225522],[-575,-1123]],[[231565,224399],[-195,1521],[-119,4851]],[[247737,223730],[-329,24],[-91,1327],[-454,2362]],[[247598,233762],[-7,-4806],[112,-2059],[203,-840],[-169,-2327]],[[248125,213951],[70,748]],[[248195,214699],[-70,-748]],[[248079,233034],[-93,-5082],[223,493],[141,-926]],[[248350,227519],[-169,-1242],[4,-1676],[243,-2463],[7,-2735]],[[248435,219403],[-412,1680],[32,1883],[-166,601]],[[247889,223567],[-152,163]],[[262232,228931],[-64,-3572]],[[262168,225359],[-1,-32]],[[262167,225327],[-471,-1559]],[[261696,223768],[-261,-97],[30,2044],[-245,2372]],[[261213,230026],[0,122]],[[249417,232348],[-288,-1735],[-365,-1099]],[[248764,229514],[-363,-244],[-62,-812],[282,-1441]],[[248621,227017],[-271,502]],[[271521,233023],[-209,-1367],[211,19],[-61,-1459],[280,-1434],[17,-2305]],[[271759,226477],[-575,-58],[-356,-1657],[-304,-536]],[[270524,224226],[-1,7902]],[[237634,226951],[-209,-1156],[251,-2060]],[[237676,223735],[-488,-232],[-947,-2205]],[[236241,221298],[-10,6070],[-235,39]],[[234522,223522],[-12,-43]],[[234510,223479],[12,43]],[[234497,227308],[188,-999],[-40,-2779]],[[234645,223530],[-248,563],[-164,2138],[-76,-1074],[334,-1692],[-100,-2415]],[[234391,221050],[-400,-946],[-152,1095]],[[233839,221199],[-128,697],[-451,-247]],[[233260,221649],[-1120,3873]],[[249700,222967],[-38,168]],[[249749,222685],[-49,282]],[[249760,222644],[-11,41]],[[249662,223135],[98,-491]],[[249979,228312],[58,-697]],[[250037,227615],[219,-583],[-251,-835],[182,-672]],[[250187,225525],[-339,382],[73,-1088]],[[249921,224819],[-580,0],[296,-1820]],[[249637,222999],[-31,137]],[[249606,223136],[-17,-11]],[[249589,223125],[-854,2530],[-114,1362]],[[248764,229514],[179,-1315],[188,485],[62,-1578],[278,-40],[74,2258],[374,1265],[60,-2277]],[[246544,227406],[-360,598],[-279,-303]],[[245905,227701],[-207,1351],[-11,1649]],[[229923,230288],[145,-1541]],[[230068,228747],[-848,-6121]],[[229220,222626],[-748,-86]],[[228472,222540],[-488,2914]],[[227984,225454],[6,36]],[[242899,230151],[-21,-3786]],[[242878,226365],[-34,-727],[-509,-1743],[181,-15],[-22,-1563],[-530,-1558],[-885,887]],[[241079,221646],[6,8528],[-339,-15]],[[270524,224226],[-18,-8]],[[270506,224218],[-216,2191],[-389,132],[-406,1491]],[[269495,228032],[311,620],[458,3476]],[[269495,228032],[-310,331]],[[224930,228301],[-647,-3579]],[[224283,224722],[-370,-472]],[[223913,224250],[-386,1144]],[[243094,222650],[507,-1497],[-373,-1379],[-504,1635],[370,1241]],[[244961,229935],[-86,-1003]],[[244271,228644],[-426,-208],[-645,-3854]],[[243200,224582],[80,1698],[-402,85]],[[231565,224399],[21,-2330],[-178,-21]],[[231408,222048],[-241,599]],[[231167,222647],[-226,618],[-208,2751],[-251,273],[-414,2458]],[[245905,227701],[-338,-3548]],[[245567,224153],[-211,-1645]],[[245356,222508],[-48,1359]],[[221624,227744],[-7,-5221]],[[221617,222523],[-1145,-69]],[[227984,225454],[-780,-2865],[-98,357]],[[227106,222946],[-308,893],[-366,2986]],[[241079,221646],[-915,2301],[-643,1244],[-860,-33],[-722,-624],[-263,-799]],[[226086,224855],[-251,-1276],[-617,-1664]],[[225218,221915],[-189,1265],[72,1338],[-818,204]],[[261985,223666],[47,-1027],[-349,843],[302,184]],[[262167,225327],[153,-1172],[-624,-387]],[[263836,228967],[267,-1311],[-501,150],[-1029,-3275],[-405,828]],[[231167,222647],[-368,-2222],[-114,-1978],[-813,-2987]],[[229872,215460],[-51,299]],[[229821,215759],[273,1332],[-648,4505],[-226,1030]],[[245356,222508],[-369,-725]],[[244987,221783],[-279,-1858],[-257,1227],[-396,-628],[-7,2090],[-205,-84],[-21,2141],[-622,-89]],[[270506,224218],[-1,-4598]],[[270505,219620],[-384,-936],[-50,1175],[-543,18]],[[269528,219877],[-418,-93],[-2,1071],[-275,504]],[[268833,221359],[-7,4941]],[[268833,221359],[-783,495]],[[268050,221854],[-36,3076],[82,1260]],[[247232,214624],[0,134]],[[247232,214758],[0,-134]],[[248242,218852],[-13,-1967]],[[248229,216885],[-87,-464]],[[248142,216421],[53,-1722]],[[248125,213951],[-368,-1498],[-122,993]],[[247635,213446],[-258,3162],[-145,-1654]],[[247232,214954],[0,261]],[[247232,215215],[0,103]],[[247232,215318],[15,317]],[[247247,215635],[13,86]],[[247260,215721],[55,1625],[-668,4635],[-413,1833],[-106,1452],[-208,-1075],[-353,-38]],[[247889,223567],[32,-2068],[-180,-981],[501,-1666]],[[222150,222529],[-533,-6]],[[223913,224250],[-73,-520]],[[223840,223730],[-339,-2416],[-158,1139],[-1193,76]],[[249173,217874],[111,-1305],[-399,135],[288,1170]],[[249760,222644],[-11,41]],[[249749,222685],[-49,282]],[[249637,222999],[-31,137]],[[249372,222960],[-16,-479]],[[249356,222481],[-255,-314],[203,-1847],[362,-1191],[-111,-778],[530,-230],[375,-1687],[141,832],[308,-3040],[-51,-1666],[-254,-1183],[-172,580],[-446,-2430],[421,3793],[-138,1557],[-368,-602],[-133,2619],[-305,1183],[-511,-71],[-60,606]],[[248892,218612],[-160,571]],[[248732,219183],[-297,220]],[[249589,223125],[-217,-165]],[[234522,223522],[-12,-43]],[[236241,221298],[-46,-111]],[[236195,221187],[-276,34]],[[235919,221221],[-211,269],[-540,-841],[124,4279],[-301,699],[-199,-2304],[-147,207]],[[227106,222946],[-578,-5085]],[[226528,217861],[-680,52],[-139,1116]],[[225709,219029],[23,814],[-514,2072]],[[271988,222438],[9,-2348],[241,-1825]],[[272238,218265],[-390,-928]],[[271848,217337],[-291,1592],[-5,2835],[246,455],[-91,1669],[277,1096]],[[271759,226477],[138,-1241],[-266,-1803],[-119,-4400],[73,-1266]],[[271585,217767],[-204,-13],[-96,2167],[-186,605],[-594,-906]],[[233260,221649],[-107,-2625],[-243,-15],[-79,-1781],[-212,-962],[-275,622],[-264,-1152]],[[232080,215736],[-403,3185],[-269,3127]],[[228472,222540],[-171,-902],[-100,-3633]],[[228201,218005],[-1039,-5115]],[[227162,212890],[-322,2085]],[[226840,214975],[-363,2441],[51,445]],[[247232,214624],[0,134]],[[247232,214954],[0,261]],[[247232,215318],[15,317]],[[244928,217715],[211,-933],[-106,-1819],[-306,1568],[201,1184]],[[246757,215344],[-84,-582]],[[246673,214762],[-79,239]],[[246594,215001],[-200,-1714]],[[246394,213287],[-354,101]],[[246040,213388],[-298,518],[376,1325],[-332,809],[-84,1095],[-260,-2776],[-234,825],[15,1953],[-358,1999],[122,2647]],[[247260,215721],[-525,940],[22,-1317]],[[225709,219029],[-760,-6052]],[[224949,212977],[-1107,2536]],[[223842,215513],[-2,8217]],[[223842,215513],[-1,-2983]],[[223841,212530],[-1697,17]],[[222144,212547],[6,9982]],[[272759,223118],[386,-4323]],[[273145,218795],[-132,-298],[16,-2699],[-744,-89],[-47,2556]],[[272837,223331],[-14,0]],[[272855,223332],[309,-4535]],[[273164,218797],[-14,-2]],[[273150,218795],[-308,4537]],[[232080,215736],[-73,-613]],[[232007,215123],[-254,-1542],[-959,-3418]],[[230794,210163],[-922,5297]],[[229821,215759],[-780,-3735]],[[229041,212024],[-105,760]],[[228936,212784],[-735,5221]],[[222144,212547],[-1943,-93]],[[220201,212454],[-2,9996]],[[220201,212454],[-1549,-37]],[[218652,212417],[-301,1679],[-53,1254]],[[235183,217123],[-168,-987],[-897,-3581],[738,3847],[327,721]],[[234707,216436],[-103,255],[-320,-2118]],[[234284,214573],[-491,4941],[46,1685]],[[234391,221050],[302,-994],[-118,-528],[193,-1648],[-61,-1444]],[[236195,221187],[-1084,-2978],[545,2519],[263,493]],[[234284,214573],[-259,-341],[-244,-3555],[-457,-2355],[-295,-718]],[[233029,207604],[-168,76],[-313,2527],[-238,32],[-6,1604],[-193,733],[-104,2547]],[[269528,219877],[10,-5016],[-370,-17],[1,-3162]],[[269169,211682],[-610,-874]],[[268559,210808],[-4,-27]],[[268555,210781],[11,1181],[-229,1768],[-631,424],[-130,2752]],[[267576,216906],[349,3016],[125,1932]],[[271585,217767],[74,-1764]],[[271659,216003],[-47,-5893],[-823,-7]],[[270789,210103],[-996,7]],[[269793,210110],[-470,1716],[-154,-144]],[[226840,214975],[-1288,-6319]],[[225552,208656],[-603,4321]],[[274192,206960],[-19,0]],[[274173,206960],[19,0]],[[274038,206958],[-133,1]],[[273905,206959],[-39,-1]],[[273866,206958],[-329,-17],[-54,-3299]],[[273483,203642],[-228,3745],[-313,-578],[-517,1780]],[[272425,208589],[33,1991],[-410,2090],[-148,1729]],[[271900,214399],[73,2025],[-125,913]],[[273145,218795],[71,-1438],[822,-10399]],[[273164,218797],[-14,-2]],[[228017,208315],[-310,1140],[-438,-2084]],[[227269,207371],[-501,3616],[394,1903]],[[228936,212784],[-919,-4469]],[[272425,208589],[-133,-1746]],[[272292,206843],[-680,-113],[4,-8038]],[[271616,198692],[-837,-36]],[[270779,198656],[10,11447]],[[271659,216003],[241,-1604]],[[230794,210163],[-40,-5356]],[[230754,204807],[-187,-17]],[[230567,204790],[-105,612]],[[230462,205402],[-405,-38]],[[230057,205364],[-229,273]],[[229828,205637],[-8,-8]],[[229820,205629],[-3,31]],[[229817,205660],[-203,3106],[-295,2552],[-278,706]],[[225150,203636],[-1,663],[-1297,-17]],[[223852,204282],[-11,8248]],[[225552,208656],[258,-1776]],[[225810,206880],[-660,-3244]],[[231528,203108],[198,1338],[-526,-948],[-443,705]],[[230757,204203],[-3,604]],[[233029,207604],[-514,-1808],[-270,336],[-408,-941],[-74,-1321],[716,1769],[-1754,-5264],[803,2733]],[[226700,204666],[-381,956],[-250,-542]],[[226069,205080],[-259,1800]],[[227269,207371],[-569,-2705]],[[229828,205637],[-8,-8]],[[229817,205660],[1,-375]],[[229818,205285],[-753,-2127],[109,-1486]],[[229174,201672],[-68,-398],[-684,1260]],[[228422,202534],[5,4145],[-122,1447],[-288,189]],[[223852,204282],[-1657,-123]],[[222195,204159],[-37,-4]],[[222158,204155],[-14,8392]],[[222158,204155],[-1965,141]],[[220193,204296],[8,8158]],[[220193,204296],[2,-8383]],[[220195,195913],[-275,-20]],[[219920,195893],[-228,1634],[-299,5632],[-269,1346],[-138,3111],[-290,2181],[-44,2620]],[[268559,210808],[-4,-27]],[[269793,210110],[391,-3170],[-256,-2281]],[[269928,204659],[-433,507],[-598,-8]],[[268897,205158],[-149,697],[-6,3294],[-183,1659]],[[270779,198656],[-274,-620]],[[270505,198036],[2,3100]],[[270507,201136],[2,765],[-428,978],[-153,1780]],[[228422,202534],[-599,-3072]],[[227823,199462],[-496,1216],[-66,1281],[-333,364],[-228,2343]],[[273483,203642],[-949,-51],[-367,555],[125,2697]],[[273866,206958],[152,-4434],[384,-6703],[460,-6126],[-21,-546]],[[274841,189149],[-1029,-223]],[[273812,188926],[17,9773]],[[273829,198699],[-61,3007],[-285,1936]],[[274038,206958],[411,-2425],[-276,2427]],[[274192,206960],[437,-3794],[144,-2446],[-167,-836],[-12,3069],[-288,-3646],[-72,101],[-11,3804],[-174,605],[153,1506],[-297,1636]],[[226409,193289],[-981,20]],[[225428,193309],[-276,-3]],[[225152,193306],[-2,10330]],[[226069,205080],[-235,-508],[782,-8928],[-25,-123]],[[226591,195521],[-240,-1164],[58,-1068]],[[273829,198699],[-2213,-7]],[[229435,196419],[-34,-86]],[[229401,196333],[30,1032]],[[229431,197365],[4,-946]],[[229989,197536],[539,1175],[-1215,-4810]],[[229313,193901],[29,366]],[[229342,194267],[112,1478],[226,125],[309,1666]],[[230757,204203],[-190,587]],[[229467,200563],[-293,1109]],[[229818,205285],[138,-1955],[593,-2863],[-702,-2229],[-380,2325]],[[230125,204170],[-68,1194]],[[230462,205402],[-19,-1857],[-318,625]],[[227823,199462],[-461,-4167]],[[227362,195295],[-76,-648],[-695,874]],[[270507,201136],[-552,2],[0,-813],[-1166,-24]],[[268789,200301],[108,4857]],[[223852,204282],[-6,-10982]],[[223846,193300],[-1632,-504],[-15,3243]],[[222199,196039],[-4,8120]],[[225152,193306],[-1306,-6]],[[222199,196039],[-2004,-126]],[[229467,200563],[-206,-609],[192,-1783]],[[229453,198171],[-927,-863],[295,-1303]],[[228821,196005],[-626,-2605]],[[228195,193400],[-52,240]],[[228143,193640],[-159,1151],[-622,504]],[[268308,195427],[-9,-1]],[[268299,195426],[9,1]],[[270505,198036],[-139,-2611]],[[270366,195425],[-1519,30]],[[268847,195455],[-374,-12]],[[268473,195443],[316,4858]],[[271616,198692],[0,-1636],[277,2],[94,-2170],[192,-1069],[303,-12],[-262,-959],[585,-3549],[246,-3711]],[[273051,185588],[-1173,63]],[[271878,185651],[-1368,-4]],[[270510,185647],[-6,9777],[-138,1]],[[273812,188926],[-11,-3355]],[[273801,185571],[-750,17]],[[228693,189439],[13,358]],[[228706,189797],[-13,-358]],[[229222,193536],[-326,-2723],[39,1456],[287,1267]],[[228195,193400],[229,-263],[-76,-1380]],[[228348,191757],[-205,1883]],[[228490,190446],[-134,1236]],[[228356,191682],[444,2664],[-7,-1493],[-303,-2407]],[[229313,193901],[29,366]],[[229311,195731],[-328,-1201],[-162,1475]],[[229453,198171],[-22,-806]],[[229401,196333],[-90,-602]],[[223846,193300],[13,-13096]],[[223859,180204],[1,-1601],[-436,25]],[[223424,178628],[-1056,72],[-103,855],[-232,-1006]],[[222033,178549],[-140,1379],[71,2611],[-322,2960],[-327,370],[-463,2627],[-155,3393],[-154,197],[-278,2896],[-345,911]],[[228348,191757],[8,-75]],[[228490,190446],[-3,-28]],[[228487,190418],[-15,-156]],[[228472,190262],[-134,-1330],[-231,1091],[-604,-773]],[[227503,189250],[-412,475],[-458,1287],[12,1137]],[[226645,192149],[-236,1140]],[[268308,195427],[-9,-1]],[[268847,195455],[7,-2885]],[[268854,192570],[-198,-1538],[342,-1334],[-115,-2958],[-287,863],[-300,2065],[148,2365],[29,3410]],[[270510,185647],[-1388,-18]],[[269122,185629],[467,3253],[-294,1838],[49,-1786],[-175,308],[19,1798],[-334,1530]],[[225428,193309],[8,-14809]],[[225436,178500],[-811,49]],[[224625,178549],[3,1455],[-769,200]],[[226645,192149],[-378,-2056],[-22,-4641]],[[226245,185452],[-328,-1],[5,-6979]],[[225922,178472],[-486,28]],[[228250,184343],[-70,94]],[[228180,184437],[70,-94]],[[228402,187096],[249,1941],[-343,-3973],[94,2032]],[[228693,189439],[13,358]],[[228487,190418],[-15,-156]],[[227948,186969],[214,-388],[-199,-2526]],[[227963,184055],[-1437,-47],[-281,1444]],[[227503,189250],[288,8],[157,-2289]],[[275241,183987],[-902,23]],[[274339,184010],[-271,5],[-3,1572],[-264,-16]],[[274841,189149],[341,-3281],[59,-1881]],[[275339,183990],[-31,-2]],[[275308,183988],[31,2]],[[273051,185588],[-176,-2846],[396,-2050],[334,-3158]],[[273605,177534],[-628,-17],[1,-1638],[-277,-12],[2,-1650],[-824,23]],[[271879,174240],[-2,5716]],[[271877,179956],[1,5695]],[[271877,179956],[-1371,-46]],[[270506,179910],[4,5737]],[[268884,180868],[-11,1]],[[268873,180869],[11,-1]],[[270506,179910],[-2,-2429]],[[270504,177481],[-549,21],[3,3302],[-874,53]],[[269084,180857],[-345,1646],[240,735],[143,2391]],[[274339,184010],[6,-6562]],[[274345,177448],[1,-1562],[-580,-3038]],[[273766,172848],[42,3614],[-203,1072]],[[227901,178788],[-37,1]],[[227864,178789],[9,1767],[307,3881]],[[228250,184343],[-349,-5555]],[[227963,184055],[-251,-4476],[-258,-852],[114,2439],[-407,-1627],[-336,1829],[200,-2388],[-320,-89]],[[226705,178891],[-582,-1386]],[[226123,177505],[-201,967]],[[275676,178510],[-28,1]],[[275648,178511],[28,-1]],[[275339,183990],[-31,-2]],[[275241,183987],[343,-5477]],[[275584,178510],[-145,-585]],[[275439,177925],[-1,-410]],[[275438,177515],[-1093,-67]],[[269628,172609],[-7,-1]],[[269621,172608],[7,1]],[[270504,177481],[-3,-3270]],[[270501,174211],[-551,16],[0,-506]],[[269950,173721],[0,-130]],[[269950,173591],[-1,-995],[-273,11]],[[269676,172607],[-254,2093],[-338,6157]],[[268884,180868],[-11,1]],[[224625,178549],[159,-3917],[135,10],[-13,-5059]],[[224906,169583],[-1481,40]],[[223425,169623],[-1,9005]],[[271879,174240],[-1378,-29]],[[223425,169623],[-158,-2060],[-441,-1923]],[[222826,165640],[-276,5054],[-494,3303],[54,2886],[-77,1666]],[[227998,166171],[-12,1]],[[227986,166172],[12,-1]],[[228016,167093],[52,-920]],[[228068,166173],[-55,-2]],[[228013,166171],[-73,1392]],[[227940,167563],[76,-470]],[[228016,167093],[-141,1392]],[[227875,168485],[-65,3026]],[[227810,171511],[31,176]],[[227841,171687],[175,-4594]],[[227841,171687],[-58,4800],[50,1667]],[[227833,178154],[31,635]],[[227901,178788],[-82,-3294],[22,-3807]],[[227619,175770],[-62,-1844],[-237,-795]],[[227320,173131],[22,-2788],[212,-1258],[78,-2921]],[[227632,166164],[-1163,-35],[-267,256]],[[226202,166385],[-78,3149]],[[226124,169534],[-1,7971]],[[226705,178891],[360,-136],[284,-872],[340,626],[-70,-2739]],[[226124,169534],[-934,41]],[[225190,169575],[-284,8]],[[276009,173064],[-4,0]],[[276005,173064],[4,0]],[[275994,173067],[-63,2]],[[275931,173069],[-9,1]],[[275922,173070],[-37,2]],[[275885,173072],[-22,0]],[[275863,173072],[-26,-266],[-2071,42]],[[275438,177515],[375,-1551],[181,-2897]],[[275676,178510],[-28,1]],[[275584,178510],[-145,-585]],[[273766,172848],[-167,-3535],[-1729,10]],[[271870,169323],[9,4917]],[[269903,169694],[11,1]],[[269914,169695],[-11,-1]],[[269628,172609],[-7,-1]],[[269950,173591],[197,-1158],[100,-2595],[-427,921],[-144,1848]],[[271870,169323],[-1374,16]],[[270496,169339],[-105,2644],[304,1032],[-368,4],[-94,-803],[-283,1505]],[[276009,173064],[-4,0]],[[275994,173067],[-63,2]],[[275922,173070],[-37,2]],[[275863,173072],[173,-517],[58,-7156],[-69,-4426]],[[276025,160973],[-424,253],[-1823,-65]],[[273778,161161],[-12,11687]],[[273778,161161],[4,-1335]],[[273782,159826],[-1083,-119],[-9,4915],[-814,-69]],[[271876,164553],[-6,4770]],[[270224,168130],[277,-3878],[-101,-153],[-176,4031]],[[271876,164553],[5,-1689],[-271,-30],[4,-1925],[-522,237]],[[271092,161146],[-65,2473],[-474,1226],[-124,2786],[67,1708]],[[269914,169695],[-11,-1]],[[225190,169575],[-740,-9780]],[[224450,159795],[-249,-323],[-357,2361],[-856,1101],[54,932],[-216,1774]],[[226202,166385],[-130,-3036],[396,-281],[0,-1592]],[[226468,161476],[1,-5174]],[[226469,156302],[-921,-316],[-541,1945],[-163,1244],[-394,620]],[[228232,162657],[-37,0]],[[228195,162657],[37,0]],[[227998,166171],[-12,1]],[[228159,164435],[-24,128]],[[228135,164563],[-188,861]],[[227947,165424],[66,747]],[[228068,166173],[91,-1738]],[[227684,164584],[-2,-1373]],[[227682,163211],[-57,-570]],[[227625,162641],[156,-4]],[[227781,162637],[-161,-1600],[-220,-469],[-932,908]],[[227632,166164],[52,-1580]],[[272238,151333],[13,1]],[[272251,151334],[-13,-1]],[[273782,159826],[19,-5213]],[[273801,154613],[0,-3238]],[[273801,151375],[-1312,-39]],[[272489,151336],[-461,1867],[-228,-193],[-395,1936],[-191,1815],[-122,4385]],[[228287,156177],[-113,-1804],[-350,-1094],[6,-1266],[-410,861],[-516,2813],[-435,615]],[[227781,162637],[266,-4006],[-45,-1341],[285,-1113]],[[228232,162657],[-37,0]],[[276025,160973],[-123,-6439]],[[275902,154534],[-491,-343],[-1072,2],[-538,420]],[[275887,153137],[-181,-2634],[-305,-2456],[-117,-2922],[81,-1855],[-329,-2502]],[[275036,140768],[-16,1]],[[275020,140769],[-149,-433]],[[274871,140336],[3,-77]],[[274874,140259],[-199,601],[-388,-1467],[-445,274]],[[273842,139667],[-42,3489],[1,8219]],[[275902,154534],[-15,-1397]],[[274871,140336],[3,-77]],[[275036,140768],[-16,1]],[[275491,142866],[-401,-4416],[-362,-2015],[523,3742],[240,2689]],[[272251,151334],[-13,-1]],[[273842,139667],[-118,-686],[-512,-438],[-244,1966],[102,2255],[184,-1642],[296,-971],[166,920],[-246,1771],[-349,147],[-259,2833],[-188,3420],[234,599],[-210,869],[-82,-1356],[-127,1982]],[[267576,216906],[-181,85],[-177,1931],[-468,2015],[37,2409]],[[233545,583163],[470,-1],[0,7179],[546,-266],[367,-1412],[396,-7637],[-21,-1976],[288,-1125],[438,-310]],[[245498,568092],[407,1053],[402,-2818],[1697,311],[722,-2325],[297,673],[566,-549],[-1155,-3041],[-1287,-1848],[-816,-1938],[-809,-2999]],[[244050,541402],[0,-11146]],[[242235,539623],[262,1550],[296,-1143],[509,151],[748,1221]],[[245789,538230],[7,-8017]],[[244050,541402],[832,1558],[406,334],[672,1956],[311,-1206],[-365,-2639],[91,-1053],[-208,-2122]],[[245789,538230],[629,1480],[422,-1520]],[[246668,543604],[119,-491],[-601,-1771],[482,2262]],[[254361,554779],[386,-1343],[-682,-275],[51,-770],[-806,-2719]],[[253310,549672],[-198,1604],[-595,-3]],[[252517,551273],[244,1548],[576,1487],[1024,471]],[[252129,567028],[638,1076],[-743,-3451],[-872,-1784],[142,-551],[-582,-1037],[-166,1627],[1583,4120]],[[251344,546531],[128,1308],[718,2354],[11,-1815],[317,-391],[169,-3174]],[[253310,549672],[-511,-4124],[-63,2158],[-506,612],[-29,1852],[316,1103]],[[256406,536544],[1,-6252]],[[256407,530292],[-349,-1],[-4,-3236],[-348,4]],[[253820,544308],[634,-392],[509,-1456],[171,-1720],[674,-4188],[598,-8]],[[257802,530287],[-1395,5]],[[256406,536544],[265,725],[573,-1784],[182,767],[315,-1250],[489,2628],[901,2228],[763,318]],[[258234,522812],[-689,-1999],[491,3712],[-698,206],[-249,-2796],[-468,641],[-361,-1503],[-267,-2116]],[[255011,510600],[-34,-2168],[-371,-346]],[[254679,502703],[345,2967],[570,1286],[473,4865],[330,489],[143,1897],[235,8],[-572,-6085],[-44,-1741],[-319,-1641],[-155,-2072]],[[266291,525882],[-781,768]],[[265510,526650],[490,1336],[-121,1234],[374,-217],[304,-1926],[-266,-1195]],[[264534,537282],[192,-119],[-169,-2149],[-263,1387],[240,881]],[[264771,527003],[-281,50]],[[264490,527053],[-48,-2]],[[264442,527051],[-19,1612],[-346,2],[-1,1612],[-2086,1],[-3,1628],[-347,-5]],[[261640,541399],[788,243],[-207,-1485],[-13,-3740],[568,-816],[557,744],[103,-1308],[702,1641],[226,-1324],[215,-3356],[-110,-1628],[279,286],[140,-1587],[477,-2056],[-594,-10]],[[263518,523913],[579,-847],[-387,-679],[-192,1526]],[[264771,527003],[-281,50]],[[264442,527051],[-1182,1275],[-230,-2015],[-35,-1966],[-708,3125],[-1172,1762],[-254,-167],[-380,-2334],[-589,11]],[[263048,514103],[-754,-309],[-259,1733]],[[262035,515527],[484,1049],[-316,238],[-233,2467],[498,2960],[580,1140]],[[261221,512584],[62,1278],[458,1591],[294,74]],[[260809,522685],[124,-2642],[-358,-487],[234,3129]],[[259619,509895],[-266,698],[189,945],[77,-1643]],[[260516,504530],[-484,-12]],[[259309,504590],[9,2169],[372,1309],[375,-215],[515,4334],[144,-2358],[-222,-3318],[14,-1981]],[[260516,504530],[127,-152],[342,4121],[-150,-4480],[236,2087]],[[266792,512571],[208,-2838],[-312,125],[-6,-2477],[298,-1300]],[[266298,493143],[-35,-2126],[-276,-1071],[-460,39],[-182,-1553]],[[266573,485031],[572,4601],[580,621],[347,1131],[524,-1430],[297,-2969],[77,-2649]],[[269259,474627],[236,-3606],[-157,-1606],[-122,-4884],[-353,82],[-169,1029]],[[268236,461261],[-80,-1234],[-498,-1330],[-248,-2668],[-87,-2522]],[[267323,453507],[-275,1113],[-676,-161]],[[266582,447887],[26,6]],[[266536,447880],[30,6]],[[267323,453507],[-736,-4608],[-57,-1020]],[[276369,283639],[51,-2897],[155,-81],[-317,-2464],[-721,-1619]],[[275274,275950],[-210,1998]],[[278269,287761],[-76,264]],[[276907,282488],[-285,-1501],[-132,340],[309,2769]],[[277898,288714],[355,-1067],[-412,-2026],[-412,-58],[-522,-3075]],[[61945,65039],[-273,273]],[[61672,65312],[273,-273]],[[64909,48285],[736,-2763],[362,-391],[488,-1482],[554,-3197],[-24,-2084],[243,12],[55,-1737],[491,-2285],[-473,-3219],[-429,-1374],[-452,-185],[-605,-2496],[-405,-3858],[-627,2125],[-104,1501],[90,4218],[-292,5440],[-196,1716],[344,2264],[318,3335],[-188,1774],[-22,2014],[136,672]],[[55029,84761],[232,-813],[-50,-4138],[-317,-1725],[-532,857],[-340,1190],[-70,1626],[168,1566],[391,1358],[518,79]],[[53118,80469],[25,-1794],[-232,-683],[-127,-1603],[-75,1953],[409,2127]],[[61945,65039],[564,-356],[-456,-1900],[-525,1011],[-688,11],[160,2281],[672,-774]],[[61752,60573],[247,-292],[244,-1961],[-83,-859],[-351,-533],[-258,3325],[201,320]],[[62905,54516],[44,-1245],[-350,-600],[8,965],[298,880]],[[62814,62496],[341,-2480],[438,902],[263,-353],[321,-1916],[311,-600],[36,-1558],[-161,-1021],[-712,-1317],[-390,412],[-55,3221],[-459,617],[-171,1326],[59,2293],[179,474]],[[58972,75139],[409,-3431],[-19,-1219],[215,22],[315,-3032],[-404,-786],[-271,1419],[-580,-705],[-493,5220],[435,169],[393,2343]],[[996993,632383],[817,-1163],[91,-906],[715,-2639],[-620,1211],[-351,1710],[-879,1732],[227,55]],[[950,635992],[99,-1643],[-281,619],[182,1024]],[[7984,636500],[-23,-2275],[-307,-73],[-67,2101],[397,247]],[[8255,636861],[429,-729],[-176,-971],[-344,386],[91,1314]],[[8792,637399],[78,-1228],[-422,750],[344,478]],[[2944,637533],[354,-28],[110,-1138],[763,-730],[-472,-573],[-85,-1947],[-423,-823],[-299,1293],[443,1084],[-738,1715],[347,1147]],[[5406,633633],[-183,-598],[-329,1038],[-855,-380],[1116,1264],[255,737],[16,1940],[428,-501],[-230,-1193],[22,-1774],[-240,-533]],[[996377,638802],[311,-784],[-244,-853],[-67,1637]],[[7153,639094],[-123,-3160],[551,52],[-111,-1993],[-621,-692],[-248,-1116],[-149,1715],[-276,-2445],[-149,1181],[345,1636],[-141,1180],[574,-355],[-294,2579],[642,1418]],[[999634,639522],[333,-975],[-327,-1865],[-356,430],[-110,1602],[460,808]],[[8394,641129],[361,-843],[-150,-1151],[-356,-113],[145,2107]],[[993962,641501],[134,-1164],[-300,-1591],[4,-1345],[-561,-90],[-112,-1517],[-310,1266],[482,1562],[297,123],[366,2756]],[[15681,641867],[-88,-644],[552,-599],[499,441],[599,-277],[-1373,-851],[-663,468],[-397,-613],[-511,1117],[345,752],[244,-725],[793,931]],[[18717,646240],[354,-1060],[-312,-984],[-542,-452],[87,1790],[413,706]],[[13937,646817],[445,-1871],[-209,-1713],[-378,-563],[294,-1046],[-846,-838],[-954,-1616],[-415,665],[-937,-680],[1038,1800],[664,138],[756,1388],[293,1606],[-346,796],[247,1637],[348,297]],[[983194,648582],[-58,-2897],[-305,734],[-723,157],[686,1802],[400,204]],[[23639,652034],[278,-453],[-114,-1345],[-515,-1145],[-82,1788],[433,1155]],[[26147,655623],[247,-1330],[-168,-813],[-713,1495],[634,648]],[[980647,657671],[765,-147],[436,-2390],[462,-235],[-708,-1136],[-317,775],[-432,-1614],[-470,872],[166,1668],[-517,-336],[77,1140],[-541,-71],[552,1546],[527,-72]],[[28035,654543],[906,4624],[-94,1472],[528,2186],[746,66],[-272,830],[81,1805],[502,2014],[613,648],[608,-970],[-156,-2372],[-1194,-2675],[-517,-3466],[-1751,-4162]],[[36358,673363],[-391,-2467],[-196,1415],[587,1052]],[[34798,676523],[212,-3286],[496,2734],[387,-122],[70,-1818],[-314,-391],[-519,-2697],[580,1301],[182,-1595],[-674,-869],[-156,-1539],[-279,591],[46,-1877],[-401,265],[-1841,-3579],[-466,-1377],[-652,1241],[1054,2460],[1095,1206],[-225,1759],[468,337],[-151,1493],[938,464],[-874,524],[-341,2033],[352,1704],[1013,1038]],[[26198,724966],[755,-263],[-288,-1191],[-467,1454]],[[25148,736553],[-442,-1992],[-390,1137],[832,855]],[[39421,678834],[125,-1114],[-549,-30],[-135,746],[559,398]],[[36826,680387],[730,-1661],[-578,-1781],[-480,150],[-103,2381],[431,911]],[[38083,681762],[-169,-1483],[356,-62],[-384,-1861],[-335,2175],[179,1214],[353,17]],[[45572,685391],[687,-1487],[-645,-37],[-42,1524]],[[46952,694607],[167,-1890],[-230,-1028],[-301,1739],[364,1179]],[[42854,695877],[634,14],[266,-1568],[336,-4089],[372,437],[215,-1478],[-488,52],[-124,932],[-254,-1722],[-453,-779],[-609,441],[-826,-359],[-642,-1585],[-62,-1249],[-802,-1375],[-569,492],[-278,2147],[72,1348],[583,1047],[405,3984],[356,1064],[528,-514],[981,2495],[359,265]],[[48298,698203],[503,-1314],[-281,-971],[-458,2013],[236,272]],[[54720,699114],[34,-1697],[-430,-1611],[396,3308]],[[53769,699716],[-57,-3065],[-690,-2054],[15,3270],[392,-758],[86,2465],[254,142]],[[52387,701641],[20,-2213],[-523,1390],[503,823]],[[51367,702388],[94,-1872],[270,219],[342,-2313],[-186,-1094],[-922,1412],[40,2470],[362,1178]],[[52632,703466],[225,-1137],[-483,351],[258,786]],[[56429,729876],[0,-164]],[[56429,729712],[1,-1271]],[[56430,728441],[-377,-902],[0,-1576],[-688,44],[0,-1691],[-884,0],[-2,-1609],[-853,-47],[-11,-3153],[262,-17],[12,-6313],[-174,-1752],[844,0],[1,-4664]],[[54560,706761],[-221,3583],[-466,487],[-226,-1331],[-227,543],[-128,-1791],[-941,-1953],[-502,-2559],[-203,1961],[-156,-2246],[-321,-160],[-388,1335],[-213,-1550],[-895,-1527],[-527,87],[101,2154],[312,2133],[-634,501],[-315,-1612],[37,-2336],[-533,-3433],[-423,296],[235,-2134],[-319,-953],[-327,1503],[-197,-2494],[-614,575],[-125,3742],[-386,953],[-195,-965],[304,-1471],[136,-4091],[-323,1731],[-86,-1650],[-582,-104],[-228,2389],[-557,961],[-44,-1927],[533,-1473],[-912,-2585],[204,4150],[-78,1484],[292,977],[935,318],[218,2402],[379,1261],[396,-106],[-126,1804],[846,4218],[1241,3632],[1202,1198],[763,131],[637,709],[-430,-1918],[671,-3199],[315,-565],[-272,3532],[663,-283],[157,-1434],[615,-516],[79,1328],[-802,1925],[-146,1183],[588,5125],[1474,4921],[1403,2354],[1201,3895]],[[131840,702693],[477,-1814],[-260,-3591],[-338,3444],[121,1961]],[[133475,712615],[472,-2306],[365,-3908],[-109,-3956],[-237,-2723],[-412,-1256],[-725,1926],[513,3110],[-666,-3077],[-841,2748],[552,2915],[-269,716],[-19,1677],[520,1253],[-148,1905],[1004,976]],[[60957,762087],[579,2152],[206,3039]],[[61742,767278],[1862,118],[1,-5313],[-2648,4]],[[51410,765657],[-524,-4199],[-535,-542],[51,2808],[1008,1933]],[[50362,766039],[-742,-1613]],[[49620,764426],[-84,1634],[826,-21]],[[55886,766355],[508,-1408],[386,-3700]],[[56780,761247],[192,-1494],[-550,-1757],[-437,893],[-941,6038],[-709,1488],[71,1756],[-304,-417],[-190,-2035],[-311,-514],[-283,1932],[-661,238],[-84,2398],[-286,617],[-1293,-3499]],[[50994,766891],[-530,-524]],[[50464,766367],[-1,3048],[447,4],[154,1512],[-5,3289],[475,13],[-1,1619],[475,67],[154,1547],[2,3270],[484,-4],[7,3225],[637,43],[12,3203],[445,89],[14,3067],[183,1579],[477,25],[0,3225],[492,-12],[187,1563],[7,3225],[507,4],[6,1610],[466,16],[198,3194],[2957,4],[8,-1469],[509,-22],[-8,1633],[498,0],[-1,1661],[983,-7],[4,-1637],[3363,-86]],[[64599,804865],[7,-22904],[-456,-40],[-3,-1619],[-951,22],[-1,-1609],[-472,19],[-265,-3261],[-958,50],[-2,-1622],[-474,33],[7,-3299],[225,-22],[8,-3864]],[[61264,766749],[-1094,-1968],[-1643,-2597],[-487,694],[-101,1404],[-594,1396],[199,3649],[-345,-1592],[-443,-553],[-69,-2804],[-801,1977]],[[61034,724293],[684,-546],[-930,-219],[246,765]],[[56429,729876],[0,-164]],[[71634,804882],[114,-1587],[1,-6528],[-371,-12],[-9,-6508],[-349,-18],[4,-6265],[-357,2],[-2,-1646],[-479,-10],[4,-3277],[-474,-9],[9,-1643],[-817,-49],[14,-3240],[-942,-29],[6,-3278],[148,-20],[-1,-6355],[154,-1634],[923,0]],[[69210,762776],[7,-3471],[-452,-1843],[-737,-1056],[-774,-256],[-138,-1018],[-764,-799],[5,-5901],[-324,-916],[-689,-542],[-207,-2415],[-364,-3],[218,-1278],[-365,-813],[-334,533],[-169,-1391],[-664,236],[-689,-3490],[-376,-3218],[1106,-40]],[[63500,735095],[-534,-3229],[-497,991],[-283,-2643],[-165,1250],[-876,-3730],[-514,1840],[-92,-1738],[-418,-1466],[296,-1221],[-476,-272],[-290,1208],[-837,-1435],[-237,-1294],[834,518],[-97,-1512],[-773,886],[47,-1061],[-586,328],[-1007,-4054],[729,1416],[657,-1055],[-889,-5668],[-359,2476],[4,-2388],[-569,564],[-201,-1660],[-1171,-915],[-207,-1647],[-123,1992],[-209,-168],[97,-2311],[-194,-2336]],[[56430,728441],[767,-812],[-200,4018],[209,1634],[848,4197],[641,1528],[417,1983],[585,1663],[449,-1863],[-119,2913],[-249,-206],[-34,2059],[291,6723],[197,1491],[338,172],[-417,2930],[210,2814],[594,2402]],[[61742,767278],[-122,1251],[-356,-1780]],[[64599,804865],[4288,-12],[2747,29]],[[65700,709070],[-505,743],[539,1338],[-34,-2081]],[[70400,724037],[-203,-1087],[-508,-19],[711,1106]],[[68718,724702],[-248,-2013],[-522,-1694],[196,2303],[574,1404]],[[69851,724354],[-436,-1711],[-362,958],[373,1309],[425,-556]],[[72264,735888],[653,-276],[179,-1133],[-749,-602],[-369,-1913],[-212,2137],[498,1787]],[[71588,750055],[467,-1112],[219,-1969],[-712,1537],[26,1544]],[[72121,750653],[752,-1609],[143,801],[402,-896],[-318,-1580],[497,-23],[268,1873],[865,-1804],[-645,-1899],[255,-25],[-10,-2224],[879,340],[-526,-3652],[-487,209],[-629,1356],[-203,-635],[516,-1217],[-217,-2411],[-309,-183],[-529,1368],[-252,-544],[428,-942],[-369,-902],[-532,226],[-639,-2935],[-448,348],[323,-1679],[-994,-4198],[-692,-436],[210,1779],[607,2267],[-321,-122],[144,1970],[-434,-1663],[-185,436],[447,2279],[-692,794],[-670,-678],[120,-1421],[343,1135],[575,213],[-522,-3914],[-671,1507],[-37,3723],[-598,1836],[85,2427],[257,1668],[757,2434],[973,102],[64,-1861],[627,-4463],[-146,2063],[78,2795],[-204,1554],[706,-247],[-927,1791],[7,1470],[588,1559],[420,-1165],[70,-2442],[173,2478],[786,-908],[-90,1984],[526,-1432],[-620,2635],[25,690]],[[72294,752633],[298,-243],[385,-2001],[-772,1279],[-524,168],[436,1600],[177,-803]],[[74768,758553],[203,-1495],[404,602],[-150,-1955],[513,1173],[-64,-2164],[-263,-1096],[-683,1715],[168,-2076],[-474,81],[-333,-1022],[-38,2484],[-167,-2623],[-421,-227],[-1,-1319],[-1063,2147],[-115,1830],[591,-260],[-305,1148],[155,946],[718,-477],[-71,2281],[416,1422],[495,-330],[341,-2521],[144,1736]],[[73815,761335],[866,1186],[-377,-3031],[-416,471],[-73,1374]],[[72145,766667],[-257,-165],[-279,-2562],[-648,-1819],[-613,-91],[-175,-2212],[-343,-44],[106,-2403],[-387,-650],[216,-848],[-603,-2221],[-49,-1387],[-356,1779],[54,-1574],[-325,275],[-478,-1769],[-794,237],[-110,-2260],[-820,-2014],[-130,-1038],[-600,883],[68,-2348],[-395,-579],[8,-1505],[-775,384],[-34,-2432],[-520,654],[-902,-2724],[-22,-1053],[572,813],[-196,-1902],[142,-997]],[[69210,762776],[1368,8],[-4,1620],[1015,10],[-8,2250],[405,-14]],[[71986,766650],[159,17]],[[127092,749125],[-107,-525]],[[126985,748600],[107,525]],[[125289,753706],[-418,-538]],[[124871,753168],[418,538]],[[124853,753145],[-548,-606],[-395,-1297],[-254,1284]],[[123656,752526],[58,1721]],[[123714,754247],[93,682]],[[123807,754929],[27,-1188],[797,113],[222,-709]],[[124293,757146],[750,-2547],[-1015,763],[-163,962],[428,822]],[[124853,768717],[212,-1953],[1164,-2450],[371,-2181],[919,-3383],[-234,-797],[807,-4371]],[[128092,753582],[-944,-4191]],[[127148,749391],[-329,-1604]],[[126819,747787],[-267,-754]],[[126552,747033],[-348,2791],[-601,2743],[-35,3375],[303,711],[-120,1531],[-569,-3645],[-842,2824],[-590,340],[-225,2827],[-376,2538],[-7,5750]],[[123142,768818],[1711,-101]],[[122508,768865],[608,-35]],[[123116,768830],[93,-1237],[-284,-3190],[-328,1968],[-89,2494]],[[125029,711856],[427,56],[-445,-1309],[18,1253]],[[123927,716625],[1,1610]],[[123928,718235],[-1,-1610]],[[130216,719801],[107,-2063],[755,-2315],[-337,-626],[254,-2100],[-618,-805],[-299,1242],[210,718],[-488,1319],[-280,-690],[-164,3076],[325,413],[163,1718],[372,113]],[[129378,721918],[250,-1134],[-116,-2167],[-589,-788],[-541,1882],[182,1807],[814,400]],[[130831,716911],[-489,1839],[73,2204],[-143,1693],[379,-777],[303,-2037],[326,197],[294,-2758],[-436,-1557],[-307,1196]],[[128816,728558],[1033,-4174],[-771,-1663],[-361,265],[62,2359],[-148,3100],[185,113]],[[126153,727422],[-66,1104],[447,-558],[-459,-4949],[-272,-4761],[207,396],[-145,-3602],[-197,615],[-111,3651],[-136,-5807],[-278,1260],[-135,3642],[146,2920],[525,49],[-191,2101],[-591,341],[100,963],[-331,2219],[-28,2428],[338,-758],[185,2186],[563,-1046],[429,-2394]],[[126097,733937],[1280,-1597],[902,-116],[343,-1458],[135,-4906],[-200,-1131],[-500,1925],[-370,3180],[208,-4438],[379,-383],[50,-1490],[-321,-1688],[-659,1031],[14,-897],[-617,-308],[-202,4527],[181,2218],[-303,119],[-132,1682],[-636,2448],[448,1282]],[[130329,738612],[337,-2561],[-337,-2230],[902,-864],[-219,-3192],[699,-1259],[104,-3850],[709,243],[1430,-3886]],[[133954,721013],[215,-2256],[-268,-1574],[-450,60],[-522,-1243],[216,-2328],[-366,57],[-534,1418],[-527,-1313],[-52,-1739],[-416,-1188],[273,-1077],[-70,-1917],[-375,-1020],[-347,1785]],[[130731,708678],[461,1375],[54,2694],[202,40],[-10,3932],[574,625],[-464,526],[-186,2188],[-505,719],[-71,1280],[-436,1390],[254,2614],[-501,-62],[-115,1369],[-778,1724],[-462,2944],[355,-607],[-84,2467],[-160,-1289],[-1098,1537],[-631,1283]],[[127130,735427],[606,1116],[481,-957],[392,2691],[952,1778],[768,-1443]],[[126819,747787],[-267,-754]],[[120135,747679],[-911,-75]],[[119224,747604],[-113,-8]],[[119111,747596],[-67,-5]],[[119044,747591],[175,3135],[916,-3047]],[[118825,752542],[435,-1596],[-323,-3068],[-279,1218],[167,3446]],[[127130,735427],[-12,1829],[1070,643],[-784,626],[-296,2733],[132,1559],[-542,1078],[246,1825],[1153,-2770],[-708,2710],[-429,722],[25,2218]],[[127092,749125],[56,266]],[[128092,753582],[283,-2841],[560,-2991],[879,-6389],[515,-2749]],[[124853,753145],[18,23]],[[125289,753706],[-147,-677],[908,-5880],[23,-2256],[-213,92],[-809,7066],[78,-1904],[-258,510],[590,-4994],[507,-2211],[51,-2144],[-521,218],[645,-2298],[-310,-1473],[-532,1666],[52,-3416],[-404,-716],[-196,-1446],[-545,-1275],[-190,2104],[51,2518],[366,839],[-3,1567],[-686,5962],[70,2065],[-287,4181],[127,722]],[[121078,751604],[-275,-1102]],[[120803,750502],[345,-330],[594,2364],[321,432]],[[122063,752968],[1024,-1345],[153,-1323],[-236,-1608],[-621,1179],[756,-2767],[-774,-493],[-453,1349],[-1420,2800]],[[120492,750760],[-345,-2996]],[[120147,747764],[-1106,4388],[167,2241],[347,-765],[278,1062],[445,-194],[426,1507],[913,-2103],[-539,-2296]],[[121729,756782],[0,-124]],[[121729,756658],[0,124]],[[123714,754247],[-96,-1670],[-340,1775],[-157,4018],[686,-3441]],[[120619,759891],[-327,4038],[-329,1992],[900,1274],[-791,545],[-394,2927],[235,-3862],[-190,-2032],[-886,1580],[-102,2460],[-279,-1505],[-689,1343],[-390,-1137],[761,-460],[670,-1418],[-346,-493],[775,-1580],[-470,-1770],[538,847],[341,-512],[456,-4298],[-640,-1196],[-13,909],[-620,-1129],[-299,1069],[42,-2620],[-717,2665],[-654,322],[-1471,4078],[-935,3744]],[[114795,765672],[1165,1936]],[[115960,767608],[1943,4721],[681,110],[264,1748]],[[118848,774187],[1665,-1619],[530,-1279],[325,-2198],[-479,-4537],[582,-819],[408,-1530],[232,-2152],[-489,-12]],[[121622,760041],[48,-2295],[-405,960],[-824,-873],[178,2058]],[[122913,775799],[-901,-103]],[[122012,775696],[-90,222]],[[121922,775918],[-935,7001]],[[120987,782919],[676,1289],[684,-1897],[571,-2466],[-201,-2534],[196,-1512]],[[121729,756782],[0,-124]],[[123142,768818],[-26,12]],[[122508,768865],[-496,6831]],[[122913,775799],[197,-1218],[722,-594],[60,-1056],[546,-1130],[415,-3084]],[[121922,775918],[-144,-2315],[-359,301],[514,-2662],[-51,-2482],[722,-6540],[-70,-1017],[303,-3963],[-99,-2100],[-512,-56],[-321,1677],[-283,3280]],[[118848,774187],[35,3836],[479,-24],[182,1767],[-318,752],[1118,1161],[643,1240]],[[120877,738496],[390,-2216],[136,-2451],[-166,-1458],[-605,-381],[12,6413],[233,93]],[[122849,727161],[-438,-147],[77,1437],[-514,245],[-60,2351],[447,1679],[-481,1224],[98,2801],[-465,-185],[-451,2135],[588,60],[-327,842],[146,1990],[629,564],[-76,-1197],[394,257],[557,-1676],[491,7],[-82,-2391],[464,-6886],[222,-4906],[-140,-7130]],[[123927,716625],[-394,1362],[-697,5371],[278,1647],[-447,-368],[182,2524]],[[119111,747596],[-67,-5]],[[119342,746836],[-118,768]],[[120135,747679],[12,85]],[[120492,750760],[1454,-3708],[395,-1834],[350,1018],[503,16],[258,-5147],[-689,-337],[-264,1058],[-1794,4643],[718,-3300],[32,-2583],[-442,-1626],[-366,435],[-451,2590],[538,-1592],[-959,3723],[85,790],[-518,1930]],[[40063,839903],[771,-536],[-860,-2772],[-124,3412],[213,-104]],[[40964,844347],[884,-2223],[78,-1915],[441,-2171],[-39,-1740],[-705,2419],[-1774,1536],[91,1825],[445,2301],[579,-32]],[[42066,848667],[903,-976],[562,-1663],[-706,-1821],[-339,-2285],[-563,-2],[-711,2401],[-621,812],[15,1297],[651,1761],[809,476]],[[46154,848898],[568,1],[10,-1610],[528,1],[19,-1676],[523,-12],[-3,-1547],[3176,0]],[[50975,844055],[-396,-2136],[-20,-6445],[-125,-25],[-7,-6437],[383,-5],[-10,-4748],[1058,-39]],[[51858,824220],[180,-1451],[604,-215],[-1456,-2220],[-979,-2588],[-900,-571],[-1213,-1602],[-1235,662],[-494,709],[-1854,-1999],[-659,648],[-690,-2756],[-940,669],[31,-2392],[-390,-2165],[-92,-1943],[-1046,-1523],[-745,776],[-936,-1159]],[[39044,805100],[-171,1112],[663,830],[-1128,3201],[-12,-2176],[-520,182],[-235,2567],[116,849],[-614,571],[-206,2123],[418,1187],[-482,1394],[-545,-1187],[-96,2644],[1074,795],[-527,599],[-413,1924],[1381,582],[-440,3023],[250,2550],[1109,5288],[620,2101],[570,-118],[933,3976],[823,-896],[964,-4031],[-209,4581],[-456,1793],[11,1197],[848,640],[79,1725],[725,1785],[524,-1399],[777,464],[611,2670],[668,1252]],[[46897,791982],[260,-1285],[-269,-665],[9,1950]],[[36192,795959],[181,-1952],[581,363],[562,-947],[-124,-4413],[449,-2402],[-1266,-1162],[-486,-2126],[-685,2000],[-493,-131],[-1055,2670],[-324,-89],[-643,1570],[-207,2198],[273,857],[1285,-624],[56,1282],[1009,2111],[904,-187],[-17,982]],[[17304,799216],[127,-1745],[803,-2158],[617,-183],[448,-1365],[-1029,65],[-1000,3083],[-322,373],[356,1930]],[[39655,805231],[135,-1106],[641,589],[-100,-1482],[648,41],[525,-808],[140,-1889],[-483,-1833],[-425,-534],[212,-1308],[-375,-571],[-318,-2845],[-422,135],[-776,2413],[439,1980],[-611,-774],[-640,997],[1251,2977],[-199,1359],[401,589],[-43,2070]],[[49028,771386],[-724,158],[-208,-907],[-383,2903],[195,2895],[687,2210],[-440,2484],[-274,3082],[-346,1471],[-415,3726],[137,1138],[-768,3115],[394,1259],[285,3791],[-130,577],[-402,-4189],[-433,-1001],[310,-2534],[-69,-3036],[-405,-1085],[-1628,-2447],[-1521,-851],[-1011,789],[-258,2086],[267,429],[-760,1956],[-694,3087],[-88,1177],[383,1597],[-109,1291],[397,379],[-183,1458],[364,50],[277,1573],[410,342],[229,1752],[701,-118],[-15,-3249],[424,240],[608,2498],[-385,1562],[-1050,843],[630,147],[399,1002],[-599,266],[-481,-1345],[-126,3490],[-457,-1833],[194,-1308],[-1242,-569],[-216,1520],[-537,-627],[-175,927],[-743,-457]],[[51858,824220],[3536,-32],[11,1610],[2034,-6],[-4,1618],[4083,202],[1,-1838],[5892,-29],[3388,4],[-14,1716],[962,60],[3,1570],[903,-11],[192,1616]],[[72845,830700],[-2,-16190]],[[72843,814510],[-1303,45],[-15,-5422],[125,-21],[-16,-4230]],[[50464,766367],[-102,-328]],[[49620,764426],[-138,-1468],[-1062,-1905],[-96,1293],[-1035,446],[861,1205],[617,1650],[-307,160],[-106,3181],[674,2398]],[[91002,847056],[9,-3988],[146,-1795],[7,-11469],[-1548,27],[56,-1647],[46,-12768],[-726,13],[-1,-927],[-3493,32]],[[85498,814534],[-2027,-32],[-140,995]],[[83331,815497],[-404,601],[-736,-459],[-672,-2022],[-282,-2604],[-1336,126],[-327,2166],[-16,-1517],[-1058,-1636]],[[78500,810152],[-3,1152],[-1003,21],[-4,3165],[-4647,20]],[[72845,830700],[0,8044]],[[72845,838744],[3088,-82],[11,1347],[6637,9993],[4096,-25],[11,2587],[2946,186]],[[89634,852750],[1354,59],[14,-5753]],[[86653,868811],[757,-1569],[2146,14]],[[89556,867256],[86,-6324],[-8,-8182]],[[72845,838744],[1580,8236],[-1,2534],[-535,-83],[-516,926],[-152,2433],[36,3161],[1704,93],[23,3109],[497,-4],[22,3291],[619,95],[96,1432],[589,58],[151,-985],[624,-419],[1536,6612],[4539,-401],[2996,-21]],[[85498,814534],[-38,-10730],[-759,-83],[1,-2090]],[[84702,801631],[-828,17],[8,1987]],[[83882,803635],[-434,1931],[-461,-308],[-1061,1669],[-902,2471],[707,3049],[1129,2748],[471,302]],[[97244,787641],[-970,-3439],[64,996],[906,2443]],[[87222,789210],[-180,-1772],[-422,-410],[602,2182]],[[86635,788749],[223,2200],[233,-961],[-456,-1239]],[[86658,791573],[-368,-3115],[-331,1166],[699,1949]],[[89188,794370],[360,-14],[-242,-1523],[503,637],[-807,-2688],[-814,-3927],[157,-1241],[-672,-1392],[-694,-249],[129,1479],[1250,4306],[686,2953],[144,1659]],[[86770,795107],[-72,-1859],[-323,817],[395,1042]],[[90857,797012],[511,-366],[-56,-1276],[665,500],[124,-712],[-949,-1371],[-533,-1467],[-219,828],[596,1566],[-651,-377],[-24,893],[536,1782]],[[87021,792777],[237,3392],[601,-294],[-413,-4995],[-425,1897]],[[84911,786500],[233,323],[68,9012],[-286,661]],[[84926,796496],[584,1622],[519,-1019],[524,1881],[285,-1347],[30,-1922],[-996,-2869],[605,-1030],[-718,-2215],[-215,-2677],[-633,-420]],[[92921,798974],[-9,-545],[-1146,-2071],[-222,1208],[1377,1408]],[[88340,800844],[382,-576],[-511,-770],[129,1346]],[[84952,800675],[-2,141]],[[84950,800816],[2,-141]],[[86236,802132],[283,-1843],[-420,600],[137,1243]],[[84952,797243],[-2,3142]],[[84950,800385],[697,2105],[-36,-2998],[388,2501],[244,-2671],[-389,-1481],[-418,814],[-484,-1412]],[[86677,805210],[324,-2117],[-624,-286],[300,2403]],[[87480,803390],[-299,-138],[-586,2075],[324,2591],[505,2085],[122,1742],[-770,-3524],[-194,-1549],[-274,1236],[-413,-4415],[-943,-1986]],[[84952,801507],[-250,124]],[[91002,847056],[976,234],[64,648],[2681,18],[1,-1651],[1594,-292],[4038,-37],[350,-2273],[-454,-2972],[436,-1269],[-270,-2840],[731,-302],[296,1951],[1174,-336],[-16,-1588],[538,-65],[-11,-1550],[467,-24],[-41,-6434],[413,-697],[4,-4218],[2304,16]],[[106277,823375],[-1,-28115]],[[106276,795260],[-590,822],[-1600,-31],[4,1610],[-3846,-34],[-2014,-8871],[13,-855]],[[98243,787901],[-1022,2714],[47,774],[-1004,-33],[-277,1650],[-683,426],[369,3141],[18,2628],[-611,-1565],[-178,-1401],[-660,-2022],[-456,1505],[-715,1071],[-573,-359],[943,3800],[-1115,-764],[259,1691],[-925,-1558],[532,2101],[-1009,-888],[-678,62],[-94,801],[1014,956],[446,1115],[-1049,-704],[-517,1833],[318,3213],[1053,57],[-177,903],[-745,-283],[-1115,-3389],[-359,1426],[-193,-1467],[-586,-1116],[-377,408],[6,3472],[-279,-1340],[36,-2946],[-407,-423]],[[71986,766650],[159,17]],[[71670,777076],[234,-1111],[-578,-409],[344,1520]],[[79473,776784],[-278,-2074],[90,2387],[188,-313]],[[75949,796147],[-337,-1236],[224,2635],[113,-1399]],[[84952,800675],[-2,141]],[[84952,801507],[-2,-1122]],[[84952,797243],[-26,-747]],[[84911,786500],[-1246,807],[241,1474],[-372,-167],[-386,-2037],[-206,3701],[-28,-2396],[-481,-1666],[-173,-1732],[-183,2620],[-198,-5326],[-848,3357],[-38,-1545],[419,-800],[-370,-1104],[-68,-1569],[-635,-858],[207,3841],[-673,-5212],[-363,1475],[21,-2088],[-388,-20],[-756,-4011],[-73,2111],[-348,-2231],[-660,934],[-173,-858],[-798,-888],[-19,1049],[-622,715],[205,2741],[545,1203],[719,24],[16,1316],[737,963],[-66,842],[764,2927],[-371,25],[-1227,-2967],[-387,246],[-630,2275],[461,4880],[786,3381],[112,2715],[231,475],[102,3025],[-411,3227],[967,1254],[994,2799],[846,1832],[515,-1977],[449,-352],[796,1052],[345,-814],[1491,-887],[199,-646]],[[78500,810152],[-529,-2655],[-888,-709],[-892,-2927],[269,-2258],[-403,43],[-549,-1340],[-154,-1303],[-581,-1608],[-230,-3803],[-475,-1478],[-1034,410],[707,-1448],[308,-1772],[-382,-2913],[-286,-680],[-1150,-274],[-158,-989],[796,2],[-164,-2224],[-453,-1067],[-563,295],[228,1314],[-337,1411],[32,-1983],[-397,-2577],[-671,-197],[278,-1966],[-1055,-1700],[-60,-2764],[-263,-488],[166,-2860],[276,1050],[1025,35],[413,-1681],[604,-1165],[58,-1233]],[[110730,776964],[-495,571],[-783,1959],[461,898]],[[109913,780392],[89,-474],[542,2604],[-420,3603],[332,1618],[530,-2532],[165,177],[-480,2635],[-360,838],[-174,-1871],[-464,-2242],[-1472,-2407],[-1544,735],[-1623,2726],[527,2089],[-370,3153],[-376,-856],[438,-1616],[-624,-1272],[-2761,2302],[-2697,-711],[-928,-990]],[[106276,795260],[0,-1588],[1342,-1612],[173,1644],[1330,-2348],[802,2853],[1723,322],[-346,-4919],[404,-1740],[965,-1648],[226,-2564],[2839,-9770],[299,-4811],[-73,-1471]],[[114795,765672],[-74,1821],[-694,2541],[-828,1292],[164,1473],[-569,-631],[-2064,4796]],[[106279,946180],[-1,-49413]],[[106278,896767],[-929,-2456],[-130,-1601],[-1321,-3359],[-906,419],[-784,-1692],[-450,407],[-348,-3372],[-325,-1449],[-654,-436],[-1189,-2953],[59,-3793],[-686,-1989],[-822,655]],[[97793,875148],[-204,2150],[357,3326],[-316,718],[514,846],[-159,1080],[-964,-156],[-338,-900],[-1243,1394],[-887,-1195],[-579,221],[-638,-953],[-287,1202],[374,853],[-1289,1884],[-166,1203],[399,1801],[-515,869],[-625,-618],[-460,-1403],[-1060,-1320],[-1074,-10],[-601,-1159],[-3102,6],[49,-11364],[273,317],[853,-2473],[548,-2656]],[[50975,844055],[10,4776],[491,30],[-58,4948],[549,10],[4,1425],[1102,13],[-16,1635],[486,23],[-4,1682],[645,-27],[-89,4840],[-627,-25],[-61,12985],[569,33],[-9,3217],[558,13],[-5,6310],[540,-19],[-5,4867],[-576,-8]],[[54479,890783],[-5,8106],[1752,-15],[3,3093],[2984,34],[5,6544],[2342,37],[1,-3290],[1183,54],[7,3236],[1193,13],[-3,-1577],[601,61],[1,-1630],[1167,-18],[135,4836],[1805,-29],[9,2778],[1841,-6],[168,1643],[-10,6647],[-438,-21],[-10,1684],[-1233,59],[3,4809],[170,1665],[-625,-6],[17,1605],[-634,-27],[7,1645],[-620,11],[151,4212]],[[66446,936936],[6419,-3],[8315,-30],[7299,19],[3887,5],[-10,9096],[4663,309],[4791,-300],[4469,148]],[[97793,875148],[-1158,-1861],[-1436,-203],[-263,-1097],[-803,-486],[-476,-1290],[-577,796],[-731,-1428],[-434,44],[-567,-2104],[-1792,-263]],[[106278,896767],[-1,-73392]],[[79659,983425],[-186,-1457],[-394,1372],[580,85]],[[47581,977020],[610,-136],[635,1731],[830,26],[1623,2411],[1469,3493],[883,527],[-302,-3710],[454,-2621],[-62,3461],[379,971],[-494,2434],[-541,45],[1261,3273],[1370,1209],[-524,-1007],[415,-852],[3298,1404],[1420,2118],[705,1934],[1322,4592],[408,808],[628,-1392],[914,-472],[118,-1342],[1297,-97],[154,-1576],[-590,-1821],[-1263,-1269],[607,-390],[-400,-1319],[1303,-54],[455,606],[-99,1673],[703,1368],[285,1695],[248,-1304],[705,927],[682,-1755],[-272,-1974],[1375,-2190],[693,2096],[1211,87],[987,692],[971,-831],[278,-2575],[299,2682],[990,-1071],[-562,-2518],[6,-1533],[1009,-524],[-1483,-682],[2475,170],[-500,-2281],[1552,-378],[327,-1080],[240,1607],[1279,271],[-194,-2495],[904,1574],[844,524],[665,1465],[1963,-425],[887,-1703],[687,50],[281,-1515],[1082,464],[877,-2131],[2068,-1578],[1438,784],[1494,-1037],[432,637],[1637,-3240],[1773,-386],[359,911],[3399,1866],[1397,-1343],[1103,-2166],[387,-1504],[1266,-1052],[1132,-2959],[449,853],[588,-630],[-1,-21361]],[[66446,936936],[-2,524],[-2610,-22],[-7,1899],[-2541,-186],[-16,1669],[-3117,28],[-4016,91],[-18,1253],[-1129,59],[26,-1251],[-2705,21],[-18,1269],[-1182,3],[11,-1280],[-1329,46],[-6,1283],[-2021,165],[0,-1272],[-4957,-97],[16,-3782],[-2411,69]],[[38414,937425],[-1696,2109],[-331,1546],[-1319,2644],[530,804],[402,3101],[39,5653],[2425,-416],[2905,1305],[906,1070],[1221,2916],[1185,4350],[538,5905],[-330,649],[1231,3476],[365,2385],[846,2065],[658,2578],[522,-1587],[-930,-958]],[[54479,890783],[-578,0],[-10,-1617],[-6226,43],[-4789,-36],[-2,3199],[-592,6],[1,3250],[-769,-9],[7,6455],[-205,26],[-5,6430],[-226,1957]],[[41085,910487],[1578,205],[202,-2555],[-310,-1226],[120,-2178],[-264,-1027],[216,-1764],[590,-1196],[375,540],[1137,-445],[966,699],[320,-1177],[667,-159],[982,798],[546,-2005],[189,2045],[811,3513],[796,-959],[609,527],[-496,1902],[-1494,1020],[-695,-1271],[195,3234],[-840,3465],[-836,788],[-421,2411],[385,1580],[446,30],[876,-3094],[-153,-2498],[447,-1913],[932,-1943],[1113,1832],[1075,-3147],[1510,331],[132,1770],[-332,2163],[-931,-161],[-461,1240],[-884,-262],[-254,-1861],[-716,-284],[-1100,3517],[232,3055],[871,1517],[-974,1716],[-1121,-1075],[-1025,-154],[-390,1249],[-528,-563],[-2240,1824],[-398,5492],[-677,3577],[-1096,2080],[-2353,5735]],[[46592,855663],[132,-1524],[-911,379],[779,1145]],[[20847,858392],[154,-1917],[1767,-2170],[1395,2515],[527,-233],[506,-1478],[154,-2243],[1092,-1044],[302,-1308],[1484,-327],[899,-741],[-444,-2851],[-1263,664],[-756,-2937],[125,-925],[-598,-398],[121,1508],[-580,2032],[-993,769],[95,1587],[-981,2254],[-776,1164],[-1179,-1499],[-406,-1269],[-853,1089],[-307,2233],[515,5525]],[[40155,909679],[-378,-812],[-1697,-1707],[2075,2519]],[[50525,879793],[-440,-60],[-575,-2906],[-898,82],[-597,-1411],[-710,-398],[-195,-1163],[-840,-1617],[-253,-2635],[-445,-1194],[-161,3173],[-951,2836],[-459,-1140],[679,-1515],[9,-1792],[-1367,2870],[-2018,-58],[-2018,-2251],[-808,901],[-2508,1784],[-688,2790],[278,1486],[-107,1445],[-748,1760],[-608,2843],[665,-528],[574,1254],[301,1828],[875,-777],[601,-2903],[499,-323],[641,1345],[-382,829],[-829,195],[-166,-711],[-668,2577],[-1942,1613],[-1533,482],[-1421,2817],[-464,461],[785,2442],[579,22],[-1,1594],[845,1740],[325,-909],[992,2482],[419,1887],[1043,1672],[644,-1027],[1215,98],[319,783],[-1042,1554],[505,1845],[720,1193],[1099,506],[107,-679],[853,2834],[830,668]],[[46154,848898],[640,3384],[127,1617],[836,-1042],[-393,-958],[1743,358],[1079,995],[1027,4968],[-512,5797],[-74,2808],[-782,2761],[-730,205],[138,2075],[1090,-246],[769,2178],[52,1989],[-320,1964],[-701,1681],[382,361]],[[129336,693546],[270,-1010],[-141,-1955],[-386,2864],[257,101]],[[133465,694933],[144,-1619],[-433,-1116],[-420,1401],[709,1334]],[[129051,698432],[384,-3029],[-187,-665],[-381,868],[234,1060],[-50,1766]],[[128271,699419],[265,-2151],[-73,-1620],[898,-5179],[112,-1526],[-465,199],[-650,4105],[-450,3522],[52,2603],[311,47]],[[132791,699517],[330,-1553],[-34,-2741],[-744,304],[318,2136],[-199,1261],[329,593]],[[127590,701351],[341,-1447],[-98,-960],[-490,-120],[13,2204],[234,323]],[[126996,702605],[286,-601],[-587,-1676],[301,2277]],[[127349,703974],[-507,-707],[254,1440],[253,-733]],[[126550,705223],[399,-333],[-240,-1814],[-293,1014],[134,1133]],[[127577,705503],[193,-1280],[-213,-947],[-253,1520],[273,707]],[[126975,710441],[673,-1209],[-593,136],[47,-1542],[-470,1638],[343,977]],[[127739,711890],[119,-2595],[-287,1329],[168,1266]],[[132952,712558],[-518,-394],[514,1289],[4,-895]],[[127378,716539],[386,-260],[-15,-2495],[-236,369],[-705,-1255],[-241,-1211],[-256,709],[518,3076],[549,1067]],[[126879,720141],[451,-580],[749,57],[385,-2788],[-192,-1469],[384,-1822],[434,266],[497,-1761],[457,-2620],[47,-3196],[267,734],[169,-1960],[371,-1718],[-850,2044],[-552,-1434],[684,586],[160,-2342],[298,487],[445,-2791],[-464,-922],[454,-352],[205,1480],[89,-3147],[-430,-827],[396,-512],[100,-2502],[-397,-59],[420,-1193],[-182,-2479],[-613,702],[-449,4336],[-562,-35],[258,2640],[-329,-710],[132,1822],[-300,-502],[-724,2251],[-726,264],[28,1948],[514,-22],[-360,1667],[235,2818],[-438,-873],[-449,796],[644,3530],[-239,1458],[-150,5160],[-813,306],[-187,1878],[133,1386]],[[133954,721013],[796,-683],[442,-1861],[433,-443],[117,-1890],[501,-840],[397,491],[279,-2136],[-1,-1873],[-414,-2628],[68,-3449],[429,-5569],[-359,-1662],[-236,-2416],[-441,-2702],[-867,-2699],[-213,1932],[-286,-2060],[-268,663],[-208,4192],[490,1686],[-537,-477],[-156,1948],[582,2169],[-76,7458],[-887,5088],[-486,-470],[-72,869],[-949,-2430],[-397,-118],[344,-1651],[-544,-5283],[-589,1664],[-115,2845]],[[312327,9345],[187,-606]],[[312514,8739],[-15,-1700]],[[312499,7039],[-350,67]],[[312149,7106],[17,912]],[[312166,8018],[43,812]],[[312209,8830],[118,515]],[[312084,13311],[6,-1330]],[[312090,11981],[-147,-92]],[[311943,11889],[-157,1158]],[[311786,13047],[223,739]],[[312009,13786],[75,-475]],[[312224,10808],[178,-1248]],[[312402,9560],[-75,-215]],[[312209,8830],[-202,84]],[[312007,8914],[-56,275]],[[311951,9189],[-19,1837]],[[311932,11026],[292,-218]],[[313107,15065],[-14,-2951]],[[313093,12114],[-156,-32]],[[312937,12082],[5,365]],[[312942,12447],[-35,2686]],[[312907,15133],[200,-68]],[[312774,6524],[70,-1308]],[[312844,5216],[-339,-41]],[[312505,5175],[62,1491]],[[312567,6666],[207,-142]],[[312709,10760],[26,-1200]],[[312735,9560],[-333,0]],[[312224,10808],[123,646]],[[312347,11454],[362,-694]],[[315123,11876],[54,-128]],[[315177,11748],[78,-1804]],[[315255,9944],[-163,-1769]],[[315092,8175],[-119,1033]],[[314973,9208],[-63,762]],[[314910,9970],[144,1736]],[[315054,11706],[69,170]],[[314670,11208],[37,-329]],[[314707,10879],[60,-642]],[[314767,10237],[-199,-737]],[[314568,9500],[-72,1128]],[[314496,10628],[174,580]],[[314052,6833],[264,-476]],[[314316,6357],[-19,-702]],[[314297,5655],[-318,75]],[[313979,5730],[73,1103]],[[312567,6666],[-68,373]],[[312514,8739],[144,137]],[[312658,8876],[116,-2352]],[[312942,12447],[-192,449]],[[312750,12896],[-16,-18]],[[312734,12878],[-7,2191]],[[312727,15069],[180,64]],[[314923,14259],[-51,-292]],[[314872,13967],[-106,119]],[[314766,14086],[83,583]],[[314849,14669],[74,-410]],[[313370,8907],[-65,-415]],[[313305,8492],[-209,83]],[[313096,8575],[-80,-29]],[[313016,8546],[-82,705]],[[312934,9251],[27,1093]],[[312961,10344],[345,-244],[64,-1193]],[[313305,8492],[-14,-2846]],[[313291,5646],[-154,398]],[[313137,6044],[-41,2531]],[[314464,12205],[-98,-1501]],[[314366,10704],[-105,-151]],[[314261,10553],[-75,982]],[[314186,11535],[35,730]],[[314221,12265],[129,637]],[[314350,12902],[114,-697]],[[314016,15105],[-14,-2136]],[[314002,12969],[-54,8]],[[313948,12977],[-196,-375]],[[313752,12602],[-27,1007]],[[313725,13609],[25,1393]],[[313750,15002],[266,103]],[[313604,15086],[-4,-1775]],[[313600,13311],[-8,-956]],[[313592,12355],[-45,-184]],[[313547,12171],[-454,-57]],[[313107,15065],[497,21]],[[314264,15070],[-76,-2600]],[[314188,12470],[-186,499]],[[314016,15105],[248,-35]],[[315464,13512],[1,-2420]],[[315465,11092],[-66,435]],[[315399,11527],[-159,1610]],[[315240,13137],[-103,1306]],[[315137,14443],[122,139]],[[315259,14582],[205,-1070]],[[314938,14054],[76,-2458]],[[315014,11596],[-176,-329]],[[314838,11267],[34,2700]],[[314923,14259],[15,-205]],[[315375,10330],[63,-1609]],[[315438,8721],[6,-426]],[[315444,8295],[-234,-827]],[[315210,7468],[-116,640]],[[315094,8108],[-2,67]],[[315255,9944],[120,386]],[[313784,8891],[255,-1302]],[[314039,7589],[13,-756]],[[313979,5730],[-242,-149]],[[313737,5581],[-39,3312]],[[313698,8893],[16,20]],[[313714,8913],[70,-22]],[[315465,11092],[-90,-762]],[[315177,11748],[222,-221]],[[313725,9098],[-11,-185]],[[313698,8893],[-269,126]],[[313429,9019],[231,2603]],[[313660,11622],[65,-2524]],[[314361,14912],[31,-1700]],[[314392,13212],[-42,-310]],[[314221,12265],[-33,205]],[[314264,15070],[97,-158]],[[312306,11771],[41,-317]],[[311932,11026],[-107,569]],[[311825,11595],[118,294]],[[312090,11981],[216,-210]],[[312937,12082],[24,-1738]],[[312934,9251],[-27,-13]],[[312907,9238],[-172,322]],[[312709,10760],[41,2136]],[[315183,5666],[-140,-215]],[[315043,5451],[-34,1224]],[[315009,6675],[174,-1009]],[[312505,5175],[-359,-115]],[[312146,5060],[3,2046]],[[314634,8728],[-21,-892]],[[314613,7836],[-92,-362]],[[314521,7474],[-160,1419]],[[314361,8893],[201,525]],[[314562,9418],[72,-690]],[[312295,14640],[81,-1252]],[[312376,13388],[-70,-1617]],[[312084,13311],[211,1329]],[[312007,8914],[159,-896]],[[312146,5060],[-281,902],[86,3227]],[[315210,7468],[60,-309]],[[315270,7159],[221,-1460]],[[315491,5699],[-308,-33]],[[315009,6675],[81,1384]],[[315090,8059],[4,49]],[[314186,11535],[-217,-675]],[[313969,10860],[-21,2117]],[[311825,11595],[-39,1452]],[[312009,13786],[157,1825]],[[312166,15611],[129,-971]],[[313137,6044],[-273,-821]],[[312864,5223],[152,3323]],[[314838,11267],[-131,-388]],[[314670,11208],[-8,753]],[[314662,11961],[28,1347]],[[314690,13308],[76,778]],[[313547,12171],[113,-549]],[[313429,9019],[-59,-112]],[[314973,9208],[-339,-480]],[[314562,9418],[6,82]],[[314767,10237],[143,-267]],[[314776,6994],[-112,-1631]],[[314664,5363],[-367,292]],[[314316,6357],[205,1117]],[[314613,7836],[163,-842]],[[312679,13385],[55,-507]],[[312376,13388],[303,-3]],[[315090,8059],[-314,-1065]],[[314662,11961],[-198,244]],[[314392,13212],[143,201]],[[314535,13413],[155,-105]],[[312679,13385],[-109,1773]],[[312570,15158],[157,-89]],[[313752,12602],[-160,-247]],[[313600,13311],[125,298]],[[314496,10628],[-130,76]],[[313604,15086],[146,-84]],[[315240,13137],[-117,-1261]],[[315054,11706],[-40,-110]],[[314938,14054],[199,389]],[[315043,5451],[-379,-88]],[[312864,5223],[-20,-7]],[[312658,8876],[249,362]],[[314001,9350],[196,-57]],[[314197,9293],[164,-400]],[[314039,7589],[-38,1761]],[[313784,8891],[217,459]],[[313969,10860],[-244,-1762]],[[313737,5581],[-446,65]],[[314261,10553],[-64,-1260]],[[312166,15611],[404,-453]],[[315579,8262],[226,-900]],[[315805,7362],[-150,-1087]],[[315655,6275],[-385,884]],[[315444,8295],[135,-33]],[[314535,13413],[108,1311]],[[314643,14724],[206,-55]],[[318309,12804],[169,-865],[-544,829],[375,36]],[[316158,12801],[-233,-1215]],[[315925,11586],[-15,-114]],[[315910,11472],[23,1756]],[[315933,13228],[225,-427]],[[315714,13936],[-106,-833]],[[315608,13103],[-144,409]],[[315259,14582],[455,-646]],[[315655,6275],[-164,-576]],[[315910,11472],[-22,-197]],[[315888,11275],[-160,-130]],[[315728,11145],[-34,41]],[[315694,11186],[-86,1917]],[[315714,13936],[219,-708]],[[315888,11275],[293,-1349]],[[316181,9926],[-216,-625]],[[315965,9301],[-242,492]],[[315723,9793],[5,1352]],[[315723,9793],[-144,-1531]],[[315438,8721],[211,2085]],[[315649,10806],[45,380]],[[315649,10806],[-184,286]],[[316256,11347],[-331,239]],[[316158,12801],[154,393],[-56,-1847]],[[316256,11347],[125,-1005],[-200,-416]],[[315965,9301],[-160,-1939]],[[316936,9055],[328,-560],[-757,-908],[-91,684],[520,784]],[[318661,1986],[376,-1084],[-740,-782],[24,1182],[340,684]],[[314361,14912],[282,-188]]],"transform":{"scale":[0.0003589261789261791,0.0000537148685138684],"translate":[-179.1473399999999,17.67439566600018]}} \ No newline at end of file diff --git a/webapp/dev/index.html b/webapp/dev/index.html new file mode 100644 index 000000000..9d45a2740 --- /dev/null +++ b/webapp/dev/index.html @@ -0,0 +1,44 @@ +<!doctype html> + +<html lang="en"> + +<head> + <meta name="viewport" content="width=device-width, initial-scale=1.0"> + <meta charset="UTF-8"> + + <!-- Bootstrap --> + <link rel="stylesheet" + href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css"> + + <!-- CSS Files --> + <link rel="stylesheet" href="../apps/css/template.css" type="text/css"> + <link rel="stylesheet" href="../apps/css/dashboard.css" type="text/css"> +</head> + + +<body> + + <!-- jQuery --> + <script + src="https://ajax.googleapis.com/ajax/libs/jquery/3.2.1/jquery.min.js"> + </script> + + <!-- Bootstrap --> + <script + src="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/js/bootstrap.min.js"> + </script> + + <!-- D3 --> + <script src="https://d3js.org/d3.v4.min.js"> </script> + + <!-- Application Specific JavaScript --> + <script type='module'> + import { CONFIG } from '../apps/config.js'; + import Dashboard from '../apps/js/DashboardDev.js'; + + new Dashboard(CONFIG); + </script> + +</body> + +</html> diff --git a/webapp/etc/examples/Dashboard.js b/webapp/etc/examples/Dashboard.js new file mode 100644 index 000000000..1d03a19c7 --- /dev/null +++ b/webapp/etc/examples/Dashboard.js @@ -0,0 +1,80 @@ + +import Footer from '../../apps/js/lib/Footer.js'; +import Header from '../../apps/js/lib/Header.js'; + +/** + * @fileoverview The dashboard for the examples + * + * @class Dashboard + * @author Brandon Clayton + */ +export class Dashboard { + + constructor() { + let footer = new Footer() + footer.removeButtons(); + footer.removeInfoIcon(); + + let header = new Header(); + header.setTitle('Examples Dashboard'); + header.setCustomMenu(Dashboard.headerMenuItems()); + + let examples = [ + { + label: 'D3 Basic Line Plot', + href: 'd3/d3-basic-line-plot.html', + }, { + label: 'D3 Custom Line Plot', + href: 'd3/d3-custom-line-plot.html', + }, + ]; + + this.createDashboard(examples); + } + + /** + * Header menu items + */ + static headerMenuItems() { + let menuItems = [ + { + label: 'Dashboard', + href: '/nshmp-haz-ws/etc/examples', + }, { + label: 'D3 Basic Line Plot', + href: '/nshmp-haz-ws/etc/examples/d3/d3-basic-line-plot.html', + }, { + label: 'D3 Custom Line Plot', + href: '/nshmp-haz-ws/etc/examples/d3/d3-custom-line-plot.html', + }, + ]; + + return menuItems; + } + + /** + * Create the dashboard + */ + createDashboard(examples) { + let elD3 = d3.select('body') + .append('div') + .attr('id', 'container') + .append('div') + .attr('id', 'dash'); + + elD3.selectAll('div') + .data(examples) + .enter() + .append('div') + .attr('class', 'col-sm-offset-3 col-sm-6') + .on('click', (d) => { window.location = d.href; }) + .append('div') + .attr('class', 'panel panel-default') + .append('div') + .attr('class', 'panel-heading') + .append('h2') + .attr('class', 'panel-title') + .text((d) => { return d.label; }); + } + +} diff --git a/webapp/etc/examples/d3/D3BasicLinePlot.js b/webapp/etc/examples/d3/D3BasicLinePlot.js new file mode 100644 index 000000000..2bbff1364 --- /dev/null +++ b/webapp/etc/examples/d3/D3BasicLinePlot.js @@ -0,0 +1,112 @@ + +import { D3LineView } from '../../../apps/js/d3/view/D3LineView.js'; +import { D3LinePlot } from '../../../apps/js/d3/D3LinePlot.js'; +import { D3LineData } from '../../../apps/js/d3/data/D3LineData.js'; +import { D3LineSubView } from '../../../apps/js/d3/view/D3LineSubView.js'; +import { D3LineSubViewOptions } from '../../../apps/js/d3/options/D3LineSubViewOptions.js'; + +import ControlPanel from '../../../apps/js/lib/ControlPanel.js'; +import { Dashboard } from '../Dashboard.js'; +import Header from '../../../apps/js/lib/Header.js'; +import Footer from '../../../apps/js/lib/Footer.js'; + +/** + * @fileoverview This is an example of plotting a simple + * line graph using the d3 package. + * + * @class D3BasicLinePlot + * @author Brandon Clayton + */ +export class D3BasicLinePlot { + + constructor() { + /* Create the footer */ + let footer = new Footer(); + footer.removeButtons(); + footer.removeInfoIcon(); + + /* Create the header */ + let header = new Header(); + header.setTitle('D3 Simple Line Plot'); + header.setCustomMenu(Dashboard.headerMenuItems()); + + /* Create a control panel */ + let controlPanel = new ControlPanel(); + + /* Container for plot(s) */ + let containerEl = document.querySelector('#content'); + + /* Create the line view */ + let lineView = this.createLineView(containerEl); + + /* Set the plot title */ + lineView.setTitle('Simple Line Plot'); + + /* Get the sub view that will have the plot on it */ + let upperSubView = lineView.upperSubView; + + /* Plot the data */ + this.plot(lineView, upperSubView); + } + + /** + * Create the view. + * + * @param {HTMLElement} containerEl The container element + */ + createLineView(containerEl) { + /* Create the upper sub view options */ + let upperSubViewOptions = D3LineSubViewOptions.upperBuilder() + .xLabel('X') + .yLabel('Y') + .filename('upper-line-plot') + .build(); + + /* Create the line view */ + let lineView = D3LineView.builder() + .containerEl(containerEl) + .upperSubViewOptions(upperSubViewOptions) + .build(); + + return lineView; + } + + /** + * Plot some data. + * + * @param {D3LineView} lineView The view + * @param {D3LineSubView} subView The sub view + */ + plot(lineView, subView) { + /* Create a new line plot */ + let linePlot = new D3LinePlot(lineView); + + /* Create the data to plot */ + let data = D3LineData.builder() + .subView(subView) + .data([ 2, 4, 6, 8 ], [ 4, 9, 25, 15 ]) + .data([ 2, 4, 6, 8 ], [ 5, 10, 8, 3 ]) + .data([ 2, 4, 6, 8 ], [ 2, 12, 15, 5 ]) + .build(); + + /* Plot the data */ + linePlot.plot(data); + + /* Set the data that will be saved */ + lineView.setSaveData(data); + + /* Create a data table in the 'Data' view */ + lineView.createDataTable(data); + + /* Create metadata to be shown in the 'Metadata' view */ + let metadata = new Map(); + metadata.set('This is some metadata', ['Some value', 'Another value']); + + /* Set the metadata */ + lineView.setMetadata(metadata); + + /* Create the metadata table */ + lineView.createMetadataTable(); + } + +} diff --git a/webapp/etc/examples/d3/D3CustomLinePlot.js b/webapp/etc/examples/d3/D3CustomLinePlot.js new file mode 100644 index 000000000..b2af026d7 --- /dev/null +++ b/webapp/etc/examples/d3/D3CustomLinePlot.js @@ -0,0 +1,244 @@ + +import { D3LineView } from '../../../apps/js/d3/view/D3LineView.js'; +import { D3LinePlot } from '../../../apps/js/d3/D3LinePlot.js'; +import { D3LineData } from '../../../apps/js/d3/data/D3LineData.js'; +import { D3LineSubView } from '../../../apps/js/d3/view/D3LineSubView.js'; + +import { D3LineSubViewOptions } from '../../../apps/js/d3/options/D3LineSubViewOptions.js'; +import { D3LineOptions } from '../../../apps/js/d3/options/D3LineOptions.js'; + +import ControlPanel from '../../../apps/js/lib/ControlPanel.js'; +import { Dashboard } from '../Dashboard.js'; +import Header from '../../../apps/js/lib/Header.js'; +import Footer from '../../../apps/js/lib/Footer.js'; + +/** + * @fileoverview This is an example of plotting a custom + * line graph using the d3 package. + * + * @class D3CustomLinePlot + * @author Brandon Clayton + */ +export class D3CustomLinePlot { + + constructor() { + /* Create the footer */ + let footer = new Footer(); + footer.removeButtons(); + footer.removeInfoIcon(); + + /* Create the header */ + let header = new Header(); + header.setTitle('D3 Custom Line Plot'); + header.setCustomMenu(Dashboard.headerMenuItems()); + + /* Create a control panel */ + let controlPanel = new ControlPanel(); + + /* Container for plot(s) */ + let containerEl = document.querySelector('#content'); + + /* Create the line view */ + let lineView = this.createLineView(containerEl); + + /* Set the plot title */ + lineView.setTitle('Custom Line Plot'); + + /* Create the line plot */ + let linePlot = new D3LinePlot(lineView); + + /* Plot the data in the upper sub view */ + let upperSubViewData = this.plotUpperSubView(linePlot, lineView); + + /* Plot the data in the lower sub view */ + let lowerSubViewData = this.plotLowerSubView(linePlot, lineView); + + /* Set the data that will be saved */ + lineView.setSaveData(upperSubViewData, lowerSubViewData); + + /* Create a data table in the 'Data' view */ + lineView.createDataTable(upperSubViewData, lowerSubViewData); + + /* Create metadata to be shown in the 'Metadata' view */ + let metadata = new Map(); + metadata.set('This is some metadata', ['Some value', 'Another value']); + + /* Set the metadata */ + lineView.setMetadata(metadata); + + /* Create the metadata table */ + lineView.createMetadataTable(); + + } + + /** + * Create the view. + * + * @param {HTMLElement} containerEl The container element + */ + createLineView(containerEl) { + /* Create the lower sub view options */ + let lowerSubViewOptions = D3LineSubViewOptions.lowerBuilder() + .xLabel('X') + .yLabel('Y') + .filename('lower-line-plot') + .build(); + + /* Create the upper sub view options */ + let upperSubViewOptions = D3LineSubViewOptions.upperBuilder() + .xLabel('X') + .yLabel('Y') + .filename('upper-line-plot') + .build(); + + /* Create the line view */ + let lineView = D3LineView.builder() + .addLowerSubView(true) + .containerEl(containerEl) + .lowerSubViewOptions(lowerSubViewOptions) + .upperSubViewOptions(upperSubViewOptions) + .build(); + + return lineView; + } + + /** + * Create the data with custom line options for the lower sub view plot. + * + * @param {D3LineSubView} lowerSubView The lower sub view + */ + createLowerSubViewData(lowerSubView) { + /* Create data for the lower sub view */ + let data = D3LineData.builder() + .subView(lowerSubView) + .data([ 0, 2, 4, 6 ], [ -5, 2, -1, 8 ]) + .data([ 0, 2, 4, 6 ], [ 5, -4, 8, -2 ]) + .build(); + + return data; + } + + /** + * Create the data with custom line options for the upper sub view plot. + * + * @param {D3LineSubView} upperSubView The upper sub view + */ + createUpperSubViewData(upperSubView) { + /** + * Create custom line options with: + * - Star markers + * - Black marker edges + * - Marker size of 12 + */ + let lineOptions1 = D3LineOptions.builder() + .label('Custom Line 1') + .markerStyle('*') + .markerEdgeColor('black') + .markerSize(12) + .build(); + + /** + * Create custom line options with: + * - Dashed line style + * - Square markers + */ + let lineOptions2 = D3LineOptions.builder() + .label('Custom Line 2') + .lineStyle('--') + .markerStyle('s') + .build(); + + /** + * Create custom line options with: + * - Dotted line style + * - Cross markers + */ + let lineOptions3 = D3LineOptions.builder() + .label('Custom Line 3') + .lineStyle(':') + .markerStyle('x') + .build(); + + /* Create data for the upper sub view */ + let data = D3LineData.builder() + .subView(upperSubView) + .data([ 2, 4, 6, 8 ], [ 4, 9, 25, 15 ], lineOptions1) + .data([ 2, 4, 6, 8 ], [ 5, 10, 8, 3 ], lineOptions2) + .data([ 2, 4, 6, 8 ], [ 2, 12, 15, 5 ], lineOptions3) + .build(); + + return data; + } + + /** + * Plot some data. + * + * @param {D3LinePlot} linePlot + * @param {D3LineView} lineView The view + */ + plotLowerSubView(linePlot, lineView) { + /* Get the lower sub view */ + let subView = lineView.lowerSubView; + + /* Create the data to plot */ + let data = this.createLowerSubViewData(subView); + + /* Plot the data */ + linePlot.plot(data); + + /* Plot a reference line at y=0 */ + linePlot.plotZeroRefLine(subView); + + return data; + } + + /** + * Plot some data. + * + * @param {D3LinePlot} linePlot + * @param {D3LineView} lineView The view + */ + plotUpperSubView(linePlot, lineView) { + /* Get the upper sub view */ + let subView = lineView.upperSubView; + + /* Create the data to plot */ + let data = this.createUpperSubViewData(subView); + + /* Plot the data */ + linePlot.plot(data); + + /* Add a reference line at y=5 */ + let yRef = 5; + let refLineEl = linePlot.plotHorizontalRefLine(subView, yRef); + + /* Add text above reference line */ + let textEl = linePlot.addText( + subView, + 4, + yRef + 0.5, + `This line is draggable: y=${yRef}`); + + /* Y limits for a dragging the reference line */ + let yLimits = [ 4, 20 ]; + + /** + * Make the reference line draggable. + * This method takes an optional callback function that is + * called when the line is dragged with arguments: + * - Number: the current Y value + * - D3LineSeriesData: the line series data + * - SVGElement: the element being dragged + */ + linePlot.makeDraggableInY(subView, refLineEl, yLimits, (y) => { + /* Move the text as the line drags */ + linePlot.moveText(subView, 4, y + 0.5, textEl); + + /* Update the text as the line drags */ + linePlot.updateText(textEl, `This line is draggable: y=${y}`); + }); + + return data; + } + +} diff --git a/webapp/etc/examples/d3/d3-basic-line-plot.html b/webapp/etc/examples/d3/d3-basic-line-plot.html new file mode 100644 index 000000000..b2336d593 --- /dev/null +++ b/webapp/etc/examples/d3/d3-basic-line-plot.html @@ -0,0 +1,40 @@ +<!doctype html> +<html lang="en"> + + <head> + <meta name="viewport" content="width=device-width, initial-scale=1.0"> + <meta charset="UTF-8"> + + <!-- Import Bootstrap CSS --> + <link rel="stylesheet" + href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.6/css/bootstrap.min.css"> + + <!-- CSS Files --> + <link rel="stylesheet" href="../../../apps/css/template.css" type="text/css"> + <link rel="stylesheet" href="../../../apps/css/D3View.css" type="text/css"> + </head> + + <body> + + <!-- Plots --> + <div id="content"></div> + + <!-- Import jQuery --> + <script src="https://ajax.googleapis.com/ajax/libs/jquery/3.2.1/jquery.min.js" + charset="utf-8"></script> + <!-- Import Bootstrap JavaScript --> + <script src="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/js/bootstrap.min.js" + charset="utf-8"></script> + <!-- Import D3 --> + <script src="https://d3js.org/d3.v4.min.js" charset="utf-8"></script> + + <!-- Run main JavaScript --> + <script type="module"> + import { D3BasicLinePlot } from './D3BasicLinePlot.js'; + + new D3BasicLinePlot(); + </script> + + </body> + +</html> diff --git a/webapp/etc/examples/d3/d3-custom-line-plot.html b/webapp/etc/examples/d3/d3-custom-line-plot.html new file mode 100644 index 000000000..5d257a2cc --- /dev/null +++ b/webapp/etc/examples/d3/d3-custom-line-plot.html @@ -0,0 +1,40 @@ +<!doctype html> +<html lang="en"> + + <head> + <meta name="viewport" content="width=device-width, initial-scale=1.0"> + <meta charset="UTF-8"> + + <!-- Import Bootstrap CSS --> + <link rel="stylesheet" + href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.6/css/bootstrap.min.css"> + + <!-- CSS Files --> + <link rel="stylesheet" href="../../../apps/css/template.css" type="text/css"> + <link rel="stylesheet" href="../../../apps/css/D3View.css" type="text/css"> + </head> + + <body> + + <!-- Plots --> + <div id="content"></div> + + <!-- Import jQuery --> + <script src="https://ajax.googleapis.com/ajax/libs/jquery/3.2.1/jquery.min.js" + charset="utf-8"></script> + <!-- Import Bootstrap JavaScript --> + <script src="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/js/bootstrap.min.js" + charset="utf-8"></script> + <!-- Import D3 --> + <script src="https://d3js.org/d3.v4.min.js" charset="utf-8"></script> + + <!-- Run main JavaScript --> + <script type="module"> + import { D3CustomLinePlot } from './D3CustomLinePlot.js'; + + new D3CustomLinePlot(); + </script> + + </body> + +</html> diff --git a/webapp/etc/examples/index.html b/webapp/etc/examples/index.html new file mode 100644 index 000000000..f510434b2 --- /dev/null +++ b/webapp/etc/examples/index.html @@ -0,0 +1,43 @@ +<!doctype html> + +<html lang="en"> + + <head> + <meta name="viewport" content="width=device-width, initial-scale=1.0"> + <meta charset="UTF-8"> + + <!-- Bootstrap --> + <link rel="stylesheet" + href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css"> + + <!-- CSS Files --> + <link rel="stylesheet" href="../../apps/css/template.css" type="text/css"> + <link rel="stylesheet" href="../../apps/css/dashboard.css" type="text/css"> + </head> + + + <body> + + <!-- jQuery --> + <script + src="https://ajax.googleapis.com/ajax/libs/jquery/3.2.1/jquery.min.js"> + </script> + + <!-- Bootstrap --> + <script + src="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/js/bootstrap.min.js"> + </script> + + <!-- D3 --> + <script src="https://d3js.org/d3.v4.min.js"> </script> + + <!-- Application Specific JavaScript --> + <script type='module'> + import { Dashboard } from './Dashboard.js'; + + new Dashboard(); + </script> + + </body> + +</html> diff --git a/webapp/favicon.ico b/webapp/favicon.ico new file mode 100644 index 0000000000000000000000000000000000000000..0c3e87c87b8ecc9900a3b6368598be574e9d0716 GIT binary patch literal 10990 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 literal 0 HcmV?d00001 diff --git a/webapp/index.html b/webapp/index.html new file mode 100644 index 000000000..3c76b0fa1 --- /dev/null +++ b/webapp/index.html @@ -0,0 +1,44 @@ +<!doctype html> + +<html lang="en"> + +<head> + <meta name="viewport" content="width=device-width, initial-scale=1.0"> + <meta charset="UTF-8"> + + <!-- Bootstrap --> + <link rel="stylesheet" + href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css"> + + <!-- CSS Files --> + <link rel="stylesheet" href="apps/css/template.css" type="text/css"> + <link rel="stylesheet" href="apps/css/dashboard.css" type="text/css"> +</head> + + +<body> + + <!-- jQuery --> + <script + src="https://ajax.googleapis.com/ajax/libs/jquery/3.2.1/jquery.min.js"> + </script> + + <!-- Bootstrap --> + <script + src="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/js/bootstrap.min.js"> + </script> + + <!-- D3 --> + <script src="https://d3js.org/d3.v4.min.js"> </script> + + <!-- Application Specific JavaScript --> + <script type='module'> + import { CONFIG } from './apps/config.js'; + import Dashboard from './apps/js/Dashboard.js'; + + new Dashboard(CONFIG); + </script> + +</body> + +</html> -- GitLab